@mutmutco/hub 4.0.3 → 4.0.5
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 +310 -42
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -27,6 +27,8 @@ __export(index_exports, {
|
|
|
27
27
|
hermesArm: () => hermesArm,
|
|
28
28
|
hermesHome: () => hermesHome,
|
|
29
29
|
hubStatus: () => hubStatus,
|
|
30
|
+
launchdPlist: () => launchdPlist,
|
|
31
|
+
launcherSh: () => launcherSh,
|
|
30
32
|
launcherVbs: () => launcherVbs,
|
|
31
33
|
localStartBoundary: () => localStartBoundary,
|
|
32
34
|
main: () => main,
|
|
@@ -37,6 +39,8 @@ __export(index_exports, {
|
|
|
37
39
|
retryHint: () => retryHint,
|
|
38
40
|
schedulerStatus: () => schedulerStatus,
|
|
39
41
|
startSpinner: () => startSpinner,
|
|
42
|
+
systemdServiceUnit: () => systemdServiceUnit,
|
|
43
|
+
systemdTimerUnit: () => systemdTimerUnit,
|
|
40
44
|
taskXml: () => taskXml,
|
|
41
45
|
updateHeader: () => updateHeader,
|
|
42
46
|
wrapWords: () => wrapWords
|
|
@@ -150,6 +154,22 @@ function journalHighWater(path, surface) {
|
|
|
150
154
|
}
|
|
151
155
|
return high;
|
|
152
156
|
}
|
|
157
|
+
function journalSurfaceRepeatedFail(path, surface, currentRun) {
|
|
158
|
+
let lastVerdict = null;
|
|
159
|
+
try {
|
|
160
|
+
for (const line of (0, import_node_fs.readFileSync)(path, "utf8").split("\n")) {
|
|
161
|
+
if (!line.trim()) continue;
|
|
162
|
+
try {
|
|
163
|
+
const event = JSON.parse(line);
|
|
164
|
+
if (event.dryRun || event.kind !== "arm" || event.surface !== surface || event.run === currentRun) continue;
|
|
165
|
+
lastVerdict = event.verdict;
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
return lastVerdict === "fail";
|
|
172
|
+
}
|
|
153
173
|
function parseSemver(version) {
|
|
154
174
|
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(version.trim());
|
|
155
175
|
if (!match) return null;
|
|
@@ -200,6 +220,11 @@ function runNpm(args, env = process.env, options = {}) {
|
|
|
200
220
|
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 });
|
|
201
221
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
202
222
|
}
|
|
223
|
+
function globalRoot(env = process.env) {
|
|
224
|
+
const root = runNpm(["root", "-g"], env);
|
|
225
|
+
if (root.status !== 0 || !root.stdout.trim()) return null;
|
|
226
|
+
return root.stdout.trim();
|
|
227
|
+
}
|
|
203
228
|
function npmView(spec, field, env = process.env) {
|
|
204
229
|
const args = ["view", spec, "--json"];
|
|
205
230
|
if (field) args.splice(2, 0, field);
|
|
@@ -314,11 +339,12 @@ function checkAtomicity(cachePath, tagCommit, bom, candidate, npm) {
|
|
|
314
339
|
rows.push({ id: row.id, name, ok: false, detail: `${name}@${candidate} not on npm` });
|
|
315
340
|
continue;
|
|
316
341
|
}
|
|
317
|
-
|
|
342
|
+
const legacySsri = /^sha\d+-/.test(row.identity.value ?? "");
|
|
343
|
+
if (legacySsri && integrity !== row.identity.value) {
|
|
318
344
|
rows.push({ id: row.id, name, ok: false, detail: `${name}@${candidate} dist.integrity differs from BOM identity` });
|
|
319
345
|
continue;
|
|
320
346
|
}
|
|
321
|
-
rows.push({ id: row.id, name, ok: true, detail: `${name}@${candidate} published, integrity matches` });
|
|
347
|
+
rows.push({ id: row.id, name, ok: true, detail: legacySsri ? `${name}@${candidate} published, integrity matches` : `${name}@${candidate} published` });
|
|
322
348
|
}
|
|
323
349
|
return { ok: rows.length > 0 && rows.every((row) => row.ok), rows };
|
|
324
350
|
}
|
|
@@ -362,9 +388,8 @@ var import_node_fs5 = require("node:fs");
|
|
|
362
388
|
var import_node_child_process3 = require("node:child_process");
|
|
363
389
|
var import_node_path4 = require("node:path");
|
|
364
390
|
function globalCliDist(env) {
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
return (0, import_node_path4.join)(prefix.stdout.trim(), "node_modules", "@mutmutco", "cli", "dist", "index.cjs");
|
|
391
|
+
const root = globalRoot(env);
|
|
392
|
+
return root ? (0, import_node_path4.join)(root, "@mutmutco", "cli", "dist", "index.cjs") : null;
|
|
368
393
|
}
|
|
369
394
|
function probeCliVersion(distPath) {
|
|
370
395
|
if (!fileExists(distPath)) return null;
|
|
@@ -421,9 +446,10 @@ function cliArm(options) {
|
|
|
421
446
|
if (globalInstall.status !== 0) {
|
|
422
447
|
return { surface: "cli", from: installed, to: target, verdict: "defer", detail: `global install failed: ${(globalInstall.stderr || globalInstall.stdout).split("\n").filter(Boolean).slice(-3).join(" | ")}` };
|
|
423
448
|
}
|
|
424
|
-
const
|
|
449
|
+
const switchedDist = globalCliDist(env);
|
|
450
|
+
const switched = probeCliVersion(switchedDist ?? "");
|
|
425
451
|
if (switched !== target) {
|
|
426
|
-
return { surface: "cli", from: installed, to: switched ?? installed ?? "unknown", verdict: "fail", detail: `post-switch probe returned ${switched ?? "dead"}, expected ${target}` };
|
|
452
|
+
return { surface: "cli", from: installed, to: switched ?? installed ?? "unknown", verdict: "fail", detail: `post-switch probe returned ${switched ?? "dead"} at ${switchedDist ?? "an unresolvable path"}, expected ${target}; the global install already ran \u2014 nothing was rolled back` };
|
|
427
453
|
}
|
|
428
454
|
return { surface: "cli", from: installed, to: target, verdict: "ok", detail: `converged ${installed ?? "absent"} -> ${target} (staged, verified, switched)` };
|
|
429
455
|
}
|
|
@@ -433,9 +459,8 @@ var import_node_fs6 = require("node:fs");
|
|
|
433
459
|
var import_node_child_process4 = require("node:child_process");
|
|
434
460
|
var import_node_path5 = require("node:path");
|
|
435
461
|
function globalDist(env, packageName, distRel) {
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
return (0, import_node_path5.join)(prefix.stdout.trim(), "node_modules", ...packageName.split("/"), ...distRel.split("/"));
|
|
462
|
+
const root = globalRoot(env);
|
|
463
|
+
return root ? (0, import_node_path5.join)(root, ...packageName.split("/"), ...distRel.split("/")) : null;
|
|
439
464
|
}
|
|
440
465
|
function probeVersion(distPath) {
|
|
441
466
|
if (!fileExists(distPath)) return null;
|
|
@@ -501,9 +526,10 @@ function npmGlobalArm(options) {
|
|
|
501
526
|
if (globalInstall.status !== 0) {
|
|
502
527
|
return result(installed, target, "defer", `global install failed: ${tail(globalInstall.stderr || globalInstall.stdout)}`);
|
|
503
528
|
}
|
|
504
|
-
const
|
|
529
|
+
const switchedDist = globalDist(env, packageName, distRel);
|
|
530
|
+
const switched = probeVersion(switchedDist ?? "");
|
|
505
531
|
if (switched !== target) {
|
|
506
|
-
return result(installed, switched ?? installed ?? "unknown", "fail", `post-switch probe returned ${switched ?? "dead"}, expected ${target}`);
|
|
532
|
+
return result(installed, switched ?? installed ?? "unknown", "fail", `post-switch probe returned ${switched ?? "dead"} at ${switchedDist ?? "an unresolvable path"}, expected ${target}; the global install already ran \u2014 nothing was rolled back`);
|
|
507
533
|
}
|
|
508
534
|
return result(installed, target, "ok", `converged ${installed ?? "absent"} -> ${target} (staged, verified, switched)`);
|
|
509
535
|
}
|
|
@@ -523,6 +549,9 @@ var import_node_child_process5 = require("node:child_process");
|
|
|
523
549
|
var import_node_fs7 = require("node:fs");
|
|
524
550
|
var import_node_os3 = require("node:os");
|
|
525
551
|
var import_node_path6 = require("node:path");
|
|
552
|
+
function convergedAtOrAbove(installed, target, healthy) {
|
|
553
|
+
return healthy && typeof installed === "string" && compareSemver(installed, target) >= 0;
|
|
554
|
+
}
|
|
526
555
|
function cmdArg(value) {
|
|
527
556
|
return /^[A-Za-z0-9_@./:\\=+-]+$/.test(value) ? value : `"${value.replaceAll('"', '""')}"`;
|
|
528
557
|
}
|
|
@@ -646,9 +675,12 @@ function claudeArm(options) {
|
|
|
646
675
|
}
|
|
647
676
|
observed = claudeMmi(runner, env);
|
|
648
677
|
}
|
|
649
|
-
if (observed.row?.version
|
|
678
|
+
if (!convergedAtOrAbove(observed.row?.version, options.target, observed.row?.enabled === true)) {
|
|
650
679
|
return { surface: "claude", from, to: observed.row?.version ?? "unknown", verdict: "fail", detail: `post-update evidence is version ${observed.row?.version ?? "missing"}, enabled=${String(observed.row?.enabled)}, expected ${options.target}/true` };
|
|
651
680
|
}
|
|
681
|
+
if (observed.row.version !== options.target) {
|
|
682
|
+
return { surface: "claude", from, to: observed.row.version, verdict: "skip", detail: `installed ${observed.row.version} is above candidate ${options.target} (monotonic)` };
|
|
683
|
+
}
|
|
652
684
|
return { surface: "claude", from, to: options.target, verdict: "ok", detail: `catalog refreshed; converged and verified ${from ?? "absent"} -> ${options.target}` };
|
|
653
685
|
}
|
|
654
686
|
var SNAPSHOT_MISSING = /marketplace snapshot|marketplace root does not contain/i;
|
|
@@ -688,9 +720,12 @@ function codexArm(options) {
|
|
|
688
720
|
}
|
|
689
721
|
observed = codexMmi(runner, env);
|
|
690
722
|
}
|
|
691
|
-
if (observed.row?.version
|
|
723
|
+
if (!convergedAtOrAbove(observed.row?.version, options.target, observed.row?.installed === true && observed.row?.enabled === true)) {
|
|
692
724
|
return { surface: "codex", from, to: observed.row?.version ?? "unknown", verdict: "fail", detail: `post-upgrade evidence is version ${observed.row?.version ?? "missing"}, installed=${String(observed.row?.installed)}, enabled=${String(observed.row?.enabled)}, expected ${options.target}/true/true` };
|
|
693
725
|
}
|
|
726
|
+
if (observed.row.version !== options.target) {
|
|
727
|
+
return { surface: "codex", from, to: observed.row.version, verdict: "skip", detail: `installed ${observed.row.version} is above candidate ${options.target} (monotonic)` };
|
|
728
|
+
}
|
|
694
729
|
return { surface: "codex", from, to: options.target, verdict: "ok", detail: `marketplace upgraded; payload refresh and native evidence verified at ${options.target}` };
|
|
695
730
|
}
|
|
696
731
|
var KILO_PACKAGE = "@mutmutco/kilo-plugin";
|
|
@@ -1787,6 +1822,7 @@ function reconcile(options) {
|
|
|
1787
1822
|
};
|
|
1788
1823
|
const summary = { run, target: null, gate: null, atomicity: null, cli: null, updater: null, plugins: [], reap: [], surfaces: [], journalOk: true, exit: 0 };
|
|
1789
1824
|
const recordArm = (arm) => {
|
|
1825
|
+
if (arm.verdict === "fail") arm.repeatFailure = journalSurfaceRepeatedFail(paths.journalPath, arm.surface, run);
|
|
1790
1826
|
journal({ kind: "arm", surface: arm.surface, from: arm.from, to: arm.to, verdict: arm.verdict, detail: arm.detail, compat: arm.compat });
|
|
1791
1827
|
options.onArm?.(arm);
|
|
1792
1828
|
if (arm.verdict === "defer") summary.exit = Math.max(summary.exit, 2);
|
|
@@ -1890,16 +1926,37 @@ function reconcile(options) {
|
|
|
1890
1926
|
// src/scheduler.ts
|
|
1891
1927
|
var import_node_child_process9 = require("node:child_process");
|
|
1892
1928
|
var import_node_fs14 = require("node:fs");
|
|
1929
|
+
var import_node_os5 = require("node:os");
|
|
1893
1930
|
var import_node_path12 = require("node:path");
|
|
1894
1931
|
var TASK_NAME = "MMI Fleet Updater";
|
|
1895
1932
|
var LAUNCHER_FILE = "reconcile-hidden.vbs";
|
|
1896
1933
|
var RUN_BOUND = "PT15M";
|
|
1897
1934
|
var REPEAT_INTERVAL = "PT1H";
|
|
1898
1935
|
var CADENCE = `hourly from registration, StartWhenAvailable, single-instance, ${RUN_BOUND} bound`;
|
|
1936
|
+
var POSIX_LAUNCHER_FILE = "reconcile.sh";
|
|
1937
|
+
var STARTINTERVAL_SECONDS = 3600;
|
|
1938
|
+
var RUN_BOUND_SECONDS = 900;
|
|
1939
|
+
var POSIX_CADENCE = "hourly from registration, catch-up-if-missed, single-instance";
|
|
1940
|
+
var LAUNCHD_LABEL = "com.mutmutco.mmi-hub.updater";
|
|
1941
|
+
var LAUNCHD_PLIST_NAME = `${LAUNCHD_LABEL}.plist`;
|
|
1942
|
+
var SYSTEMD_UNIT = "mmi-hub-updater";
|
|
1943
|
+
var SYSTEMD_SERVICE_NAME = `${SYSTEMD_UNIT}.service`;
|
|
1944
|
+
var SYSTEMD_TIMER_NAME = `${SYSTEMD_UNIT}.timer`;
|
|
1899
1945
|
function schtasks(args) {
|
|
1900
1946
|
const result = (0, import_node_child_process9.spawnSync)("schtasks.exe", args, { encoding: "utf8", windowsHide: true });
|
|
1901
1947
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
1902
1948
|
}
|
|
1949
|
+
function launchctl(args) {
|
|
1950
|
+
const result = (0, import_node_child_process9.spawnSync)("launchctl", args, { encoding: "utf8", windowsHide: true });
|
|
1951
|
+
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
1952
|
+
}
|
|
1953
|
+
function systemctlUser(args) {
|
|
1954
|
+
const result = (0, import_node_child_process9.spawnSync)("systemctl", ["--user", ...args], { encoding: "utf8", windowsHide: true });
|
|
1955
|
+
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
1956
|
+
}
|
|
1957
|
+
function uid() {
|
|
1958
|
+
return typeof process.getuid === "function" ? process.getuid() : 0;
|
|
1959
|
+
}
|
|
1903
1960
|
function xmlEscape(value) {
|
|
1904
1961
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1905
1962
|
}
|
|
@@ -1918,6 +1975,14 @@ code = shell.Run("""${vbsQuote(nodeExe)}"" ""${vbsQuote(hubDist)}"" update", 0,
|
|
|
1918
1975
|
WScript.Quit code
|
|
1919
1976
|
`;
|
|
1920
1977
|
}
|
|
1978
|
+
function launcherSh(nodeExe, hubDist) {
|
|
1979
|
+
const shQuote = (value) => `'${value.replaceAll("'", `'\\''`)}'`;
|
|
1980
|
+
return `#!/bin/sh
|
|
1981
|
+
# MMI Hub maintenance launcher \u2014 generated by \`mmi-hub autoupdate on\`.
|
|
1982
|
+
# The exit code is propagated and reported by \`mmi-hub status\`.
|
|
1983
|
+
exec ${shQuote(nodeExe)} ${shQuote(hubDist)} update
|
|
1984
|
+
`;
|
|
1985
|
+
}
|
|
1921
1986
|
function taskXml(launcherPath, workDir, userId, startBoundary) {
|
|
1922
1987
|
return `<?xml version="1.0" encoding="UTF-16"?>
|
|
1923
1988
|
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
@@ -1960,7 +2025,53 @@ function taskXml(launcherPath, workDir, userId, startBoundary) {
|
|
|
1960
2025
|
</Task>
|
|
1961
2026
|
`;
|
|
1962
2027
|
}
|
|
1963
|
-
function
|
|
2028
|
+
function launchdPlist(launcherPath, workDir) {
|
|
2029
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
2030
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2031
|
+
<plist version="1.0">
|
|
2032
|
+
<dict>
|
|
2033
|
+
<key>Label</key>
|
|
2034
|
+
<string>${LAUNCHD_LABEL}</string>
|
|
2035
|
+
<key>ProgramArguments</key>
|
|
2036
|
+
<array>
|
|
2037
|
+
<string>/bin/sh</string>
|
|
2038
|
+
<string>${xmlEscape(launcherPath)}</string>
|
|
2039
|
+
</array>
|
|
2040
|
+
<key>WorkingDirectory</key>
|
|
2041
|
+
<string>${xmlEscape(workDir)}</string>
|
|
2042
|
+
<key>StartInterval</key>
|
|
2043
|
+
<integer>${STARTINTERVAL_SECONDS}</integer>
|
|
2044
|
+
<key>RunAtLoad</key>
|
|
2045
|
+
<true/>
|
|
2046
|
+
</dict>
|
|
2047
|
+
</plist>
|
|
2048
|
+
`;
|
|
2049
|
+
}
|
|
2050
|
+
function systemdServiceUnit(launcherPath, workDir) {
|
|
2051
|
+
return `[Unit]
|
|
2052
|
+
Description=MMI Hub maintenance \u2014 converge this machine's MMI surfaces to the newest gated release
|
|
2053
|
+
|
|
2054
|
+
[Service]
|
|
2055
|
+
Type=oneshot
|
|
2056
|
+
WorkingDirectory=${workDir}
|
|
2057
|
+
ExecStart=/bin/sh ${launcherPath}
|
|
2058
|
+
TimeoutStartSec=${RUN_BOUND_SECONDS}
|
|
2059
|
+
`;
|
|
2060
|
+
}
|
|
2061
|
+
function systemdTimerUnit() {
|
|
2062
|
+
return `[Unit]
|
|
2063
|
+
Description=MMI Hub maintenance hourly reconcile timer
|
|
2064
|
+
|
|
2065
|
+
[Timer]
|
|
2066
|
+
OnUnitActiveSec=${STARTINTERVAL_SECONDS}
|
|
2067
|
+
Persistent=true
|
|
2068
|
+
AccuracySec=1min
|
|
2069
|
+
|
|
2070
|
+
[Install]
|
|
2071
|
+
WantedBy=timers.target
|
|
2072
|
+
`;
|
|
2073
|
+
}
|
|
2074
|
+
function readFileOrNull(path) {
|
|
1964
2075
|
try {
|
|
1965
2076
|
return (0, import_node_fs14.readFileSync)(path, "utf8");
|
|
1966
2077
|
} catch {
|
|
@@ -1983,6 +2094,7 @@ function elevatedRelaunch(mode, env) {
|
|
|
1983
2094
|
}
|
|
1984
2095
|
var NEVER_RUN_RESULT = 267011;
|
|
1985
2096
|
var NEVER_RUN_YEAR = 1999;
|
|
2097
|
+
var EMPTY_RUN_INFO = { lastRunTime: null, lastTaskResult: null, nextRunTime: null };
|
|
1986
2098
|
function taskRunInfo() {
|
|
1987
2099
|
const result = (0, import_node_child_process9.spawnSync)(
|
|
1988
2100
|
"powershell.exe",
|
|
@@ -2004,24 +2116,20 @@ function taskRunInfo() {
|
|
|
2004
2116
|
function neverRan(info) {
|
|
2005
2117
|
return info.lastTaskResult === NEVER_RUN_RESULT || (info.lastRunTime?.includes(String(NEVER_RUN_YEAR)) ?? false);
|
|
2006
2118
|
}
|
|
2007
|
-
function
|
|
2008
|
-
const empty = { lastRunTime: null, lastTaskResult: null, nextRunTime: null };
|
|
2009
|
-
if (process.platform !== "win32") {
|
|
2010
|
-
return { ok: false, supported: false, enabled: false, ...empty, detail: "automatic scheduling is available on Windows Task Scheduler only" };
|
|
2011
|
-
}
|
|
2119
|
+
function windowsSchedulerStatus(env) {
|
|
2012
2120
|
const query = schtasks(["/query", "/tn", TASK_NAME, "/xml"]);
|
|
2013
2121
|
if (query.status !== 0) {
|
|
2014
|
-
return { ok: false, supported: true, enabled: false, ...
|
|
2122
|
+
return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${TASK_NAME}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
|
|
2015
2123
|
}
|
|
2016
2124
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2017
2125
|
const launcherPath = (0, import_node_path12.join)(statePaths(env).root, LAUNCHER_FILE);
|
|
2018
2126
|
const info = taskRunInfo();
|
|
2019
|
-
const runInfo = info ??
|
|
2127
|
+
const runInfo = info ?? EMPTY_RUN_INFO;
|
|
2020
2128
|
const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...runInfo, detail });
|
|
2021
2129
|
if (!query.stdout.includes(launcherPath)) {
|
|
2022
2130
|
return fail(`"${TASK_NAME}" action does not run the hidden launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2023
2131
|
}
|
|
2024
|
-
const launcher =
|
|
2132
|
+
const launcher = readFileOrNull(launcherPath);
|
|
2025
2133
|
if (hubDist && !launcher?.includes(hubDist)) {
|
|
2026
2134
|
return fail(`"${TASK_NAME}" launcher does not point at the current @mutmutco/hub dist ${hubDist} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2027
2135
|
}
|
|
@@ -2038,9 +2146,74 @@ function schedulerStatus(env = process.env) {
|
|
|
2038
2146
|
const ran = !info ? "run history unreadable" : neverRan(info) ? "never run yet" : `last run ${info.lastRunTime} \u2192 exit ${info.lastTaskResult ?? "unknown"}`;
|
|
2039
2147
|
return { ok: true, supported: true, enabled: true, ...runInfo, detail: `"${TASK_NAME}" registered ${CADENCE}; ${points}; ${ran}; next ${runInfo.nextRunTime ?? "unscheduled"}` };
|
|
2040
2148
|
}
|
|
2041
|
-
function
|
|
2042
|
-
|
|
2043
|
-
const
|
|
2149
|
+
function darwinSchedulerStatus(env) {
|
|
2150
|
+
const plistPath = (0, import_node_path12.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
|
|
2151
|
+
const plist = readFileOrNull(plistPath);
|
|
2152
|
+
if (!plist) {
|
|
2153
|
+
return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
|
|
2154
|
+
}
|
|
2155
|
+
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2156
|
+
const launcherPath = (0, import_node_path12.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
|
|
2157
|
+
const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
|
|
2158
|
+
if (!plist.includes(launcherPath)) {
|
|
2159
|
+
return fail(`"${LAUNCHD_LABEL}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2160
|
+
}
|
|
2161
|
+
const launcher = readFileOrNull(launcherPath);
|
|
2162
|
+
if (hubDist && !launcher?.includes(hubDist)) {
|
|
2163
|
+
return fail(`"${LAUNCHD_LABEL}" launcher does not point at the current @mutmutco/hub dist ${hubDist} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2164
|
+
}
|
|
2165
|
+
if (!plist.includes(`<integer>${STARTINTERVAL_SECONDS}</integer>`)) {
|
|
2166
|
+
return fail(`"${LAUNCHD_LABEL}" has no ${STARTINTERVAL_SECONDS}s StartInterval \u2014 run \`mmi-hub autoupdate on\``);
|
|
2167
|
+
}
|
|
2168
|
+
const loaded = launchctl(["print", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
2169
|
+
if (loaded.status !== 0) {
|
|
2170
|
+
return fail(`"${LAUNCHD_LABEL}" is registered but not loaded \u2014 run \`mmi-hub autoupdate on\``);
|
|
2171
|
+
}
|
|
2172
|
+
const points = `points at ${hubDist ?? "the registered Hub dist"}`;
|
|
2173
|
+
return { ok: true, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" registered ${POSIX_CADENCE}; ${points}` };
|
|
2174
|
+
}
|
|
2175
|
+
function linuxSchedulerStatus(env) {
|
|
2176
|
+
const unitDir = (0, import_node_path12.join)((0, import_node_os5.homedir)(), ".config", "systemd", "user");
|
|
2177
|
+
const timerPath = (0, import_node_path12.join)(unitDir, SYSTEMD_TIMER_NAME);
|
|
2178
|
+
const timer = readFileOrNull(timerPath);
|
|
2179
|
+
const service = readFileOrNull((0, import_node_path12.join)(unitDir, SYSTEMD_SERVICE_NAME));
|
|
2180
|
+
if (!timer || !service) {
|
|
2181
|
+
return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${SYSTEMD_TIMER_NAME}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
|
|
2182
|
+
}
|
|
2183
|
+
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2184
|
+
const launcherPath = (0, import_node_path12.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
|
|
2185
|
+
const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
|
|
2186
|
+
if (!service.includes(launcherPath)) {
|
|
2187
|
+
return fail(`"${SYSTEMD_SERVICE_NAME}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2188
|
+
}
|
|
2189
|
+
const launcher = readFileOrNull(launcherPath);
|
|
2190
|
+
if (hubDist && !launcher?.includes(hubDist)) {
|
|
2191
|
+
return fail(`"${SYSTEMD_SERVICE_NAME}" launcher does not point at the current @mutmutco/hub dist ${hubDist} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2192
|
+
}
|
|
2193
|
+
if (!timer.includes(`OnUnitActiveSec=${STARTINTERVAL_SECONDS}`)) {
|
|
2194
|
+
return fail(`"${SYSTEMD_TIMER_NAME}" has no ${STARTINTERVAL_SECONDS}s OnUnitActiveSec \u2014 run \`mmi-hub autoupdate on\``);
|
|
2195
|
+
}
|
|
2196
|
+
const enabled = systemctlUser(["is-enabled", SYSTEMD_TIMER_NAME]);
|
|
2197
|
+
if (enabled.status !== 0) {
|
|
2198
|
+
return fail(`"${SYSTEMD_TIMER_NAME}" is registered but not enabled \u2014 run \`mmi-hub autoupdate on\``);
|
|
2199
|
+
}
|
|
2200
|
+
const points = `points at ${hubDist ?? "the registered Hub dist"}`;
|
|
2201
|
+
return { ok: true, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail: `"${SYSTEMD_TIMER_NAME}" registered ${POSIX_CADENCE}, ${RUN_BOUND_SECONDS}s bound; ${points}` };
|
|
2202
|
+
}
|
|
2203
|
+
function schedulerStatus(env = process.env) {
|
|
2204
|
+
if (process.platform === "win32") return windowsSchedulerStatus(env);
|
|
2205
|
+
if (process.platform === "darwin") return darwinSchedulerStatus(env);
|
|
2206
|
+
if (process.platform === "linux") return linuxSchedulerStatus(env);
|
|
2207
|
+
return {
|
|
2208
|
+
ok: false,
|
|
2209
|
+
supported: false,
|
|
2210
|
+
enabled: false,
|
|
2211
|
+
...EMPTY_RUN_INFO,
|
|
2212
|
+
detail: "automatic scheduling is available on Windows Task Scheduler, macOS launchd, and Linux systemd user timers only"
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
function installWindowsTask(env) {
|
|
2216
|
+
const current = windowsSchedulerStatus(env);
|
|
2044
2217
|
if (current.ok) return { ok: true, detail: `already enabled \u2014 ${current.detail}` };
|
|
2045
2218
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2046
2219
|
if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
|
|
@@ -2057,18 +2230,68 @@ function installTask(env = process.env) {
|
|
|
2057
2230
|
if (!denied || !elevatedRelaunch("on", env)) {
|
|
2058
2231
|
return { ok: false, detail: denied ? "task creation needs elevation and UAC consent was not granted \u2014 run `mmi-hub autoupdate on` from an elevated terminal" : `schtasks /create failed: ${(create.stderr || create.stdout).trim()}` };
|
|
2059
2232
|
}
|
|
2060
|
-
const verify =
|
|
2233
|
+
const verify = windowsSchedulerStatus(env);
|
|
2061
2234
|
return verify.ok ? { ok: true, detail: `enabled "${TASK_NAME}" (elevated) \u2014 ${CADENCE}` } : { ok: false, detail: `elevated registration did not take: ${verify.detail}` };
|
|
2062
2235
|
}
|
|
2063
2236
|
return { ok: true, detail: `enabled "${TASK_NAME}" \u2014 ${CADENCE} \u2192 ${hubDist}` };
|
|
2064
2237
|
}
|
|
2065
|
-
function
|
|
2066
|
-
|
|
2238
|
+
function installLaunchd(env) {
|
|
2239
|
+
const current = darwinSchedulerStatus(env);
|
|
2240
|
+
if (current.ok) return { ok: true, detail: `already enabled \u2014 ${current.detail}` };
|
|
2241
|
+
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2242
|
+
if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
|
|
2243
|
+
const paths = ensureState(env);
|
|
2244
|
+
const launcherPath = (0, import_node_path12.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2245
|
+
(0, import_node_fs14.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
|
|
2246
|
+
(0, import_node_fs14.chmodSync)(launcherPath, 493);
|
|
2247
|
+
const agentsDir = (0, import_node_path12.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents");
|
|
2248
|
+
(0, import_node_fs14.mkdirSync)(agentsDir, { recursive: true });
|
|
2249
|
+
const plistPath = (0, import_node_path12.join)(agentsDir, LAUNCHD_PLIST_NAME);
|
|
2250
|
+
(0, import_node_fs14.writeFileSync)(plistPath, launchdPlist(launcherPath, paths.root), "utf8");
|
|
2251
|
+
launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
2252
|
+
const load = launchctl(["bootstrap", `gui/${uid()}`, plistPath]);
|
|
2253
|
+
if (load.status !== 0) {
|
|
2254
|
+
return { ok: false, detail: `launchctl bootstrap failed: ${(load.stderr || load.stdout).trim() || "unknown error"}` };
|
|
2255
|
+
}
|
|
2256
|
+
const verify = darwinSchedulerStatus(env);
|
|
2257
|
+
return verify.ok ? { ok: true, detail: `enabled "${LAUNCHD_LABEL}" \u2014 ${POSIX_CADENCE} \u2192 ${hubDist}` } : { ok: false, detail: `registration did not take: ${verify.detail}` };
|
|
2258
|
+
}
|
|
2259
|
+
function installSystemdTimer(env) {
|
|
2260
|
+
const current = linuxSchedulerStatus(env);
|
|
2261
|
+
if (current.ok) return { ok: true, detail: `already enabled \u2014 ${current.detail}` };
|
|
2262
|
+
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2263
|
+
if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
|
|
2264
|
+
const paths = ensureState(env);
|
|
2265
|
+
const launcherPath = (0, import_node_path12.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2266
|
+
(0, import_node_fs14.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
|
|
2267
|
+
(0, import_node_fs14.chmodSync)(launcherPath, 493);
|
|
2268
|
+
const unitDir = (0, import_node_path12.join)((0, import_node_os5.homedir)(), ".config", "systemd", "user");
|
|
2269
|
+
(0, import_node_fs14.mkdirSync)(unitDir, { recursive: true });
|
|
2270
|
+
(0, import_node_fs14.writeFileSync)((0, import_node_path12.join)(unitDir, SYSTEMD_SERVICE_NAME), systemdServiceUnit(launcherPath, paths.root), "utf8");
|
|
2271
|
+
(0, import_node_fs14.writeFileSync)((0, import_node_path12.join)(unitDir, SYSTEMD_TIMER_NAME), systemdTimerUnit(), "utf8");
|
|
2272
|
+
const reload = systemctlUser(["daemon-reload"]);
|
|
2273
|
+
if (reload.status !== 0) {
|
|
2274
|
+
return { ok: false, detail: `systemctl --user daemon-reload failed: ${(reload.stderr || reload.stdout).trim() || "unknown error \u2014 is a user systemd instance running?"}` };
|
|
2275
|
+
}
|
|
2276
|
+
const enable = systemctlUser(["enable", "--now", SYSTEMD_TIMER_NAME]);
|
|
2277
|
+
if (enable.status !== 0) {
|
|
2278
|
+
return { ok: false, detail: `systemctl --user enable --now ${SYSTEMD_TIMER_NAME} failed: ${(enable.stderr || enable.stdout).trim() || "unknown error"}` };
|
|
2279
|
+
}
|
|
2280
|
+
const verify = linuxSchedulerStatus(env);
|
|
2281
|
+
return verify.ok ? { ok: true, detail: `enabled "${SYSTEMD_TIMER_NAME}" \u2014 ${POSIX_CADENCE} \u2192 ${hubDist}` } : { ok: false, detail: `registration did not take: ${verify.detail}` };
|
|
2282
|
+
}
|
|
2283
|
+
function installTask(env = process.env) {
|
|
2284
|
+
if (process.platform === "win32") return installWindowsTask(env);
|
|
2285
|
+
if (process.platform === "darwin") return installLaunchd(env);
|
|
2286
|
+
if (process.platform === "linux") return installSystemdTimer(env);
|
|
2287
|
+
return { ok: false, detail: "scheduler registration is available on Windows Task Scheduler, macOS launchd, and Linux systemd user timers only" };
|
|
2288
|
+
}
|
|
2289
|
+
function uninstallWindowsTask(env) {
|
|
2067
2290
|
const del = schtasks(["/delete", "/tn", TASK_NAME, "/f"]);
|
|
2068
2291
|
if (del.status !== 0) {
|
|
2069
2292
|
const query = schtasks(["/query", "/tn", TASK_NAME]);
|
|
2070
2293
|
if (query.status !== 0 && !/access is denied/i.test(del.stderr + del.stdout)) {
|
|
2071
|
-
dropLauncher(env);
|
|
2294
|
+
dropLauncher(env, LAUNCHER_FILE);
|
|
2072
2295
|
return { ok: true, detail: `automatic updates already off \u2014 "${TASK_NAME}" is not registered; installed tooling was kept` };
|
|
2073
2296
|
}
|
|
2074
2297
|
const denied = /access is denied/i.test(del.stderr + del.stdout);
|
|
@@ -2079,12 +2302,47 @@ function uninstallTask(env = process.env) {
|
|
|
2079
2302
|
return { ok: false, detail: `elevated deletion did not take: "${TASK_NAME}" is still registered` };
|
|
2080
2303
|
}
|
|
2081
2304
|
}
|
|
2082
|
-
dropLauncher(env);
|
|
2305
|
+
dropLauncher(env, LAUNCHER_FILE);
|
|
2083
2306
|
return { ok: true, detail: `automatic updates off \u2014 removed "${TASK_NAME}"; installed tooling was kept` };
|
|
2084
2307
|
}
|
|
2085
|
-
function
|
|
2308
|
+
function uninstallLaunchd(env) {
|
|
2309
|
+
const plistPath = (0, import_node_path12.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
|
|
2310
|
+
const boot = launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
2311
|
+
if (boot.status !== 0 && (0, import_node_fs14.existsSync)(plistPath)) {
|
|
2312
|
+
return { ok: false, detail: `launchctl bootout failed: ${(boot.stderr || boot.stdout).trim() || "unknown error"}` };
|
|
2313
|
+
}
|
|
2314
|
+
try {
|
|
2315
|
+
(0, import_node_fs14.rmSync)(plistPath, { force: true });
|
|
2316
|
+
} catch {
|
|
2317
|
+
}
|
|
2318
|
+
dropLauncher(env, POSIX_LAUNCHER_FILE);
|
|
2319
|
+
return { ok: true, detail: `automatic updates off \u2014 removed "${LAUNCHD_LABEL}"; installed tooling was kept` };
|
|
2320
|
+
}
|
|
2321
|
+
function uninstallSystemdTimer(env) {
|
|
2322
|
+
const unitDir = (0, import_node_path12.join)((0, import_node_os5.homedir)(), ".config", "systemd", "user");
|
|
2323
|
+
const timerPath = (0, import_node_path12.join)(unitDir, SYSTEMD_TIMER_NAME);
|
|
2324
|
+
const disable = systemctlUser(["disable", "--now", SYSTEMD_TIMER_NAME]);
|
|
2325
|
+
if (disable.status !== 0 && (0, import_node_fs14.existsSync)(timerPath)) {
|
|
2326
|
+
return { ok: false, detail: `systemctl --user disable --now ${SYSTEMD_TIMER_NAME} failed: ${(disable.stderr || disable.stdout).trim() || "unknown error"}` };
|
|
2327
|
+
}
|
|
2328
|
+
try {
|
|
2329
|
+
(0, import_node_fs14.rmSync)(timerPath, { force: true });
|
|
2330
|
+
(0, import_node_fs14.rmSync)((0, import_node_path12.join)(unitDir, SYSTEMD_SERVICE_NAME), { force: true });
|
|
2331
|
+
} catch {
|
|
2332
|
+
}
|
|
2333
|
+
systemctlUser(["daemon-reload"]);
|
|
2334
|
+
dropLauncher(env, POSIX_LAUNCHER_FILE);
|
|
2335
|
+
return { ok: true, detail: `automatic updates off \u2014 removed "${SYSTEMD_TIMER_NAME}"; installed tooling was kept` };
|
|
2336
|
+
}
|
|
2337
|
+
function uninstallTask(env = process.env) {
|
|
2338
|
+
if (process.platform === "win32") return uninstallWindowsTask(env);
|
|
2339
|
+
if (process.platform === "darwin") return uninstallLaunchd(env);
|
|
2340
|
+
if (process.platform === "linux") return uninstallSystemdTimer(env);
|
|
2341
|
+
return { ok: false, detail: "scheduler registration is available on Windows Task Scheduler, macOS launchd, and Linux systemd user timers only" };
|
|
2342
|
+
}
|
|
2343
|
+
function dropLauncher(env, file) {
|
|
2086
2344
|
try {
|
|
2087
|
-
(0, import_node_fs14.rmSync)((0, import_node_path12.join)(statePaths(env).root,
|
|
2345
|
+
(0, import_node_fs14.rmSync)((0, import_node_path12.join)(statePaths(env).root, file), { force: true });
|
|
2088
2346
|
} catch {
|
|
2089
2347
|
}
|
|
2090
2348
|
}
|
|
@@ -2149,7 +2407,7 @@ function hubStatus(hubVersion, env = process.env, invokedPath = process.argv[1])
|
|
|
2149
2407
|
const failures = [];
|
|
2150
2408
|
if (error) failures.push(`${error} \u2014 inspect permissions for ${paths.journalPath}`);
|
|
2151
2409
|
if (!events.length && !error) failures.push("no maintenance journal yet \u2014 run `mmi-hub install`");
|
|
2152
|
-
if (!schedule.ok) failures.push(schedule.detail);
|
|
2410
|
+
if (!schedule.ok && schedule.supported) failures.push(schedule.detail);
|
|
2153
2411
|
for (const [surface, row] of Object.entries(installed)) {
|
|
2154
2412
|
if (!row.installed) failures.push(`${surface}: installed version is unreadable at ${row.location ?? "an unknown location"} \u2014 run \`mmi-hub update\``);
|
|
2155
2413
|
if (row.state === "behind") failures.push(`${surface}: installed ${row.installed} is behind expected ${row.expected} \u2014 run \`mmi-hub update\``);
|
|
@@ -2157,7 +2415,8 @@ function hubStatus(hubVersion, env = process.env, invokedPath = process.argv[1])
|
|
|
2157
2415
|
for (const event of latestRunEvents) {
|
|
2158
2416
|
if ((event.kind === "arm" || event.kind === "reconcile") && (event.verdict === "fail" || event.verdict === "defer")) {
|
|
2159
2417
|
const surface = event.surface === "updater" ? "hub" : event.surface ?? event.kind;
|
|
2160
|
-
|
|
2418
|
+
const repeat = event.kind === "arm" && event.verdict === "fail" && event.surface && journalSurfaceRepeatedFail(paths.journalPath, event.surface, latestEvent.run);
|
|
2419
|
+
failures.push(repeat ? `last run ${surface}: ${event.detail ?? event.verdict} \u2014 already failed the previous run too; \`mmi-hub update\` will not clear it, inspect the journal` : `last run ${surface}: ${event.detail ?? event.verdict} \u2014 run \`mmi-hub update\``);
|
|
2161
2420
|
}
|
|
2162
2421
|
}
|
|
2163
2422
|
return {
|
|
@@ -2218,9 +2477,10 @@ var RETRY_HINTS = [
|
|
|
2218
2477
|
[/elevat|access is denied/, "run from elevated terminal"]
|
|
2219
2478
|
];
|
|
2220
2479
|
var FAIL_CONSEQUENCE = [
|
|
2221
|
-
[/
|
|
2222
|
-
[/
|
|
2480
|
+
[/rollback failed|did not restore/, "rollback incomplete \u2014 run doctor now"],
|
|
2481
|
+
[/post-switch probe|post-update evidence|post-install evidence|post-switch marker|expected .*\/true/, "not verified \u2014 switch already applied, nothing was rolled back"]
|
|
2223
2482
|
];
|
|
2483
|
+
var RESTORE_EVIDENCE = /\brestored\b|\brolled back to\b/i;
|
|
2224
2484
|
function matchFirst(patterns, detail) {
|
|
2225
2485
|
const haystack = (detail ?? "").toLowerCase();
|
|
2226
2486
|
for (const [pattern, text] of patterns) if (pattern.test(haystack)) return text;
|
|
@@ -2229,13 +2489,17 @@ function matchFirst(patterns, detail) {
|
|
|
2229
2489
|
function retryHint(detail) {
|
|
2230
2490
|
return matchFirst(RETRY_HINTS, detail) ?? "retries next hourly run";
|
|
2231
2491
|
}
|
|
2232
|
-
function failConsequence(
|
|
2492
|
+
function failConsequence(row) {
|
|
2493
|
+
const detail = row.detail ?? "";
|
|
2494
|
+
if (RESTORE_EVIDENCE.test(detail)) {
|
|
2495
|
+
return `not verified \u2014 restored previous version ${row.from ?? "unknown"}, safe to use`;
|
|
2496
|
+
}
|
|
2233
2497
|
return matchFirst(FAIL_CONSEQUENCE, detail) ?? "update failed \u2014 previous version kept";
|
|
2234
2498
|
}
|
|
2235
2499
|
function classify(arm) {
|
|
2236
2500
|
const kind = arm.verdict === "ok" ? arm.from !== null && arm.from === arm.to ? "current" : "updated" : arm.verdict === "skip" ? "keep" : arm.verdict === "defer" ? "retry" : "failed";
|
|
2237
2501
|
const note = kind === "updated" ? ACTIVATION_NOTE[arm.surface] ?? null : kind === "keep" ? "above target" : kind === "retry" ? retryHint(arm.detail) : null;
|
|
2238
|
-
return { name: arm.surface === "updater" ? "hub" : arm.surface, from: arm.from, to: arm.to, kind, note, detail: arm.detail };
|
|
2502
|
+
return { name: arm.surface === "updater" ? "hub" : arm.surface, from: arm.from, to: arm.to, kind, note, detail: arm.detail, repeatFailure: arm.repeatFailure === true };
|
|
2239
2503
|
}
|
|
2240
2504
|
function statusText(kind, dryRun) {
|
|
2241
2505
|
if (dryRun && kind === "updated") return "would update";
|
|
@@ -2309,7 +2573,7 @@ function collectRows(summary) {
|
|
|
2309
2573
|
for (const arm of summary.plugins) rows.push(classify(arm));
|
|
2310
2574
|
for (const surface of summary.surfaces) {
|
|
2311
2575
|
if (surface.present && surface.action === "arm-pending") {
|
|
2312
|
-
rows.push({ name: surface.id, from: null, to: summary.target ?? "?", kind: "pending", note: "arm not landed", detail: "" });
|
|
2576
|
+
rows.push({ name: surface.id, from: null, to: summary.target ?? "?", kind: "pending", note: "arm not landed", detail: "", repeatFailure: false });
|
|
2313
2577
|
}
|
|
2314
2578
|
}
|
|
2315
2579
|
if (summary.updater) rows.push(classify(summary.updater));
|
|
@@ -2360,8 +2624,8 @@ function reportFooterLines(summary, options = {}) {
|
|
|
2360
2624
|
out.push("");
|
|
2361
2625
|
for (const row of rows.filter((row2) => row2.kind === "failed")) {
|
|
2362
2626
|
const mark = tty ? paint("failed", "\u2717 ") : "";
|
|
2363
|
-
out.push(...noteLines(" " + mark + row.name, failConsequence(row
|
|
2364
|
-
out.push(...noteLines(" fix: ",
|
|
2627
|
+
out.push(...noteLines(" " + mark + row.name, failConsequence(row), width));
|
|
2628
|
+
out.push(...noteLines(" fix: ", row.repeatFailure ? "doctor + update already tried and did not clear this \u2014 see log, do not repeat" : "mmi-cli doctor, then mmi-hub update", width));
|
|
2365
2629
|
out.push(...noteLines(" log: ", journalPath, width));
|
|
2366
2630
|
}
|
|
2367
2631
|
}
|
|
@@ -2596,6 +2860,8 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
2596
2860
|
hermesArm,
|
|
2597
2861
|
hermesHome,
|
|
2598
2862
|
hubStatus,
|
|
2863
|
+
launchdPlist,
|
|
2864
|
+
launcherSh,
|
|
2599
2865
|
launcherVbs,
|
|
2600
2866
|
localStartBoundary,
|
|
2601
2867
|
main,
|
|
@@ -2606,6 +2872,8 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
2606
2872
|
retryHint,
|
|
2607
2873
|
schedulerStatus,
|
|
2608
2874
|
startSpinner,
|
|
2875
|
+
systemdServiceUnit,
|
|
2876
|
+
systemdTimerUnit,
|
|
2609
2877
|
taskXml,
|
|
2610
2878
|
updateHeader,
|
|
2611
2879
|
wrapWords
|
package/package.json
CHANGED