@mutmutco/hub 4.4.5 → 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 +529 -269
- 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,
|
|
@@ -80,18 +82,17 @@ __export(index_exports, {
|
|
|
80
82
|
schedulerStatus: () => schedulerStatus,
|
|
81
83
|
sharedDocLockPath: () => sharedDocLockPath,
|
|
82
84
|
sharedDocLockTimeoutReceipt: () => sharedDocLockTimeoutReceipt,
|
|
85
|
+
shippedTargetFrom: () => shippedTargetFrom,
|
|
83
86
|
shouldHandOff: () => shouldHandOff,
|
|
84
|
-
signOff: () => signOff,
|
|
85
|
-
startSpinner: () => startSpinner,
|
|
86
87
|
surfaceArmPlan: () => surfaceArmPlan,
|
|
87
88
|
systemdServiceUnit: () => systemdServiceUnit,
|
|
88
89
|
systemdTimerUnit: () => systemdTimerUnit,
|
|
89
90
|
taskXml: () => taskXml,
|
|
90
91
|
updateHeader: () => updateHeader,
|
|
92
|
+
updaterFace: () => updaterFace,
|
|
91
93
|
watcherLockPath: () => watcherLockPath,
|
|
92
|
-
welcome: () => welcome,
|
|
93
94
|
withSharedDocLock: () => withSharedDocLock,
|
|
94
|
-
wrapWords: () =>
|
|
95
|
+
wrapWords: () => wrapWords2,
|
|
95
96
|
writeLaunchTarget: () => writeLaunchTarget,
|
|
96
97
|
writePending: () => writePending
|
|
97
98
|
});
|
|
@@ -100,6 +101,206 @@ var import_node_child_process9 = require("node:child_process");
|
|
|
100
101
|
var import_node_fs18 = require("node:fs");
|
|
101
102
|
var import_node_path13 = require("node:path");
|
|
102
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
|
+
|
|
103
304
|
// src/state.ts
|
|
104
305
|
var import_node_fs = require("node:fs");
|
|
105
306
|
var import_node_os = require("node:os");
|
|
@@ -164,11 +365,12 @@ function withLease(path, fn, waitMs = LEASE_WAIT_MS) {
|
|
|
164
365
|
for (; ; ) {
|
|
165
366
|
const attempt = tryAcquire(path);
|
|
166
367
|
if (typeof attempt === "object") {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
attempt.release();
|
|
368
|
+
const result = fn();
|
|
369
|
+
if (result && typeof result.then === "function") {
|
|
370
|
+
return result.finally(() => attempt.release());
|
|
171
371
|
}
|
|
372
|
+
attempt.release();
|
|
373
|
+
return result;
|
|
172
374
|
}
|
|
173
375
|
if (Date.now() >= deadline) {
|
|
174
376
|
throw new UpdaterBusy(`another updater holds ${path} (waited ${waitMs}ms) \u2014 deferring`);
|
|
@@ -187,23 +389,29 @@ function appendJournal(path, event) {
|
|
|
187
389
|
return false;
|
|
188
390
|
}
|
|
189
391
|
}
|
|
190
|
-
function journalHighWater(path, surface) {
|
|
392
|
+
function journalHighWater(path, surface, events) {
|
|
191
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 = [];
|
|
192
404
|
try {
|
|
193
405
|
for (const line of (0, import_node_fs.readFileSync)(path, "utf8").split("\n")) {
|
|
194
406
|
if (!line.trim()) continue;
|
|
195
407
|
try {
|
|
196
|
-
|
|
197
|
-
if (event.dryRun) continue;
|
|
198
|
-
if (event.surface === surface && event.verdict === "ok" && event.to && (!high || compareSemver(event.to, high) > 0)) {
|
|
199
|
-
high = event.to;
|
|
200
|
-
}
|
|
408
|
+
events.push(JSON.parse(line));
|
|
201
409
|
} catch {
|
|
202
410
|
}
|
|
203
411
|
}
|
|
204
412
|
} catch {
|
|
205
413
|
}
|
|
206
|
-
return
|
|
414
|
+
return events;
|
|
207
415
|
}
|
|
208
416
|
function journalSurfaceRepeatedFail(path, surface, currentRun) {
|
|
209
417
|
let lastVerdict = null;
|
|
@@ -254,6 +462,9 @@ function fileExists(path) {
|
|
|
254
462
|
return (0, import_node_fs.existsSync)(path);
|
|
255
463
|
}
|
|
256
464
|
|
|
465
|
+
// src/reconcile.ts
|
|
466
|
+
var import_node_perf_hooks2 = require("node:perf_hooks");
|
|
467
|
+
|
|
257
468
|
// src/npm.ts
|
|
258
469
|
var import_node_child_process = require("node:child_process");
|
|
259
470
|
var import_node_fs2 = require("node:fs");
|
|
@@ -294,10 +505,40 @@ function runNpm(args, env = process.env, options = {}) {
|
|
|
294
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 });
|
|
295
506
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
296
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();
|
|
297
534
|
function globalRoot(env = process.env) {
|
|
535
|
+
const key = env;
|
|
536
|
+
const memoized = globalRootMemo.get(key);
|
|
537
|
+
if (memoized !== void 0) return memoized;
|
|
298
538
|
const root = runNpm(["root", "-g"], env);
|
|
299
|
-
|
|
300
|
-
|
|
539
|
+
const resolved = root.status !== 0 || !root.stdout.trim() ? null : root.stdout.trim();
|
|
540
|
+
globalRootMemo.set(key, resolved);
|
|
541
|
+
return resolved;
|
|
301
542
|
}
|
|
302
543
|
function npmView(spec, field, env = process.env) {
|
|
303
544
|
const args = ["view", spec, "--json"];
|
|
@@ -317,8 +558,15 @@ function parseNpmDistIntegrity(value) {
|
|
|
317
558
|
return typeof only === "string" && only.length > 0 ? only : null;
|
|
318
559
|
}
|
|
319
560
|
var npmStringField = parseNpmDistIntegrity;
|
|
320
|
-
function
|
|
321
|
-
|
|
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
|
+
}
|
|
322
570
|
}
|
|
323
571
|
function npmVersions(packageName, env = process.env) {
|
|
324
572
|
const versions = npmView(packageName, "versions", env);
|
|
@@ -398,38 +646,60 @@ function gateCandidate(cachePath, repoUrl, candidate) {
|
|
|
398
646
|
}
|
|
399
647
|
return { ok: true, tagCommit: commit, bom };
|
|
400
648
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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) => {
|
|
404
668
|
if (row.version !== candidate) {
|
|
405
|
-
|
|
406
|
-
|
|
669
|
+
verdicts[index] = { id: row.id, ok: false, detail: `BOM row at ${row.version}, candidate ${candidate}` };
|
|
670
|
+
return;
|
|
407
671
|
}
|
|
408
672
|
const manifestText = showFile(cachePath, tagCommit, `${row.identity.packagePath}/package.json`);
|
|
409
673
|
let name;
|
|
410
|
-
let
|
|
674
|
+
let manifestVersion2;
|
|
411
675
|
try {
|
|
412
676
|
const manifest2 = JSON.parse(manifestText ?? "");
|
|
413
677
|
name = manifest2.name;
|
|
414
|
-
|
|
678
|
+
manifestVersion2 = manifest2.version;
|
|
415
679
|
} catch {
|
|
416
680
|
}
|
|
417
|
-
if (!name ||
|
|
418
|
-
|
|
419
|
-
|
|
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;
|
|
420
684
|
}
|
|
421
|
-
|
|
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];
|
|
422
690
|
if (!integrity) {
|
|
423
|
-
|
|
424
|
-
|
|
691
|
+
verdicts[entry.index] = { id: entry.id, name: entry.name, ok: false, detail: `${entry.name}@${candidate} not on npm` };
|
|
692
|
+
return;
|
|
425
693
|
}
|
|
694
|
+
const row = bomRows[entry.index];
|
|
426
695
|
const legacySsri = /^sha\d+-/.test(row.identity.value ?? "");
|
|
427
696
|
if (legacySsri && integrity !== row.identity.value) {
|
|
428
|
-
|
|
429
|
-
|
|
697
|
+
verdicts[entry.index] = { id: entry.id, name: entry.name, ok: false, detail: `${entry.name}@${candidate} dist.integrity differs from BOM identity` };
|
|
698
|
+
return;
|
|
430
699
|
}
|
|
431
|
-
|
|
432
|
-
}
|
|
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" });
|
|
433
703
|
return { ok: rows.length > 0 && rows.every((row) => row.ok), rows };
|
|
434
704
|
}
|
|
435
705
|
|
|
@@ -605,6 +875,15 @@ function probeVersion(distPath) {
|
|
|
605
875
|
function tail(output) {
|
|
606
876
|
return output.split("\n").filter(Boolean).slice(-3).join(" | ");
|
|
607
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
|
+
}
|
|
608
887
|
function stagePackage(options) {
|
|
609
888
|
const { surface, packageName, target, witness, paths, env } = options;
|
|
610
889
|
const stageRoot = (0, import_node_path4.join)(paths.staging, surface, target);
|
|
@@ -632,12 +911,17 @@ function npmGlobalArm(options) {
|
|
|
632
911
|
const { surface, packageName, distRel, target, dryRun, paths } = options;
|
|
633
912
|
const spec = `${packageName}@${target}`;
|
|
634
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
|
+
}
|
|
635
920
|
const dist = globalDist(env, packageName, distRel);
|
|
636
921
|
const installed = dist ? probeVersion(dist) : null;
|
|
637
922
|
if (installed && compareSemver(installed, target) > 0) {
|
|
638
923
|
return result(installed, installed, "skip", `installed ${installed} is above candidate ${target} (monotonic)`);
|
|
639
924
|
}
|
|
640
|
-
const highWater = journalHighWater(paths.journalPath, surface);
|
|
641
925
|
if (highWater && compareSemver(highWater, target) > 0) {
|
|
642
926
|
return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${target} (monotonic)`);
|
|
643
927
|
}
|
|
@@ -861,7 +1145,7 @@ function claudeArm(options) {
|
|
|
861
1145
|
if (from && compareSemver(from, target) > 0) {
|
|
862
1146
|
return result(from, from, "skip", `installed ${from} is above candidate ${target} (monotonic)`);
|
|
863
1147
|
}
|
|
864
|
-
const highWater = journalHighWater(paths.journalPath, "claude");
|
|
1148
|
+
const highWater = journalHighWater(paths.journalPath, "claude", options.journalEvents);
|
|
865
1149
|
if (highWater && compareSemver(highWater, target) > 0) {
|
|
866
1150
|
return result(from, highWater, "skip", `journal high-water ${highWater} is above candidate ${target} (monotonic)`);
|
|
867
1151
|
}
|
|
@@ -1421,7 +1705,7 @@ function cursorArm(options) {
|
|
|
1421
1705
|
if (installed && compareSemver(installed, target) > 0) {
|
|
1422
1706
|
return result(installed, installed, "skip", `installed ${installed} is above candidate ${target} (monotonic)`);
|
|
1423
1707
|
}
|
|
1424
|
-
const highWater = journalHighWater(paths.journalPath, "cursor");
|
|
1708
|
+
const highWater = journalHighWater(paths.journalPath, "cursor", options.journalEvents);
|
|
1425
1709
|
if (highWater && compareSemver(highWater, target) > 0) {
|
|
1426
1710
|
return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${target} (monotonic)`);
|
|
1427
1711
|
}
|
|
@@ -1565,11 +1849,11 @@ function parseListText(stdout) {
|
|
|
1565
1849
|
}
|
|
1566
1850
|
function probeJervcodeInstallation(env = process.env, runner = runHostCommand) {
|
|
1567
1851
|
const manifestLocation = materializedManifestLocation(env);
|
|
1568
|
-
const
|
|
1852
|
+
const manifestVersion2 = installedVersion(env);
|
|
1569
1853
|
const manifestEvidence = () => ({
|
|
1570
|
-
version:
|
|
1854
|
+
version: manifestVersion2,
|
|
1571
1855
|
location: manifestLocation,
|
|
1572
|
-
detail:
|
|
1856
|
+
detail: manifestVersion2 ? "materialized pi package manifest" : "materialized pi package manifest was unreadable"
|
|
1573
1857
|
});
|
|
1574
1858
|
const hosted = hostEnv(env);
|
|
1575
1859
|
const command = hostCommands(env).find((bin) => runner(bin, ["--version"], hosted).status === 0);
|
|
@@ -1585,9 +1869,9 @@ function probeJervcodeInstallation(env = process.env, runner = runHostCommand) {
|
|
|
1585
1869
|
};
|
|
1586
1870
|
}
|
|
1587
1871
|
const hostError = result.status !== 0 ? `${hostLocation} failed: ${failure2(result)}` : `${hostLocation} did not list npm:${PI_PACKAGE}`;
|
|
1588
|
-
if (
|
|
1872
|
+
if (manifestVersion2) {
|
|
1589
1873
|
return {
|
|
1590
|
-
version:
|
|
1874
|
+
version: manifestVersion2,
|
|
1591
1875
|
location: manifestLocation,
|
|
1592
1876
|
detail: `${hostError}; using materialized pi package manifest`
|
|
1593
1877
|
};
|
|
@@ -1618,7 +1902,7 @@ function jervcodeArm(options) {
|
|
|
1618
1902
|
if (!before) return result(null, options.target, "defer", `cannot read ${(0, import_node_path9.join)(jervcodeAgentDir(env), "settings.json")}`);
|
|
1619
1903
|
const from = installedVersion(env);
|
|
1620
1904
|
const legacy = before.filter(isLegacyMmiPiPath);
|
|
1621
|
-
const highWater = journalHighWater(options.paths.journalPath, "jervcode");
|
|
1905
|
+
const highWater = journalHighWater(options.paths.journalPath, "jervcode", options.journalEvents);
|
|
1622
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;
|
|
1623
1907
|
if (options.dryRun) {
|
|
1624
1908
|
const action = floor ? `hold ${from ?? "the install"} and skip the install (${floor})` : before.includes(exact) && from === options.target ? "keep exact pin" : `install ${exact}`;
|
|
@@ -1810,7 +2094,7 @@ function hermesHomes(env = process.env) {
|
|
|
1810
2094
|
var VERDICT_RANK = { fail: 3, defer: 2, ok: 1, skip: 0 };
|
|
1811
2095
|
function hermesArm(options) {
|
|
1812
2096
|
const env = options.env ?? process.env;
|
|
1813
|
-
if (!hermesHostPresent(env, options.hostCommandRunner)) {
|
|
2097
|
+
if (!(options.hostPresent ?? hermesHostPresent(env, options.hostCommandRunner))) {
|
|
1814
2098
|
return {
|
|
1815
2099
|
surface: "hermes",
|
|
1816
2100
|
from: null,
|
|
@@ -1853,7 +2137,7 @@ function armHermesHome(home, env, options, primary) {
|
|
|
1853
2137
|
return result(installed, options.target, "defer", `${live} carries no ${PACKAGE2} package identity \u2014 refusing to displace an unmanaged Hermes mmi plugin`);
|
|
1854
2138
|
}
|
|
1855
2139
|
if (installed && compareSemver(installed, options.target) > 0) return result(installed, installed, "skip", `installed ${installed} is above candidate ${options.target} (monotonic)`);
|
|
1856
|
-
const highWater = primary ? journalHighWater(options.paths.journalPath, "hermes") : null;
|
|
2140
|
+
const highWater = primary ? journalHighWater(options.paths.journalPath, "hermes", options.journalEvents) : null;
|
|
1857
2141
|
if (highWater && compareSemver(highWater, options.target) > 0) return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${options.target} (monotonic)`);
|
|
1858
2142
|
const hostRunner = options.hostCommandRunner ?? runHostCommand;
|
|
1859
2143
|
if (installed === options.target && treeHealthy2(live)) {
|
|
@@ -1955,6 +2239,10 @@ function probeHermesLaunchStaging(env = process.env) {
|
|
|
1955
2239
|
// src/reap.ts
|
|
1956
2240
|
var import_node_fs14 = require("node:fs");
|
|
1957
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
|
+
}
|
|
1958
2246
|
var KEEP_GENERATIONS2 = 2;
|
|
1959
2247
|
var SCRATCH_AGE_MS = 24 * 60 * 6e4;
|
|
1960
2248
|
function listDirs(path) {
|
|
@@ -2051,10 +2339,11 @@ function reap(options) {
|
|
|
2051
2339
|
const { paths, dryRun } = options;
|
|
2052
2340
|
const env = options.env ?? process.env;
|
|
2053
2341
|
const guarded = (category, run) => {
|
|
2342
|
+
const startedAt = import_node_perf_hooks.performance.now();
|
|
2054
2343
|
try {
|
|
2055
|
-
return run();
|
|
2344
|
+
return { ...run(), durationMs: elapsedMs(startedAt) };
|
|
2056
2345
|
} catch (error) {
|
|
2057
|
-
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) };
|
|
2058
2347
|
}
|
|
2059
2348
|
};
|
|
2060
2349
|
const atomicWritePaths = [claudeMarketplacesPath(env)];
|
|
@@ -2071,8 +2360,9 @@ function shouldHandOff(self, options) {
|
|
|
2071
2360
|
if (!options.dist) return false;
|
|
2072
2361
|
return self.verdict === "ok" && Boolean(self.from) && self.from !== self.to;
|
|
2073
2362
|
}
|
|
2074
|
-
function reconcile(options) {
|
|
2363
|
+
async function reconcile(options) {
|
|
2075
2364
|
const env = options.env ?? process.env;
|
|
2365
|
+
const shipped = options.shippedTarget?.trim() || null;
|
|
2076
2366
|
const say = (line) => {
|
|
2077
2367
|
options.narrate?.(line);
|
|
2078
2368
|
options.onPhase?.(line);
|
|
@@ -2086,9 +2376,9 @@ function reconcile(options) {
|
|
|
2086
2376
|
if (!appendJournal(paths.journalPath, { ts: (/* @__PURE__ */ new Date()).toISOString(), run, ...stamped })) summary.journalOk = false;
|
|
2087
2377
|
};
|
|
2088
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 };
|
|
2089
|
-
const recordArm = (arm) => {
|
|
2379
|
+
const recordArm = (arm, durationMs) => {
|
|
2090
2380
|
if (arm.verdict === "fail") arm.repeatFailure = journalSurfaceRepeatedFail(paths.journalPath, arm.surface, run);
|
|
2091
|
-
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 });
|
|
2092
2382
|
options.onArm?.(arm);
|
|
2093
2383
|
if (arm.verdict === "ok" && !options.dryRun && !arm.launchStaged) clearPending(paths, arm.surface);
|
|
2094
2384
|
if (arm.verdict === "defer") summary.exit = Math.max(summary.exit, 2);
|
|
@@ -2097,7 +2387,8 @@ function reconcile(options) {
|
|
|
2097
2387
|
try {
|
|
2098
2388
|
say("acquiring single-writer lease");
|
|
2099
2389
|
const leaseWaitMs = Number(env.MMI_UPDATER_LEASE_WAIT_MS) || void 0;
|
|
2100
|
-
return withLease(paths.leasePath, () => {
|
|
2390
|
+
return await withLease(paths.leasePath, async () => {
|
|
2391
|
+
const journalEvents = parseJournal(paths.journalPath);
|
|
2101
2392
|
if (!options.dryRun) {
|
|
2102
2393
|
const pendingBefore = readPending(paths);
|
|
2103
2394
|
if (pendingBefore.length) {
|
|
@@ -2106,53 +2397,69 @@ function reconcile(options) {
|
|
|
2106
2397
|
}
|
|
2107
2398
|
}
|
|
2108
2399
|
summary.reap = reap({ paths, dryRun: options.dryRun, env });
|
|
2109
|
-
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 });
|
|
2110
2401
|
for (const row of summary.reap.filter((entry) => entry.verdict !== "skip")) say(`reap ${row.category}: ${row.verdict}${row.detail ? " \u2014 " + row.detail : ""}`);
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
summary.
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
journal({ kind: "candidate-reject", to: candidate, verdict: "skip", detail: gate.reason ?? "gate rejected" });
|
|
2128
|
-
continue;
|
|
2402
|
+
if (shipped) {
|
|
2403
|
+
say(`shipped target v${shipped} \u2014 the payload is the release; no npm poll, no gate`);
|
|
2404
|
+
summary.target = shipped;
|
|
2405
|
+
summary.gate = "shipped payload";
|
|
2406
|
+
summary.managedSurfaces = null;
|
|
2407
|
+
journal({ kind: "target", to: shipped, verdict: "ok", detail: "shipped payload" });
|
|
2408
|
+
} else {
|
|
2409
|
+
say("polling npm for @mutmutco/cli versions");
|
|
2410
|
+
const pollStartedAt = import_node_perf_hooks2.performance.now();
|
|
2411
|
+
const versions = npmVersions("@mutmutco/cli", env).sort((a, b) => compareSemver(b, a));
|
|
2412
|
+
const pollMs = elapsedMs(pollStartedAt);
|
|
2413
|
+
if (!versions.length) {
|
|
2414
|
+
journal({ kind: "poll", verdict: "fail", durationMs: pollMs, detail: "@mutmutco/cli has no npm versions \u2014 cannot discover a candidate" });
|
|
2415
|
+
summary.exit = 1;
|
|
2416
|
+
summary.detail = "npm poll returned no versions for @mutmutco/cli";
|
|
2417
|
+
return summary;
|
|
2129
2418
|
}
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2419
|
+
journal({ kind: "poll", verdict: "ok", count: versions.length, durationMs: pollMs, detail: `${versions.length} versions; newest ${versions[0]}` });
|
|
2420
|
+
say("fetching release BOM gate cache");
|
|
2421
|
+
const fetchStartedAt = import_node_perf_hooks2.performance.now();
|
|
2422
|
+
ensureBareCache(paths.gitCache, repoUrl);
|
|
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();
|
|
2426
|
+
for (const candidate of versions.slice(0, bound)) {
|
|
2427
|
+
say(`gating candidate v${candidate}`);
|
|
2428
|
+
const candidateStartedAt = import_node_perf_hooks2.performance.now();
|
|
2429
|
+
const gate = gateCandidate(paths.gitCache, repoUrl, candidate);
|
|
2430
|
+
if (!gate.ok) {
|
|
2431
|
+
say(`candidate v${candidate} rejected (${gate.reason ?? "gate"})`);
|
|
2432
|
+
journal({ kind: "candidate-reject", to: candidate, verdict: "skip", durationMs: elapsedMs(candidateStartedAt), detail: gate.reason ?? "gate rejected" });
|
|
2433
|
+
continue;
|
|
2434
|
+
}
|
|
2435
|
+
const atomicity = await checkAtomicity(paths.gitCache, gate.tagCommit, gate.bom, candidate, {
|
|
2436
|
+
distIntegrity: (spec) => npmDistIntegrityAsync(spec, env)
|
|
2437
|
+
});
|
|
2438
|
+
if (!atomicity.ok) {
|
|
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("; ")}` });
|
|
2440
|
+
continue;
|
|
2441
|
+
}
|
|
2442
|
+
summary.target = candidate;
|
|
2443
|
+
summary.managedSurfaces = Array.isArray(gate.bom?.managedSurfaces) ? gate.bom.managedSurfaces.map(String) : null;
|
|
2444
|
+
summary.gate = `tag v${candidate} reachable from main, BOM stamp coherent`;
|
|
2445
|
+
summary.atomicity = atomicity;
|
|
2446
|
+
journal({ kind: "target", to: candidate, verdict: "ok", durationMs: elapsedMs(candidateStartedAt), detail: summary.gate });
|
|
2447
|
+
break;
|
|
2136
2448
|
}
|
|
2137
|
-
summary.target
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
say("no green target \u2014 deferred");
|
|
2146
|
-
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` });
|
|
2147
|
-
for (const probe of enumerateSurfaces(env)) {
|
|
2148
|
-
summary.surfaces.push({ id: probe.id, present: probe.present, action: probe.present ? "deferred-no-target" : "absent-skip" });
|
|
2149
|
-
journal({ kind: "surface", surface: probe.id, present: probe.present, verdict: "skip", detail: probe.present ? "present; no green target yet" : `absent (${probe.marker}) \u2014 skipped` });
|
|
2449
|
+
if (!summary.target) {
|
|
2450
|
+
say("no green target \u2014 deferred");
|
|
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` });
|
|
2452
|
+
for (const probe of enumerateSurfaces(env)) {
|
|
2453
|
+
summary.surfaces.push({ id: probe.id, present: probe.present, action: probe.present ? "deferred-no-target" : "absent-skip" });
|
|
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` });
|
|
2455
|
+
}
|
|
2456
|
+
return summary;
|
|
2150
2457
|
}
|
|
2151
|
-
return summary;
|
|
2152
2458
|
}
|
|
2153
2459
|
say(`target v${summary.target} (${summary.gate})`);
|
|
2154
2460
|
options.onTargetResolved?.(summary.target, summary.gate);
|
|
2155
2461
|
say("updater: self-bootstrap");
|
|
2462
|
+
const selfStartedAt = import_node_perf_hooks2.performance.now();
|
|
2156
2463
|
const self = npmGlobalArm({
|
|
2157
2464
|
surface: "updater",
|
|
2158
2465
|
packageName: "@mutmutco/hub",
|
|
@@ -2160,10 +2467,11 @@ function reconcile(options) {
|
|
|
2160
2467
|
target: summary.target,
|
|
2161
2468
|
dryRun: options.dryRun,
|
|
2162
2469
|
paths,
|
|
2163
|
-
env
|
|
2470
|
+
env,
|
|
2471
|
+
journalEvents
|
|
2164
2472
|
});
|
|
2165
2473
|
summary.updater = self;
|
|
2166
|
-
recordArm(self);
|
|
2474
|
+
recordArm(self, elapsedMs(selfStartedAt));
|
|
2167
2475
|
say(`updater: ${self.verdict}${self.detail ? " \u2014 " + self.detail : ""}`);
|
|
2168
2476
|
const handoffDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2169
2477
|
if (shouldHandOff(self, { dryRun: options.dryRun, dist: handoffDist, guard: env.MMI_UPDATER_REEXEC })) {
|
|
@@ -2173,6 +2481,7 @@ function reconcile(options) {
|
|
|
2173
2481
|
return summary;
|
|
2174
2482
|
}
|
|
2175
2483
|
say(`cli: converging to v${summary.target}`);
|
|
2484
|
+
const cliStartedAt = import_node_perf_hooks2.performance.now();
|
|
2176
2485
|
const cli = npmGlobalArm({
|
|
2177
2486
|
surface: "cli",
|
|
2178
2487
|
packageName: "@mutmutco/cli",
|
|
@@ -2180,10 +2489,11 @@ function reconcile(options) {
|
|
|
2180
2489
|
target: summary.target,
|
|
2181
2490
|
dryRun: options.dryRun,
|
|
2182
2491
|
paths,
|
|
2183
|
-
env
|
|
2492
|
+
env,
|
|
2493
|
+
journalEvents
|
|
2184
2494
|
});
|
|
2185
2495
|
summary.cli = cli;
|
|
2186
|
-
recordArm(cli);
|
|
2496
|
+
recordArm(cli, elapsedMs(cliStartedAt));
|
|
2187
2497
|
say(`cli: ${cli.verdict}${cli.detail ? " \u2014 " + cli.detail : ""}`);
|
|
2188
2498
|
for (const probe of enumerateSurfaces(env)) {
|
|
2189
2499
|
if (!probe.present) {
|
|
@@ -2198,11 +2508,12 @@ function reconcile(options) {
|
|
|
2198
2508
|
continue;
|
|
2199
2509
|
}
|
|
2200
2510
|
say(`${probe.id}: converging to v${summary.target}`);
|
|
2201
|
-
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;
|
|
2202
2513
|
if (arm) {
|
|
2203
2514
|
summary.plugins.push(arm);
|
|
2204
2515
|
summary.surfaces.push({ id: probe.id, present: true, action: `arm-${arm.verdict}` });
|
|
2205
|
-
recordArm(arm);
|
|
2516
|
+
recordArm(arm, elapsedMs(armStartedAt));
|
|
2206
2517
|
say(`${probe.id}: ${arm.verdict}${arm.detail ? " \u2014 " + arm.detail : ""}`);
|
|
2207
2518
|
} else {
|
|
2208
2519
|
summary.surfaces.push({ id: probe.id, present: true, action: "arm-pending" });
|
|
@@ -2914,28 +3225,50 @@ var ARM_TITLE = {
|
|
|
2914
3225
|
hermes: "Arming Hermes",
|
|
2915
3226
|
jervcode: "Arming JervCode"
|
|
2916
3227
|
};
|
|
2917
|
-
var
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
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"
|
|
2924
3239
|
};
|
|
2925
|
-
function
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
const kind = classify(arm)
|
|
2930
|
-
const
|
|
2931
|
-
const
|
|
2932
|
-
const
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
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) {
|
|
2939
3272
|
if (available < 1) return [text];
|
|
2940
3273
|
const lines = [];
|
|
2941
3274
|
let current = "";
|
|
@@ -2956,7 +3289,7 @@ function noteLines(prefix, note, width) {
|
|
|
2956
3289
|
const available = width - used - 1;
|
|
2957
3290
|
const indent = available >= MIN_NOTE_SPACE ? " ".repeat(used + 1) : " ".repeat(Math.min(used + 1, Math.max(2, width - MIN_NOTE_SPACE)));
|
|
2958
3291
|
const room = available >= MIN_NOTE_SPACE ? available : width - indent.length;
|
|
2959
|
-
const parts =
|
|
3292
|
+
const parts = wrapWords2(note, room);
|
|
2960
3293
|
const first = available >= MIN_NOTE_SPACE ? prefix + " " + parts[0] : prefix.replace(/\s+$/, "");
|
|
2961
3294
|
const lines = [first];
|
|
2962
3295
|
for (const part of parts.slice(available >= MIN_NOTE_SPACE ? 1 : 0)) lines.push(indent + part);
|
|
@@ -2979,7 +3312,7 @@ function collectRows(summary) {
|
|
|
2979
3312
|
}
|
|
2980
3313
|
function headline(detail, width) {
|
|
2981
3314
|
const first = (detail ?? "").split(/(?<=[.;:])\s/)[0]?.replace(/\s+/g, " ").trim() ?? "";
|
|
2982
|
-
return
|
|
3315
|
+
return wrapWords2(first || "blocked", Math.max(10, width - 4))[0];
|
|
2983
3316
|
}
|
|
2984
3317
|
function updateHeader(target, dryRun = false, options) {
|
|
2985
3318
|
return brandLine(
|
|
@@ -3081,124 +3414,6 @@ function formatReconcileReport(summary, options = {}) {
|
|
|
3081
3414
|
return out.join("\n") + "\n";
|
|
3082
3415
|
}
|
|
3083
3416
|
|
|
3084
|
-
// src/face.ts
|
|
3085
|
-
var DEFAULT_WIDTH2 = 100;
|
|
3086
|
-
function welcome(color, output = process.stdout) {
|
|
3087
|
-
const mark = color ? MM_GREEN + "\u25C6" + RESET : "\u25C6";
|
|
3088
|
-
output.write(`${mark} ${accent("mmi-hub", color)} \u2014 Mutatis Mutandis
|
|
3089
|
-
`);
|
|
3090
|
-
output.write("\u2502\n");
|
|
3091
|
-
output.write("\u2502 Welcome back. Checking your surfaces\u2026\n");
|
|
3092
|
-
output.write("\u2502\n");
|
|
3093
|
-
}
|
|
3094
|
-
var TONES = { "\u2714": MM_GREEN, "\u2716": RED, "\u25CF": YELLOW };
|
|
3095
|
-
function paintGlyph(line, color) {
|
|
3096
|
-
if (!color) return line;
|
|
3097
|
-
const glyph = line.trimStart()[0];
|
|
3098
|
-
const tone = TONES[glyph];
|
|
3099
|
-
if (!tone) return line;
|
|
3100
|
-
const at = line.indexOf(glyph);
|
|
3101
|
-
return line.slice(0, at) + tone + glyph + RESET + line.slice(at + 1);
|
|
3102
|
-
}
|
|
3103
|
-
function receipt(text, options = {}) {
|
|
3104
|
-
const color = options.color ?? false;
|
|
3105
|
-
const width = Math.max(40, options.width ?? DEFAULT_WIDTH2);
|
|
3106
|
-
const output = options.output ?? process.stdout;
|
|
3107
|
-
const edges = 2 * 2 + 2;
|
|
3108
|
-
const available = width - edges;
|
|
3109
|
-
const lines = [];
|
|
3110
|
-
for (const paragraph of text.split("\n")) {
|
|
3111
|
-
if (!paragraph.trim()) lines.push(null);
|
|
3112
|
-
else if (paragraph.length <= available) lines.push(paragraph);
|
|
3113
|
-
else for (const line of wrapWords(paragraph, available)) lines.push(line);
|
|
3114
|
-
}
|
|
3115
|
-
const inner = Math.min(available, Math.max(20, ...lines.map((line) => line?.length ?? 0)));
|
|
3116
|
-
const rule = "\u2500".repeat(inner + 2 * 2);
|
|
3117
|
-
const frame = (text2) => color ? ACCENT + text2 + RESET : text2;
|
|
3118
|
-
const blank = `${frame("\u2502")}${" ".repeat(inner + 2 * 2)}${frame("\u2502")}`;
|
|
3119
|
-
output.write(frame(`\u256D${rule}\u256E`) + "\n");
|
|
3120
|
-
for (const line of lines) {
|
|
3121
|
-
output.write(line === null ? blank + "\n" : `${frame("\u2502")} ${paintGlyph(line.padEnd(inner), color)} ${frame("\u2502")}
|
|
3122
|
-
`);
|
|
3123
|
-
}
|
|
3124
|
-
output.write(frame(`\u2570${rule}\u256F`) + "\n");
|
|
3125
|
-
}
|
|
3126
|
-
function signOff(color, output = process.stdout) {
|
|
3127
|
-
output.write(`\u2570 ${accent("mmi-hub \xB7 Mutatis Mutandis", color)}
|
|
3128
|
-
`);
|
|
3129
|
-
}
|
|
3130
|
-
function receiptText(summary, autoupdate, options = {}) {
|
|
3131
|
-
const dry = options.dryRun ?? false;
|
|
3132
|
-
const counts = reportCounts(summary);
|
|
3133
|
-
const health = "Check health any time: mmi-hub status";
|
|
3134
|
-
if (!summary.target) {
|
|
3135
|
-
return `${summary.exit >= 1 ? "\u2716" : "\u25CF"} mmi-hub is not ready \u2014 no green release yet (hourly retry)
|
|
3136
|
-
${health}`;
|
|
3137
|
-
}
|
|
3138
|
-
const ready = counts.failed === 0;
|
|
3139
|
-
const glyph = counts.failed > 0 ? "\u2716" : counts.retry > 0 ? "\u25CF" : dry ? "\u25C7" : "\u2714";
|
|
3140
|
-
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`;
|
|
3141
|
-
const tail2 = autoupdate ? ` \xB7 ${autoupdate.ok ? "auto-update on" : `auto-update NOT enabled \u2014 ${autoupdate.detail}`}` : ` \xB7 ${summary.target}${dry ? " (planned)" : " (verified)"}`;
|
|
3142
|
-
return `${glyph} mmi-hub is ${ready ? "ready" : "not ready"} \u2014 ${state}${tail2}
|
|
3143
|
-
${health}`;
|
|
3144
|
-
}
|
|
3145
|
-
|
|
3146
|
-
// src/spinner.ts
|
|
3147
|
-
var import_node_worker_threads = require("node:worker_threads");
|
|
3148
|
-
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3149
|
-
var WORKER_SRC = `
|
|
3150
|
-
const { parentPort, workerData } = require('node:worker_threads');
|
|
3151
|
-
const frames = ${JSON.stringify(FRAMES)};
|
|
3152
|
-
const control = new Int32Array(workerData.sab);
|
|
3153
|
-
let frame = 0, label = '', timer = null;
|
|
3154
|
-
function tick() {
|
|
3155
|
-
if (Atomics.load(control, 0) === 1) return; // paused by the main thread
|
|
3156
|
-
if (!label) return;
|
|
3157
|
-
process.stderr.write('\\r' + frames[frame++ % frames.length] + ' ' + label + '\\x1b[K');
|
|
3158
|
-
}
|
|
3159
|
-
parentPort.on('message', (message) => {
|
|
3160
|
-
if (message && message.type === 'label') { label = message.label; if (!timer) timer = setInterval(tick, 80); }
|
|
3161
|
-
else if (message === 'stop') { if (timer) clearInterval(timer); process.stderr.write('\\r\\x1b[K'); process.exit(0); }
|
|
3162
|
-
});
|
|
3163
|
-
`;
|
|
3164
|
-
var NOOP = { label() {
|
|
3165
|
-
}, persist() {
|
|
3166
|
-
}, stop() {
|
|
3167
|
-
}, active: false };
|
|
3168
|
-
function startSpinner(enabled) {
|
|
3169
|
-
if (!enabled) return NOOP;
|
|
3170
|
-
let worker;
|
|
3171
|
-
let control;
|
|
3172
|
-
try {
|
|
3173
|
-
const sab = new SharedArrayBuffer(4);
|
|
3174
|
-
control = new Int32Array(sab);
|
|
3175
|
-
worker = new import_node_worker_threads.Worker(WORKER_SRC, { eval: true, workerData: { sab } });
|
|
3176
|
-
worker.unref();
|
|
3177
|
-
worker.on("error", () => {
|
|
3178
|
-
});
|
|
3179
|
-
} catch {
|
|
3180
|
-
return NOOP;
|
|
3181
|
-
}
|
|
3182
|
-
let stopped = false;
|
|
3183
|
-
return {
|
|
3184
|
-
active: true,
|
|
3185
|
-
label: (text) => {
|
|
3186
|
-
if (!stopped) worker.postMessage({ type: "label", label: text });
|
|
3187
|
-
},
|
|
3188
|
-
persist: (line) => {
|
|
3189
|
-
Atomics.store(control, 0, 1);
|
|
3190
|
-
process.stderr.write("\r\x1B[K");
|
|
3191
|
-
process.stdout.write(line + "\n");
|
|
3192
|
-
Atomics.store(control, 0, 0);
|
|
3193
|
-
},
|
|
3194
|
-
stop: () => {
|
|
3195
|
-
if (stopped) return;
|
|
3196
|
-
stopped = true;
|
|
3197
|
-
worker.postMessage("stop");
|
|
3198
|
-
}
|
|
3199
|
-
};
|
|
3200
|
-
}
|
|
3201
|
-
|
|
3202
3417
|
// src/stamp.ts
|
|
3203
3418
|
var import_node_fs17 = require("node:fs");
|
|
3204
3419
|
var ALWAYS_ARMED = ["cli", "updater"];
|
|
@@ -3296,17 +3511,33 @@ function printReconcile(summary, json, dryRun = false) {
|
|
|
3296
3511
|
function faceEnabled(args, json) {
|
|
3297
3512
|
return !json && !args.includes("--verbose") && !process.env.NO_COLOR && process.env.TERM !== "dumb" && Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
|
|
3298
3513
|
}
|
|
3299
|
-
function openFace(args, json) {
|
|
3300
|
-
|
|
3301
|
-
|
|
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");
|
|
3302
3518
|
return face;
|
|
3303
3519
|
}
|
|
3304
|
-
function finishFace(summary, autoupdate, dryRun) {
|
|
3520
|
+
function finishFace(face, summary, autoupdate, dryRun) {
|
|
3305
3521
|
const options = humanOptions(dryRun);
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
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());
|
|
3310
3541
|
}
|
|
3311
3542
|
function handOff(reexec, args) {
|
|
3312
3543
|
const child = (0, import_node_child_process9.spawnSync)(process.execPath, [reexec.dist, ...args], {
|
|
@@ -3337,7 +3568,13 @@ function heldOutput(write) {
|
|
|
3337
3568
|
}
|
|
3338
3569
|
};
|
|
3339
3570
|
}
|
|
3340
|
-
function
|
|
3571
|
+
function shippedTargetFrom(args) {
|
|
3572
|
+
const index = args.indexOf("--shipped");
|
|
3573
|
+
if (index < 0) return void 0;
|
|
3574
|
+
const value = (args[index + 1] ?? "").trim();
|
|
3575
|
+
return value && !value.startsWith("--") ? value : void 0;
|
|
3576
|
+
}
|
|
3577
|
+
async function runUpdate(args, json, face = null, shippedTarget) {
|
|
3341
3578
|
const dryRun = args.includes("--dry-run");
|
|
3342
3579
|
const verbose = args.includes("--verbose");
|
|
3343
3580
|
const narrate = (line) => {
|
|
@@ -3346,19 +3583,35 @@ function runUpdate(args, json, face = false) {
|
|
|
3346
3583
|
};
|
|
3347
3584
|
const progressive = !json && !verbose && Boolean(process.stderr.isTTY);
|
|
3348
3585
|
if (!progressive) {
|
|
3349
|
-
return
|
|
3586
|
+
return reconcile({ dryRun, narrate: verbose ? narrate : void 0, shippedTarget }).then((summary2) => ({ summary: summary2, rendered: false }));
|
|
3350
3587
|
}
|
|
3351
3588
|
const reportOptions = humanOptions(dryRun);
|
|
3352
|
-
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
|
+
};
|
|
3353
3605
|
let headerEmitted = false;
|
|
3354
3606
|
let summary;
|
|
3355
|
-
const { emit, stream } = heldOutput(
|
|
3607
|
+
const { emit, stream } = heldOutput(persist);
|
|
3356
3608
|
let arms = 0;
|
|
3357
3609
|
let mark = Date.now();
|
|
3358
3610
|
try {
|
|
3359
|
-
summary = reconcile({
|
|
3611
|
+
summary = await reconcile({
|
|
3360
3612
|
dryRun,
|
|
3361
|
-
|
|
3613
|
+
shippedTarget,
|
|
3614
|
+
onPhase: (line) => phase(line),
|
|
3362
3615
|
onTargetResolved: face ? void 0 : (target) => {
|
|
3363
3616
|
emit(updateHeader(target, dryRun, reportOptions));
|
|
3364
3617
|
emit("");
|
|
@@ -3368,7 +3621,7 @@ function runUpdate(args, json, face = false) {
|
|
|
3368
3621
|
const seconds = (Date.now() - mark) / 1e3;
|
|
3369
3622
|
mark = Date.now();
|
|
3370
3623
|
if (arms++) stream();
|
|
3371
|
-
emit(face ? checklistLine(arm, seconds, reportOptions) : progressiveRow(arm, reportOptions));
|
|
3624
|
+
emit(face ? checklistLine(arm, seconds, face, reportOptions) : progressiveRow(arm, reportOptions));
|
|
3372
3625
|
}
|
|
3373
3626
|
});
|
|
3374
3627
|
} finally {
|
|
@@ -3381,7 +3634,7 @@ function runUpdate(args, json, face = false) {
|
|
|
3381
3634
|
else process.stdout.write(formatReconcileReport(summary, reportOptions));
|
|
3382
3635
|
return { summary, rendered: true };
|
|
3383
3636
|
}
|
|
3384
|
-
function main(args = process.argv.slice(2)) {
|
|
3637
|
+
async function main(args = process.argv.slice(2)) {
|
|
3385
3638
|
if (args.includes("--version")) {
|
|
3386
3639
|
process.stdout.write(ownVersion() + "\n");
|
|
3387
3640
|
return 0;
|
|
@@ -3398,19 +3651,21 @@ function main(args = process.argv.slice(2)) {
|
|
|
3398
3651
|
return status.failures.length ? 1 : 0;
|
|
3399
3652
|
}
|
|
3400
3653
|
if (command === "update") {
|
|
3401
|
-
const
|
|
3402
|
-
const
|
|
3654
|
+
const dryRun = args.includes("--dry-run");
|
|
3655
|
+
const face = openFace(args, json, dryRun);
|
|
3656
|
+
const { summary, rendered } = await runUpdate(args, json, face);
|
|
3403
3657
|
if (summary.reexec) return handOff(summary.reexec, args);
|
|
3404
|
-
if (face) finishFace(summary, null,
|
|
3405
|
-
else if (!rendered) printReconcile(summary, json,
|
|
3658
|
+
if (face) finishFace(face, summary, null, dryRun);
|
|
3659
|
+
else if (!rendered) printReconcile(summary, json, dryRun);
|
|
3406
3660
|
return summary.exit;
|
|
3407
3661
|
}
|
|
3408
3662
|
if (command === "install") {
|
|
3409
|
-
const face = openFace(args, json);
|
|
3410
3663
|
const dryRun = args.includes("--dry-run");
|
|
3411
|
-
const
|
|
3664
|
+
const face = openFace(args, json, dryRun);
|
|
3665
|
+
const shippedTarget = shippedTargetFrom(args);
|
|
3666
|
+
const { summary, rendered } = await runUpdate(args, json, face, shippedTarget);
|
|
3412
3667
|
if (summary.reexec) {
|
|
3413
|
-
const handedOff = handOff(summary.reexec, ["update", ...args.filter((arg) => arg !== "install")]);
|
|
3668
|
+
const handedOff = handOff(summary.reexec, ["update", ...args.filter((arg) => arg !== "install" && arg !== "--shipped" && arg !== shippedTarget)]);
|
|
3414
3669
|
const scheduled = installTask();
|
|
3415
3670
|
if (!json) {
|
|
3416
3671
|
process.stdout.write(scheduled.ok ? ` Auto-update on \u2014 hourly task "MMI Fleet Updater".
|
|
@@ -3425,7 +3680,7 @@ function main(args = process.argv.slice(2)) {
|
|
|
3425
3680
|
if (json) {
|
|
3426
3681
|
process.stdout.write(JSON.stringify({ convergence: summary, autoupdate: schedule }, null, 2) + "\n");
|
|
3427
3682
|
} else if (face) {
|
|
3428
|
-
finishFace(summary, schedule, dryRun);
|
|
3683
|
+
finishFace(face, summary, schedule, dryRun);
|
|
3429
3684
|
} else {
|
|
3430
3685
|
if (!rendered) printReconcile(summary, false, dryRun);
|
|
3431
3686
|
process.stdout.write(schedule.ok ? ` Auto-update on \u2014 hourly task "MMI Fleet Updater".
|
|
@@ -3450,17 +3705,24 @@ function main(args = process.argv.slice(2)) {
|
|
|
3450
3705
|
return 1;
|
|
3451
3706
|
}
|
|
3452
3707
|
if (typeof require !== "undefined" && require.main === module) {
|
|
3453
|
-
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
|
+
});
|
|
3454
3713
|
}
|
|
3455
3714
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3456
3715
|
0 && (module.exports = {
|
|
3457
3716
|
ACCENT,
|
|
3717
|
+
FACE_PRODUCT,
|
|
3458
3718
|
MM_GREEN,
|
|
3459
3719
|
RESET,
|
|
3460
3720
|
accent,
|
|
3461
3721
|
acquireSharedDocLock,
|
|
3722
|
+
attentionRows,
|
|
3462
3723
|
brandLine,
|
|
3463
3724
|
buildWatcherCommand,
|
|
3725
|
+
checkAtomicity,
|
|
3464
3726
|
checklistLine,
|
|
3465
3727
|
claudeArm,
|
|
3466
3728
|
claudeHostEvidence,
|
|
@@ -3500,7 +3762,6 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3500
3762
|
progressiveRow,
|
|
3501
3763
|
readLaunchTarget,
|
|
3502
3764
|
readPending,
|
|
3503
|
-
receipt,
|
|
3504
3765
|
receiptText,
|
|
3505
3766
|
reconcile,
|
|
3506
3767
|
reconcileCursorUserHooks,
|
|
@@ -3513,16 +3774,15 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3513
3774
|
schedulerStatus,
|
|
3514
3775
|
sharedDocLockPath,
|
|
3515
3776
|
sharedDocLockTimeoutReceipt,
|
|
3777
|
+
shippedTargetFrom,
|
|
3516
3778
|
shouldHandOff,
|
|
3517
|
-
signOff,
|
|
3518
|
-
startSpinner,
|
|
3519
3779
|
surfaceArmPlan,
|
|
3520
3780
|
systemdServiceUnit,
|
|
3521
3781
|
systemdTimerUnit,
|
|
3522
3782
|
taskXml,
|
|
3523
3783
|
updateHeader,
|
|
3784
|
+
updaterFace,
|
|
3524
3785
|
watcherLockPath,
|
|
3525
|
-
welcome,
|
|
3526
3786
|
withSharedDocLock,
|
|
3527
3787
|
wrapWords,
|
|
3528
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",
|