@mutmutco/hub 4.4.0 → 4.4.1

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.
Files changed (2) hide show
  1. package/dist/index.cjs +180 -36
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -21,8 +21,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  // src/index.ts
22
22
  var index_exports = {};
23
23
  __export(index_exports, {
24
+ ACCENT: () => ACCENT,
25
+ MM_GREEN: () => MM_GREEN,
26
+ RESET: () => RESET,
27
+ accent: () => accent,
24
28
  acquireSharedDocLock: () => acquireSharedDocLock,
29
+ brandLine: () => brandLine,
25
30
  buildWatcherCommand: () => buildWatcherCommand,
31
+ checklistLine: () => checklistLine,
26
32
  claudeArm: () => claudeArm,
27
33
  claudeHostEvidence: () => claudeHostEvidence,
28
34
  clearLaunchTarget: () => clearLaunchTarget,
@@ -61,9 +67,12 @@ __export(index_exports, {
61
67
  progressiveRow: () => progressiveRow,
62
68
  readLaunchTarget: () => readLaunchTarget,
63
69
  readPending: () => readPending,
70
+ receipt: () => receipt,
71
+ receiptText: () => receiptText,
64
72
  reconcile: () => reconcile,
65
73
  reconcileCursorUserHooks: () => reconcileCursorUserHooks,
66
74
  releaseSharedDocLock: () => releaseSharedDocLock,
75
+ reportCounts: () => reportCounts,
67
76
  reportFooterLines: () => reportFooterLines,
68
77
  retiredSurfaces: () => retiredSurfaces,
69
78
  retryHint: () => retryHint,
@@ -72,6 +81,7 @@ __export(index_exports, {
72
81
  sharedDocLockPath: () => sharedDocLockPath,
73
82
  sharedDocLockTimeoutReceipt: () => sharedDocLockTimeoutReceipt,
74
83
  shouldHandOff: () => shouldHandOff,
84
+ signOff: () => signOff,
75
85
  startSpinner: () => startSpinner,
76
86
  surfaceArmPlan: () => surfaceArmPlan,
77
87
  systemdServiceUnit: () => systemdServiceUnit,
@@ -79,6 +89,7 @@ __export(index_exports, {
79
89
  taskXml: () => taskXml,
80
90
  updateHeader: () => updateHeader,
81
91
  watcherLockPath: () => watcherLockPath,
92
+ welcome: () => welcome,
82
93
  withSharedDocLock: () => withSharedDocLock,
83
94
  wrapWords: () => wrapWords,
84
95
  writeLaunchTarget: () => writeLaunchTarget,
@@ -2818,6 +2829,13 @@ var RED = "\x1B[31m";
2818
2829
  var RESET = "\x1B[0m";
2819
2830
  var ACCENT = "\x1B[38;2;125;211;252m";
2820
2831
  var MUTED = "\x1B[38;2;150;150;150m";
2832
+ var MM_GREEN = "\x1B[38;2;31;209;138m";
2833
+ function accent(text, color) {
2834
+ return color ? ACCENT + text + RESET : text;
2835
+ }
2836
+ function brandLine(rest, color = false) {
2837
+ return `${accent("mmi-hub", color)} \xB7 Mutatis Mutandis \u2014 ${rest}`;
2838
+ }
2821
2839
  var RETRY_HINTS = [
2822
2840
  [/applies when the host exits|applies on a quiet tick/, "installs when the app closes"],
2823
2841
  [/in flight|changed while/, "rerun update \u2014 host race"],
@@ -2887,6 +2905,36 @@ function glyphFor(kind) {
2887
2905
  function colorFor(kind) {
2888
2906
  return kind === "failed" ? RED : kind === "retry" ? YELLOW : GREEN;
2889
2907
  }
2908
+ var ARM_TITLE = {
2909
+ updater: "Updating mmi-hub",
2910
+ cli: "Installing mmi-cli",
2911
+ claude: "Arming Claude Code",
2912
+ codex: "Arming Codex",
2913
+ cursor: "Arming Cursor",
2914
+ hermes: "Arming Hermes",
2915
+ jervcode: "Arming JervCode"
2916
+ };
2917
+ var CHECKLIST_TAIL = {
2918
+ updated: "",
2919
+ current: " current",
2920
+ keep: " keep",
2921
+ retry: " will retry",
2922
+ failed: " failed",
2923
+ pending: " pending"
2924
+ };
2925
+ function checklistLine(arm, seconds, options = {}) {
2926
+ const tty = options.tty ?? false;
2927
+ const color = tty && (options.color ?? true);
2928
+ const dryRun = options.dryRun ?? false;
2929
+ const kind = classify(arm).kind;
2930
+ const tone = kind === "failed" ? RED : kind === "retry" ? YELLOW : MM_GREEN;
2931
+ const paint = (text) => color ? tone + text + RESET : text;
2932
+ const glyph = kind === "failed" ? "\u2716" : kind === "retry" ? "\u25CF" : "\u2714";
2933
+ const title = ARM_TITLE[arm.surface] ?? `Arming ${arm.surface}`;
2934
+ const tail2 = dryRun && kind === "updated" ? " would update" : CHECKLIST_TAIL[kind];
2935
+ const time = seconds !== null && kind !== "retry" && kind !== "failed" ? ` ${Math.max(1, Math.round(seconds))}s` : "";
2936
+ return ` ${paint(glyph)} ${title}${tail2}${time}`;
2937
+ }
2890
2938
  function wrapWords(text, available) {
2891
2939
  if (available < 1) return [text];
2892
2940
  const lines = [];
@@ -2934,8 +2982,10 @@ function headline(detail, width) {
2934
2982
  return wrapWords(first || "blocked", Math.max(10, width - 4))[0];
2935
2983
  }
2936
2984
  function updateHeader(target, dryRun = false, options) {
2937
- const accent = (text) => options?.color ? ACCENT + text + RESET : text;
2938
- return dryRun ? `${accent("mmi-hub")} \xB7 Mutatis Mutandis \u2014 update plan to ${target} (dry run)` : `${accent("mmi-hub")} \xB7 Mutatis Mutandis \u2014 updating to ${target} (verified)`;
2985
+ return brandLine(
2986
+ dryRun ? `update plan to ${target} (dry run)` : `updating to ${target} (verified)`,
2987
+ Boolean(options?.color)
2988
+ );
2939
2989
  }
2940
2990
  function renderRowLines(row, ctx) {
2941
2991
  const paint = (kind, text) => ctx.color ? colorFor(kind) + text + RESET : text;
@@ -2955,6 +3005,16 @@ function progressiveRow(arm, options = {}) {
2955
3005
  const dryRun = options.dryRun ?? false;
2956
3006
  return renderRowLines(classify(arm), { tty, color, dryRun, width, statusWidth: progressiveStatusWidth(dryRun) }).join("\n");
2957
3007
  }
3008
+ function reportCounts(summary) {
3009
+ const rows = collectRows(summary);
3010
+ return {
3011
+ updated: rows.filter((row) => row.kind === "updated").length,
3012
+ current: rows.filter((row) => row.kind === "current" || row.kind === "keep").length,
3013
+ retry: rows.filter((row) => row.kind === "retry").length,
3014
+ failed: rows.filter((row) => row.kind === "failed").length,
3015
+ total: rows.length
3016
+ };
3017
+ }
2958
3018
  function reportFooterLines(summary, options = {}) {
2959
3019
  const width = Math.max(40, options.width ?? DEFAULT_WIDTH);
2960
3020
  const tty = options.tty ?? false;
@@ -2963,12 +3023,7 @@ function reportFooterLines(summary, options = {}) {
2963
3023
  const journalPath = options.journalPath ?? "~/.mmi/updater/journal.jsonl";
2964
3024
  const paint = (kind, text) => color ? colorFor(kind) + text + RESET : text;
2965
3025
  const rows = collectRows(summary);
2966
- const counts = {
2967
- updated: rows.filter((row) => row.kind === "updated").length,
2968
- retry: rows.filter((row) => row.kind === "retry").length,
2969
- failed: rows.filter((row) => row.kind === "failed").length,
2970
- total: rows.length
2971
- };
3026
+ const counts = reportCounts(summary);
2972
3027
  const footer = dryRun ? `${counts.updated}/${counts.total} would update` : `${counts.updated}/${counts.total} updated` + (counts.retry ? ` \xB7 ${counts.retry} will retry` : "") + (counts.failed ? ` \xB7 ${counts.failed} failed` : "");
2973
3028
  const out = ["", " " + footer];
2974
3029
  if (tty && !dryRun && counts.failed === 0) {
@@ -3002,13 +3057,7 @@ function formatReconcileReport(summary, options = {}) {
3002
3057
  const out = [];
3003
3058
  const paint = (kind, text) => color ? colorFor(kind) + text + RESET : text;
3004
3059
  const rows = collectRows(summary);
3005
- const counts = {
3006
- updated: rows.filter((row) => row.kind === "updated").length,
3007
- current: rows.filter((row) => row.kind === "current" || row.kind === "keep").length,
3008
- retry: rows.filter((row) => row.kind === "retry").length,
3009
- failed: rows.filter((row) => row.kind === "failed").length,
3010
- total: rows.length
3011
- };
3060
+ const counts = reportCounts(summary);
3012
3061
  if (!summary.target) {
3013
3062
  if ((summary.exit >= 1 || summary.deferred) && summary.detail) {
3014
3063
  out.push("MMI Hub \u2014 cannot update now");
@@ -3032,6 +3081,68 @@ function formatReconcileReport(summary, options = {}) {
3032
3081
  return out.join("\n") + "\n";
3033
3082
  }
3034
3083
 
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
+
3035
3146
  // src/spinner.ts
3036
3147
  var import_node_worker_threads = require("node:worker_threads");
3037
3148
  var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
@@ -3166,18 +3277,36 @@ var HELP = `mmi-hub ${ownVersion()} \u2014 install and maintain MMI tooling
3166
3277
  State remains under $MMI_UPDATER_HOME (default ~/.mmi/updater), and the Windows task name remains
3167
3278
  "MMI Fleet Updater", so existing installations continue in place.
3168
3279
  Status performs no npm install, host repair, scheduler write, state mkdir, or repository cleanup.`;
3169
- function printReconcile(summary, json, dryRun = false) {
3170
- if (json) {
3171
- process.stdout.write(JSON.stringify(summary, null, 2) + "\n");
3172
- return;
3173
- }
3174
- process.stdout.write(formatReconcileReport(summary, {
3280
+ function humanOptions(dryRun) {
3281
+ return {
3175
3282
  tty: Boolean(process.stdout.isTTY),
3176
3283
  color: !process.env.NO_COLOR,
3177
3284
  width: process.stdout.columns ?? 100,
3178
3285
  dryRun,
3179
3286
  journalPath: statePaths(process.env).journalPath
3180
- }));
3287
+ };
3288
+ }
3289
+ function printReconcile(summary, json, dryRun = false) {
3290
+ if (json) {
3291
+ process.stdout.write(JSON.stringify(summary, null, 2) + "\n");
3292
+ return;
3293
+ }
3294
+ process.stdout.write(formatReconcileReport(summary, humanOptions(dryRun)));
3295
+ }
3296
+ function faceEnabled(args, json) {
3297
+ return !json && !args.includes("--verbose") && !process.env.NO_COLOR && process.env.TERM !== "dumb" && Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
3298
+ }
3299
+ function openFace(args, json) {
3300
+ const face = faceEnabled(args, json);
3301
+ if (face && !process.env.MMI_UPDATER_REEXEC) welcome(true);
3302
+ return face;
3303
+ }
3304
+ function finishFace(summary, autoupdate, dryRun) {
3305
+ const options = humanOptions(dryRun);
3306
+ if (!summary.target) process.stdout.write(formatReconcileReport(summary, options));
3307
+ else if (summary.exit >= 1) process.stdout.write(reportFooterLines(summary, options).join("\n") + "\n");
3308
+ receipt(receiptText(summary, autoupdate, { dryRun }), { color: options.color, width: options.width });
3309
+ signOff(true);
3181
3310
  }
3182
3311
  function handOff(reexec, args) {
3183
3312
  const child = (0, import_node_child_process9.spawnSync)(process.execPath, [reexec.dist, ...args], {
@@ -3208,7 +3337,7 @@ function heldOutput(write) {
3208
3337
  }
3209
3338
  };
3210
3339
  }
3211
- function runUpdate(args, json) {
3340
+ function runUpdate(args, json, face = false) {
3212
3341
  const dryRun = args.includes("--dry-run");
3213
3342
  const verbose = args.includes("--verbose");
3214
3343
  const narrate = (line) => {
@@ -3219,30 +3348,27 @@ function runUpdate(args, json) {
3219
3348
  if (!progressive) {
3220
3349
  return { summary: reconcile({ dryRun, narrate: verbose ? narrate : void 0 }), rendered: false };
3221
3350
  }
3222
- const reportOptions = {
3223
- tty: Boolean(process.stdout.isTTY),
3224
- color: !process.env.NO_COLOR,
3225
- width: process.stdout.columns ?? 100,
3226
- dryRun,
3227
- journalPath: statePaths(process.env).journalPath
3228
- };
3351
+ const reportOptions = humanOptions(dryRun);
3229
3352
  const spinner = startSpinner(true);
3230
3353
  let headerEmitted = false;
3231
3354
  let summary;
3232
3355
  const { emit, stream } = heldOutput((line) => spinner.persist(line));
3233
3356
  let arms = 0;
3357
+ let mark = Date.now();
3234
3358
  try {
3235
3359
  summary = reconcile({
3236
3360
  dryRun,
3237
3361
  onPhase: (line) => spinner.label(line),
3238
- onTargetResolved: (target) => {
3362
+ onTargetResolved: face ? void 0 : (target) => {
3239
3363
  emit(updateHeader(target, dryRun, reportOptions));
3240
3364
  emit("");
3241
3365
  headerEmitted = true;
3242
3366
  },
3243
3367
  onArm: (arm) => {
3368
+ const seconds = (Date.now() - mark) / 1e3;
3369
+ mark = Date.now();
3244
3370
  if (arms++) stream();
3245
- emit(progressiveRow(arm, reportOptions));
3371
+ emit(face ? checklistLine(arm, seconds, reportOptions) : progressiveRow(arm, reportOptions));
3246
3372
  }
3247
3373
  });
3248
3374
  } finally {
@@ -3250,6 +3376,7 @@ function runUpdate(args, json) {
3250
3376
  }
3251
3377
  if (summary.reexec) return { summary, rendered: true };
3252
3378
  stream();
3379
+ if (face) return { summary, rendered: true };
3253
3380
  if (headerEmitted) process.stdout.write(reportFooterLines(summary, reportOptions).join("\n") + "\n");
3254
3381
  else process.stdout.write(formatReconcileReport(summary, reportOptions));
3255
3382
  return { summary, rendered: true };
@@ -3271,13 +3398,17 @@ function main(args = process.argv.slice(2)) {
3271
3398
  return status.failures.length ? 1 : 0;
3272
3399
  }
3273
3400
  if (command === "update") {
3274
- const { summary, rendered } = runUpdate(args, json);
3401
+ const face = openFace(args, json);
3402
+ const { summary, rendered } = runUpdate(args, json, face);
3275
3403
  if (summary.reexec) return handOff(summary.reexec, args);
3276
- if (!rendered) printReconcile(summary, json, args.includes("--dry-run"));
3404
+ if (face) finishFace(summary, null, args.includes("--dry-run"));
3405
+ else if (!rendered) printReconcile(summary, json, args.includes("--dry-run"));
3277
3406
  return summary.exit;
3278
3407
  }
3279
3408
  if (command === "install") {
3280
- const { summary, rendered } = runUpdate(args, json);
3409
+ const face = openFace(args, json);
3410
+ const dryRun = args.includes("--dry-run");
3411
+ const { summary, rendered } = runUpdate(args, json, face);
3281
3412
  if (summary.reexec) {
3282
3413
  const handedOff = handOff(summary.reexec, ["update", ...args.filter((arg) => arg !== "install")]);
3283
3414
  const scheduled = installTask();
@@ -3293,8 +3424,10 @@ function main(args = process.argv.slice(2)) {
3293
3424
  const schedule = installTask();
3294
3425
  if (json) {
3295
3426
  process.stdout.write(JSON.stringify({ convergence: summary, autoupdate: schedule }, null, 2) + "\n");
3427
+ } else if (face) {
3428
+ finishFace(summary, schedule, dryRun);
3296
3429
  } else {
3297
- if (!rendered) printReconcile(summary, false, args.includes("--dry-run"));
3430
+ if (!rendered) printReconcile(summary, false, dryRun);
3298
3431
  process.stdout.write(schedule.ok ? ` Auto-update on \u2014 hourly task "MMI Fleet Updater".
3299
3432
  ` : ` Auto-update NOT enabled \u2014 ${schedule.detail}
3300
3433
  `);
@@ -3321,8 +3454,14 @@ if (typeof require !== "undefined" && require.main === module) {
3321
3454
  }
3322
3455
  // Annotate the CommonJS export names for ESM import in node:
3323
3456
  0 && (module.exports = {
3457
+ ACCENT,
3458
+ MM_GREEN,
3459
+ RESET,
3460
+ accent,
3324
3461
  acquireSharedDocLock,
3462
+ brandLine,
3325
3463
  buildWatcherCommand,
3464
+ checklistLine,
3326
3465
  claudeArm,
3327
3466
  claudeHostEvidence,
3328
3467
  clearLaunchTarget,
@@ -3361,9 +3500,12 @@ if (typeof require !== "undefined" && require.main === module) {
3361
3500
  progressiveRow,
3362
3501
  readLaunchTarget,
3363
3502
  readPending,
3503
+ receipt,
3504
+ receiptText,
3364
3505
  reconcile,
3365
3506
  reconcileCursorUserHooks,
3366
3507
  releaseSharedDocLock,
3508
+ reportCounts,
3367
3509
  reportFooterLines,
3368
3510
  retiredSurfaces,
3369
3511
  retryHint,
@@ -3372,6 +3514,7 @@ if (typeof require !== "undefined" && require.main === module) {
3372
3514
  sharedDocLockPath,
3373
3515
  sharedDocLockTimeoutReceipt,
3374
3516
  shouldHandOff,
3517
+ signOff,
3375
3518
  startSpinner,
3376
3519
  surfaceArmPlan,
3377
3520
  systemdServiceUnit,
@@ -3379,6 +3522,7 @@ if (typeof require !== "undefined" && require.main === module) {
3379
3522
  taskXml,
3380
3523
  updateHeader,
3381
3524
  watcherLockPath,
3525
+ welcome,
3382
3526
  withSharedDocLock,
3383
3527
  wrapWords,
3384
3528
  writeLaunchTarget,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/hub",
3
- "version": "4.4.0",
3
+ "version": "4.4.1",
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",