@mutmutco/hub 4.0.14 → 4.0.16
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 +465 -244
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
|
+
buildWatcherCommand: () => buildWatcherCommand,
|
|
25
|
+
clearPending: () => clearPending,
|
|
24
26
|
enumerateSurfaces: () => enumerateSurfaces,
|
|
25
27
|
failConsequence: () => failConsequence,
|
|
26
28
|
formatReconcileReport: () => formatReconcileReport,
|
|
@@ -31,24 +33,32 @@ __export(index_exports, {
|
|
|
31
33
|
launcherSh: () => launcherSh,
|
|
32
34
|
launcherVbs: () => launcherVbs,
|
|
33
35
|
localStartBoundary: () => localStartBoundary,
|
|
36
|
+
lockShaped: () => lockShaped,
|
|
34
37
|
main: () => main,
|
|
38
|
+
noteBusyDefer: () => noteBusyDefer,
|
|
35
39
|
ownVersion: () => ownVersion,
|
|
40
|
+
pendingPath: () => pendingPath,
|
|
41
|
+
pidAlive: () => pidAlive,
|
|
36
42
|
probeHermesInstallation: () => probeHermesInstallation,
|
|
37
43
|
progressiveRow: () => progressiveRow,
|
|
44
|
+
readPending: () => readPending,
|
|
38
45
|
reconcileCursorUserHooks: () => reconcileCursorUserHooks,
|
|
39
46
|
reportFooterLines: () => reportFooterLines,
|
|
40
47
|
retryHint: () => retryHint,
|
|
48
|
+
schedulePendingWatcher: () => schedulePendingWatcher,
|
|
41
49
|
schedulerStatus: () => schedulerStatus,
|
|
42
50
|
startSpinner: () => startSpinner,
|
|
43
51
|
systemdServiceUnit: () => systemdServiceUnit,
|
|
44
52
|
systemdTimerUnit: () => systemdTimerUnit,
|
|
45
53
|
taskXml: () => taskXml,
|
|
46
54
|
updateHeader: () => updateHeader,
|
|
47
|
-
|
|
55
|
+
watcherLockPath: () => watcherLockPath,
|
|
56
|
+
wrapWords: () => wrapWords,
|
|
57
|
+
writePending: () => writePending
|
|
48
58
|
});
|
|
49
59
|
module.exports = __toCommonJS(index_exports);
|
|
50
|
-
var
|
|
51
|
-
var
|
|
60
|
+
var import_node_fs17 = require("node:fs");
|
|
61
|
+
var import_node_path14 = require("node:path");
|
|
52
62
|
|
|
53
63
|
// src/state.ts
|
|
54
64
|
var import_node_fs = require("node:fs");
|
|
@@ -382,7 +392,11 @@ function enumerateSurfaces(env = process.env) {
|
|
|
382
392
|
return [
|
|
383
393
|
{ id: "claude", marker: (0, import_node_path3.join)(home, ".claude", "plugins", "installed_plugins.json") },
|
|
384
394
|
{ id: "codex", marker: (0, import_node_path3.join)(codexHome, "plugins", "installed_plugins.json") },
|
|
385
|
-
|
|
395
|
+
// The host, not the payload (#4951, #5356): this marker used to be plugins/local/mmi — the MMI
|
|
396
|
+
// plugin tree itself — so a machine running Cursor WITHOUT the plugin enumerated as "no Cursor"
|
|
397
|
+
// and the arm could never make the first install. The plugin's absence is a converge action, not
|
|
398
|
+
// a skip. The host root mirrors surfaceConfigRoot('cursor') in cli/src/plugin-guard-io.ts.
|
|
399
|
+
{ id: "cursor", marker: (0, import_node_path3.join)(home, ".cursor") },
|
|
386
400
|
{ id: "kilo", marker: (0, import_node_path3.join)(home, ".config", "kilo") },
|
|
387
401
|
// The host, not the payload (#4951): this marker used to be plugins/managed/mmi — the MMI plugin
|
|
388
402
|
// itself — so a machine running Kimi WITHOUT the plugin enumerated as "no Kimi" and the arm
|
|
@@ -396,16 +410,160 @@ function enumerateSurfaces(env = process.env) {
|
|
|
396
410
|
}
|
|
397
411
|
|
|
398
412
|
// src/arm-cli.ts
|
|
399
|
-
var
|
|
413
|
+
var import_node_fs6 = require("node:fs");
|
|
414
|
+
var import_node_child_process4 = require("node:child_process");
|
|
415
|
+
var import_node_path5 = require("node:path");
|
|
416
|
+
|
|
417
|
+
// src/pending.ts
|
|
400
418
|
var import_node_child_process3 = require("node:child_process");
|
|
419
|
+
var import_node_fs5 = require("node:fs");
|
|
401
420
|
var import_node_path4 = require("node:path");
|
|
421
|
+
function hubDistPath(env) {
|
|
422
|
+
const root = globalRoot(env);
|
|
423
|
+
return root ? (0, import_node_path4.join)(root, "@mutmutco", "hub", "dist", "index.cjs") : null;
|
|
424
|
+
}
|
|
425
|
+
var WATCHER_MAX_WAIT_MS = 4 * 60 * 60 * 1e3;
|
|
426
|
+
function pendingPath(paths) {
|
|
427
|
+
return (0, import_node_path4.join)(paths.root, "pending.json");
|
|
428
|
+
}
|
|
429
|
+
function watcherLockPath(paths) {
|
|
430
|
+
return (0, import_node_path4.join)(paths.root, "pending-watcher.lock.d");
|
|
431
|
+
}
|
|
432
|
+
function readPending(paths) {
|
|
433
|
+
try {
|
|
434
|
+
const parsed = JSON.parse((0, import_node_fs5.readFileSync)(pendingPath(paths), "utf8"));
|
|
435
|
+
if (!Array.isArray(parsed)) return [];
|
|
436
|
+
return parsed.filter((row) => Boolean(row) && typeof row === "object" && typeof row.surface === "string" && typeof row.target === "string" && Array.isArray(row.images) && Array.isArray(row.dirs));
|
|
437
|
+
} catch {
|
|
438
|
+
return [];
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function writePending(paths, records) {
|
|
442
|
+
(0, import_node_fs5.mkdirSync)(paths.root, { recursive: true });
|
|
443
|
+
const path = pendingPath(paths);
|
|
444
|
+
const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
445
|
+
(0, import_node_fs5.writeFileSync)(tmp, `${JSON.stringify(records, null, 2)}
|
|
446
|
+
`, "utf8");
|
|
447
|
+
(0, import_node_fs5.renameSync)(tmp, path);
|
|
448
|
+
}
|
|
449
|
+
function clearPending(paths, surface) {
|
|
450
|
+
const records = readPending(paths);
|
|
451
|
+
if (!records.some((row) => row.surface === surface)) return;
|
|
452
|
+
writePending(paths, records.filter((row) => row.surface !== surface));
|
|
453
|
+
}
|
|
454
|
+
function pidAlive(pid) {
|
|
455
|
+
try {
|
|
456
|
+
process.kill(pid, 0);
|
|
457
|
+
return true;
|
|
458
|
+
} catch (error) {
|
|
459
|
+
return error?.code === "EPERM";
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function lockShaped(detail) {
|
|
463
|
+
return /EPERM|EBUSY|EACCES|locked|access is denied/i.test(detail);
|
|
464
|
+
}
|
|
465
|
+
function quotePs(value) {
|
|
466
|
+
return `'${value.replace(/'/gu, "''")}'`;
|
|
467
|
+
}
|
|
468
|
+
function quoteSh(value) {
|
|
469
|
+
return `'${value.replace(/'/gu, "'\\''")}'`;
|
|
470
|
+
}
|
|
471
|
+
function escapeEre(value) {
|
|
472
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
473
|
+
}
|
|
474
|
+
function buildWatcherCommand(opts) {
|
|
475
|
+
const platform = opts.platform ?? process.platform;
|
|
476
|
+
const maxWaitMs = opts.maxWaitMs ?? WATCHER_MAX_WAIT_MS;
|
|
477
|
+
const apply = [opts.node, opts.hubDist, "update"];
|
|
478
|
+
if (platform === "win32") {
|
|
479
|
+
const imageChecks2 = opts.images.map((image) => `if (Get-Process -Name ${quotePs(image)} -ErrorAction SilentlyContinue) { return $true }`);
|
|
480
|
+
const dirChecks2 = opts.dirs.map((dir) => `if (Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessId -ne $PID -and $_.CommandLine -and $_.CommandLine.IndexOf(${quotePs(dir)}, [System.StringComparison]::OrdinalIgnoreCase) -ge 0 }) { return $true }`);
|
|
481
|
+
const testBusy2 = `function Test-Busy { ${[...imageChecks2, ...dirChecks2].join("; ")}; return $false }`;
|
|
482
|
+
const wait2 = `$deadline = (Get-Date).AddMilliseconds(${maxWaitMs}); while ((Get-Date) -lt $deadline -and (Test-Busy)) { Start-Sleep -Seconds 5 }; if (Test-Busy) { exit 0 }`;
|
|
483
|
+
const lock2 = `$lock = ${quotePs(opts.lockPath)}; if (Test-Path $lock) { $owner = Get-Content "$lock\\pid" -ErrorAction SilentlyContinue; if ($owner -and -not (Get-Process -Id $owner -ErrorAction SilentlyContinue)) { Remove-Item $lock -Recurse -Force -ErrorAction SilentlyContinue } }`;
|
|
484
|
+
const run2 = `if (New-Item -ItemType Directory -Path $lock -ErrorAction SilentlyContinue) { $PID | Set-Content "$lock\\pid"; try { & ${apply.map(quotePs).join(" ")} *> ${quotePs(opts.logPath)}; Set-Content -Path ${quotePs(opts.codePath)} -Value $LASTEXITCODE } finally { Remove-Item $lock -Recurse -Force -ErrorAction SilentlyContinue } }`;
|
|
485
|
+
return {
|
|
486
|
+
file: "powershell",
|
|
487
|
+
args: ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", `${testBusy2}; ${wait2}; ${lock2}; ${run2}`]
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
const imageChecks = opts.images.map((image) => `pgrep -xi ${quoteSh(image)} >/dev/null 2>&1 && return 0`);
|
|
491
|
+
const dirChecks = opts.dirs.map((dir) => `[ -n "$(pgrep -f ${quoteSh(escapeEre(dir))} | grep -vx $$)" ] && return 0`);
|
|
492
|
+
const testBusy = `test_busy() { ${[...imageChecks, ...dirChecks].join("; ") || ":"}; return 1; }`;
|
|
493
|
+
const wait = `deadline=$(( $(date +%s) + ${Math.floor(maxWaitMs / 1e3)} )); while [ "$(date +%s)" -lt "$deadline" ] && test_busy; do sleep 5; done; test_busy && exit 0`;
|
|
494
|
+
const lock = `if [ -d ${quoteSh(opts.lockPath)} ]; then owner=$(cat ${quoteSh(`${opts.lockPath}/pid`)} 2>/dev/null); if [ -n "$owner" ] && ! kill -0 "$owner" 2>/dev/null; then rm -rf ${quoteSh(opts.lockPath)}; fi; fi`;
|
|
495
|
+
const run = `if mkdir ${quoteSh(opts.lockPath)} 2>/dev/null; then echo $$ > ${quoteSh(`${opts.lockPath}/pid`)}; trap "rm -rf ${quoteSh(opts.lockPath)}" EXIT; ${apply.map(quoteSh).join(" ")} > ${quoteSh(opts.logPath)} 2>&1; echo $? > ${quoteSh(opts.codePath)}; fi`;
|
|
496
|
+
return { file: "sh", args: ["-c", `${testBusy}; ${wait}; ${lock}; ${run}`] };
|
|
497
|
+
}
|
|
498
|
+
function defaultDetachSpawn(file, args) {
|
|
499
|
+
const child = (0, import_node_child_process3.spawn)(file, [...args], {
|
|
500
|
+
detached: process.platform !== "win32",
|
|
501
|
+
stdio: "ignore",
|
|
502
|
+
windowsHide: true,
|
|
503
|
+
shell: false
|
|
504
|
+
});
|
|
505
|
+
child.on("error", () => {
|
|
506
|
+
});
|
|
507
|
+
child.unref();
|
|
508
|
+
return child.pid ?? null;
|
|
509
|
+
}
|
|
510
|
+
function schedulePendingWatcher(paths, env = process.env, seam = {}) {
|
|
511
|
+
const records = readPending(paths);
|
|
512
|
+
if (!records.length) return { scheduled: false, reason: "no pending applies" };
|
|
513
|
+
const live = records.find((row) => row.watcherPid && pidAlive(row.watcherPid));
|
|
514
|
+
if (live) return { scheduled: false, reason: `watcher pid ${live.watcherPid} is already waiting` };
|
|
515
|
+
const hubDist = seam.hubDist ?? hubDistPath(env);
|
|
516
|
+
if (!hubDist || !(0, import_node_fs5.existsSync)(hubDist)) return { scheduled: false, reason: "the installed hub bundle is unresolvable \u2014 the hourly tick remains the retry" };
|
|
517
|
+
const command = buildWatcherCommand({
|
|
518
|
+
images: [...new Set(records.flatMap((row) => row.images))],
|
|
519
|
+
dirs: [...new Set(records.flatMap((row) => row.dirs))],
|
|
520
|
+
node: process.execPath,
|
|
521
|
+
hubDist,
|
|
522
|
+
codePath: (0, import_node_path4.join)(paths.root, "pending-result.code"),
|
|
523
|
+
logPath: (0, import_node_path4.join)(paths.root, "pending-result.log"),
|
|
524
|
+
lockPath: watcherLockPath(paths)
|
|
525
|
+
});
|
|
526
|
+
try {
|
|
527
|
+
const pid = (seam.detachSpawn ?? defaultDetachSpawn)(command.file, command.args);
|
|
528
|
+
if (!pid) return { scheduled: false, reason: "watcher spawn returned no pid" };
|
|
529
|
+
writePending(paths, records.map((row) => ({ ...row, watcherPid: pid })));
|
|
530
|
+
return { scheduled: true, pid };
|
|
531
|
+
} catch (error) {
|
|
532
|
+
return { scheduled: false, reason: String(error?.message ?? error) };
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function noteBusyDefer(paths, env, entry, seam = {}) {
|
|
536
|
+
try {
|
|
537
|
+
const records = readPending(paths);
|
|
538
|
+
const existing = records.find((row) => row.surface === entry.surface);
|
|
539
|
+
const next = {
|
|
540
|
+
surface: entry.surface,
|
|
541
|
+
target: entry.target,
|
|
542
|
+
since: existing?.since ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
543
|
+
reason: entry.reason,
|
|
544
|
+
images: entry.images ?? [],
|
|
545
|
+
dirs: entry.dirs ?? [],
|
|
546
|
+
watcherPid: existing?.watcherPid
|
|
547
|
+
};
|
|
548
|
+
writePending(paths, [...records.filter((row) => row.surface !== entry.surface), next]);
|
|
549
|
+
const scheduled = schedulePendingWatcher(paths, env, seam);
|
|
550
|
+
return scheduled.scheduled ? `applies when the host exits (post-exit watcher pid ${scheduled.pid})` : `applies on a quiet tick (${scheduled.reason})`;
|
|
551
|
+
} catch (error) {
|
|
552
|
+
return `applies on a quiet tick (pending record failed: ${String(error?.message ?? error).slice(0, 80)})`;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
function resumePendingWatcher(paths, env = process.env) {
|
|
556
|
+
return schedulePendingWatcher(paths, env);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// src/arm-cli.ts
|
|
402
560
|
function globalCliDist(env) {
|
|
403
561
|
const root = globalRoot(env);
|
|
404
|
-
return root ? (0,
|
|
562
|
+
return root ? (0, import_node_path5.join)(root, "@mutmutco", "cli", "dist", "index.cjs") : null;
|
|
405
563
|
}
|
|
406
564
|
function probeCliVersion(distPath) {
|
|
407
565
|
if (!fileExists(distPath)) return null;
|
|
408
|
-
const probe = (0,
|
|
566
|
+
const probe = (0, import_node_child_process4.spawnSync)(process.execPath, [distPath, "--version"], { encoding: "utf8", windowsHide: true });
|
|
409
567
|
const version = (probe.stdout || "").trim();
|
|
410
568
|
return probe.status === 0 && /^\d+\.\d+\.\d+/.test(version) ? version : null;
|
|
411
569
|
}
|
|
@@ -432,11 +590,11 @@ function cliArm(options) {
|
|
|
432
590
|
if (dryRun) {
|
|
433
591
|
return { surface: "cli", from: installed, to: target, verdict: "ok", detail: `dry-run: would converge ${installed ?? "absent"} -> ${target}` };
|
|
434
592
|
}
|
|
435
|
-
const stageRoot = (0,
|
|
436
|
-
const stageDist = (0,
|
|
593
|
+
const stageRoot = (0, import_node_path5.join)(paths.staging, "cli", target);
|
|
594
|
+
const stageDist = (0, import_node_path5.join)(stageRoot, "node_modules", "@mutmutco", "cli", "dist", "index.cjs");
|
|
437
595
|
if (!fileExists(stageDist)) {
|
|
438
|
-
(0,
|
|
439
|
-
(0,
|
|
596
|
+
(0, import_node_fs6.mkdirSync)(stageRoot, { recursive: true });
|
|
597
|
+
(0, import_node_fs6.writeFileSync)((0, import_node_path5.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
|
|
440
598
|
const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `@mutmutco/cli@${target}`], env);
|
|
441
599
|
if (install.status !== 0) {
|
|
442
600
|
return { surface: "cli", from: installed, to: target, verdict: "defer", detail: `staging install failed: ${(install.stderr || install.stdout).split("\n").filter(Boolean).slice(-3).join(" | ")}` };
|
|
@@ -444,7 +602,7 @@ function cliArm(options) {
|
|
|
444
602
|
}
|
|
445
603
|
let stagedManifestVersion;
|
|
446
604
|
try {
|
|
447
|
-
stagedManifestVersion = JSON.parse((0,
|
|
605
|
+
stagedManifestVersion = JSON.parse((0, import_node_fs6.readFileSync)((0, import_node_path5.join)(stageRoot, "node_modules", "@mutmutco", "cli", "package.json"), "utf8")).version;
|
|
448
606
|
} catch {
|
|
449
607
|
}
|
|
450
608
|
if (stagedManifestVersion !== target) {
|
|
@@ -456,7 +614,10 @@ function cliArm(options) {
|
|
|
456
614
|
}
|
|
457
615
|
const globalInstall = runNpm(["install", "-g", "--no-audit", "--no-fund", `@mutmutco/cli@${target}`], env);
|
|
458
616
|
if (globalInstall.status !== 0) {
|
|
459
|
-
|
|
617
|
+
const reason = `global install failed: ${(globalInstall.stderr || globalInstall.stdout).split("\n").filter(Boolean).slice(-3).join(" | ")}`;
|
|
618
|
+
const root = globalRoot(env);
|
|
619
|
+
const pending = lockShaped(reason) && root ? ` \u2014 ${noteBusyDefer(paths, env, { surface: "cli", target, reason, dirs: [root] })}` : "";
|
|
620
|
+
return { surface: "cli", from: installed, to: target, verdict: "defer", detail: `${reason}${pending}` };
|
|
460
621
|
}
|
|
461
622
|
const switchedDist = globalCliDist(env);
|
|
462
623
|
const switched = probeCliVersion(switchedDist ?? "");
|
|
@@ -467,16 +628,16 @@ function cliArm(options) {
|
|
|
467
628
|
}
|
|
468
629
|
|
|
469
630
|
// src/arm-npm-global.ts
|
|
470
|
-
var
|
|
471
|
-
var
|
|
472
|
-
var
|
|
631
|
+
var import_node_fs7 = require("node:fs");
|
|
632
|
+
var import_node_child_process5 = require("node:child_process");
|
|
633
|
+
var import_node_path6 = require("node:path");
|
|
473
634
|
function globalDist(env, packageName, distRel) {
|
|
474
635
|
const root = globalRoot(env);
|
|
475
|
-
return root ? (0,
|
|
636
|
+
return root ? (0, import_node_path6.join)(root, ...packageName.split("/"), ...distRel.split("/")) : null;
|
|
476
637
|
}
|
|
477
638
|
function probeVersion(distPath) {
|
|
478
639
|
if (!fileExists(distPath)) return null;
|
|
479
|
-
const probe = (0,
|
|
640
|
+
const probe = (0, import_node_child_process5.spawnSync)(process.execPath, [distPath, "--version"], { encoding: "utf8", windowsHide: true });
|
|
480
641
|
const version = (probe.stdout || "").trim();
|
|
481
642
|
return probe.status === 0 && /^\d+\.\d+\.\d+/.test(version) ? version : null;
|
|
482
643
|
}
|
|
@@ -485,11 +646,11 @@ function tail(output) {
|
|
|
485
646
|
}
|
|
486
647
|
function stagePackage(options) {
|
|
487
648
|
const { surface, packageName, target, witness, paths, env } = options;
|
|
488
|
-
const stageRoot = (0,
|
|
489
|
-
const packageRoot = (0,
|
|
490
|
-
if (!fileExists((0,
|
|
491
|
-
(0,
|
|
492
|
-
(0,
|
|
649
|
+
const stageRoot = (0, import_node_path6.join)(paths.staging, surface, target);
|
|
650
|
+
const packageRoot = (0, import_node_path6.join)(stageRoot, "node_modules", ...packageName.split("/"));
|
|
651
|
+
if (!fileExists((0, import_node_path6.join)(packageRoot, ...witness.split("/")))) {
|
|
652
|
+
(0, import_node_fs7.mkdirSync)(stageRoot, { recursive: true });
|
|
653
|
+
(0, import_node_fs7.writeFileSync)((0, import_node_path6.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
|
|
493
654
|
const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `${packageName}@${target}`], env);
|
|
494
655
|
if (install.status !== 0) {
|
|
495
656
|
return { defer: `staging install failed: ${tail(install.stderr || install.stdout)}` };
|
|
@@ -497,7 +658,7 @@ function stagePackage(options) {
|
|
|
497
658
|
}
|
|
498
659
|
let stagedManifestVersion;
|
|
499
660
|
try {
|
|
500
|
-
stagedManifestVersion = JSON.parse((0,
|
|
661
|
+
stagedManifestVersion = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path6.join)(packageRoot, "package.json"), "utf8")).version;
|
|
501
662
|
} catch {
|
|
502
663
|
}
|
|
503
664
|
if (stagedManifestVersion !== target) {
|
|
@@ -529,14 +690,17 @@ function npmGlobalArm(options) {
|
|
|
529
690
|
if ("defer" in staged) {
|
|
530
691
|
return result(installed, target, "defer", staged.defer);
|
|
531
692
|
}
|
|
532
|
-
const stageDist = (0,
|
|
693
|
+
const stageDist = (0, import_node_path6.join)(staged.packageRoot, ...distRel.split("/"));
|
|
533
694
|
const stagedVersion = probeVersion(stageDist);
|
|
534
695
|
if (stagedVersion !== target) {
|
|
535
696
|
return result(installed, target, "defer", `staged binary probed ${stagedVersion ?? "dead"}, expected ${target}`);
|
|
536
697
|
}
|
|
537
698
|
const globalInstall = runNpm(["install", "-g", "--no-audit", "--no-fund", spec], env);
|
|
538
699
|
if (globalInstall.status !== 0) {
|
|
539
|
-
|
|
700
|
+
const reason = `global install failed: ${tail(globalInstall.stderr || globalInstall.stdout)}`;
|
|
701
|
+
const root = globalRoot(env);
|
|
702
|
+
const pending = lockShaped(reason) && root ? ` \u2014 ${noteBusyDefer(paths, env, { surface, target, reason, dirs: [root] })}` : "";
|
|
703
|
+
return result(installed, target, "defer", `${reason}${pending}`);
|
|
540
704
|
}
|
|
541
705
|
const switchedDist = globalDist(env, packageName, distRel);
|
|
542
706
|
const switched = probeVersion(switchedDist ?? "");
|
|
@@ -557,10 +721,10 @@ function selfArm(options) {
|
|
|
557
721
|
}
|
|
558
722
|
|
|
559
723
|
// src/arm-host-plugins.ts
|
|
560
|
-
var
|
|
561
|
-
var
|
|
724
|
+
var import_node_child_process6 = require("node:child_process");
|
|
725
|
+
var import_node_fs8 = require("node:fs");
|
|
562
726
|
var import_node_os3 = require("node:os");
|
|
563
|
-
var
|
|
727
|
+
var import_node_path7 = require("node:path");
|
|
564
728
|
var REPO_URL_DEFAULT = "https://github.com/mutmutco/MMI-Hub.git";
|
|
565
729
|
var MMI_MARKETPLACE_REPO = "mutmutco/MMI-Hub";
|
|
566
730
|
function convergedAtOrAbove(installed, target, healthy) {
|
|
@@ -570,13 +734,13 @@ function cmdArg(value) {
|
|
|
570
734
|
return /^[A-Za-z0-9_@./:\\=+-]+$/.test(value) ? value : `"${value.replaceAll('"', '""')}"`;
|
|
571
735
|
}
|
|
572
736
|
var runHostCommand = (command, args, env) => {
|
|
573
|
-
const result = process.platform === "win32" ? (0,
|
|
737
|
+
const result = process.platform === "win32" ? (0, import_node_child_process6.spawnSync)("cmd.exe", ["/d", "/s", "/c", [command, ...args].map(cmdArg).join(" ")], {
|
|
574
738
|
encoding: "utf8",
|
|
575
739
|
env,
|
|
576
740
|
input: "",
|
|
577
741
|
timeout: 12e4,
|
|
578
742
|
windowsHide: true
|
|
579
|
-
}) : (0,
|
|
743
|
+
}) : (0, import_node_child_process6.spawnSync)(command, args, {
|
|
580
744
|
encoding: "utf8",
|
|
581
745
|
env,
|
|
582
746
|
input: "",
|
|
@@ -603,7 +767,7 @@ function claudeMmi(runner, env) {
|
|
|
603
767
|
return { row: parsed.find((row) => row.id === "mmi@mutmutco") ?? null };
|
|
604
768
|
}
|
|
605
769
|
function claudeMarketplacesPath(env) {
|
|
606
|
-
return env.MMI_UPDATER_CLAUDE_MARKETPLACES || (0,
|
|
770
|
+
return env.MMI_UPDATER_CLAUDE_MARKETPLACES || (0, import_node_path7.join)((0, import_node_os3.homedir)(), ".claude", "plugins", "known_marketplaces.json");
|
|
607
771
|
}
|
|
608
772
|
var CLAUDE_PROCESS_SEGMENT = /^claude(-code|-cli)?(\.(exe|cmd|bat|ps1|js|mjs|cjs|py))?$/;
|
|
609
773
|
function claudeProcessSegment(line) {
|
|
@@ -617,7 +781,7 @@ function claudeProcessSegment(line) {
|
|
|
617
781
|
function claudeHostRunning(env) {
|
|
618
782
|
const marker = env.CLAUDE_CODE_SESSION_ID?.trim() ? "CLAUDE_CODE_SESSION_ID" : env.CLAUDECODE?.trim() ? "CLAUDECODE" : env.CLAUDE_PLUGIN_ROOT?.trim() ? "CLAUDE_PLUGIN_ROOT" : null;
|
|
619
783
|
if (marker) return { running: true, evidence: `${marker} is set \u2014 this process is itself inside a Claude hook` };
|
|
620
|
-
const result = process.platform === "win32" ? (0,
|
|
784
|
+
const result = process.platform === "win32" ? (0, import_node_child_process6.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.Name + ' ' + $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process6.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
|
|
621
785
|
if (result.status !== 0 || !(result.stdout ?? "").trim()) return { running: true, evidence: "the process list is unreadable \u2014 unsafe to write" };
|
|
622
786
|
for (const line of result.stdout.split(/\r?\n/)) {
|
|
623
787
|
const segment = claudeProcessSegment(line);
|
|
@@ -629,7 +793,7 @@ function ensureClaudeSingleWriter(env, dryRun) {
|
|
|
629
793
|
const path = claudeMarketplacesPath(env);
|
|
630
794
|
let body;
|
|
631
795
|
try {
|
|
632
|
-
body = JSON.parse((0,
|
|
796
|
+
body = JSON.parse((0, import_node_fs8.readFileSync)(path, "utf8"));
|
|
633
797
|
} catch (error) {
|
|
634
798
|
if (error?.code === "ENOENT") return { ok: false, detail: `${path} does not exist \u2014 mutmutco marketplace is not registered`, unregistered: true };
|
|
635
799
|
return { ok: false, detail: `cannot read ${path}` };
|
|
@@ -641,7 +805,10 @@ function ensureClaudeSingleWriter(env, dryRun) {
|
|
|
641
805
|
if (registeredRepo !== MMI_MARKETPLACE_REPO) {
|
|
642
806
|
return {
|
|
643
807
|
ok: false,
|
|
644
|
-
|
|
808
|
+
// Remedy must use the github shorthand (#5369): `marketplace add <url>` registers
|
|
809
|
+
// source:'git', which fails this same github-provenance check forever; `add owner/repo`
|
|
810
|
+
// registers source:'github' and passes.
|
|
811
|
+
detail: `mutmutco marketplace is registered to ${registeredRepo ?? "a non-github source"}, not ${MMI_MARKETPLACE_REPO} \u2014 a retired or foreign repo is squatting the name; re-register it (claude plugin marketplace remove mutmutco && claude plugin marketplace add ${MMI_MARKETPLACE_REPO}) before the updater can trust it`
|
|
645
812
|
};
|
|
646
813
|
}
|
|
647
814
|
if (registration.autoUpdate !== true) return { ok: true };
|
|
@@ -653,9 +820,9 @@ function ensureClaudeSingleWriter(env, dryRun) {
|
|
|
653
820
|
registration.autoUpdate = false;
|
|
654
821
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
655
822
|
try {
|
|
656
|
-
(0,
|
|
657
|
-
(0,
|
|
658
|
-
const reread = JSON.parse((0,
|
|
823
|
+
(0, import_node_fs8.writeFileSync)(tmp, JSON.stringify(body, null, 2) + "\n", "utf8");
|
|
824
|
+
(0, import_node_fs8.renameSync)(tmp, path);
|
|
825
|
+
const reread = JSON.parse((0, import_node_fs8.readFileSync)(path, "utf8"));
|
|
659
826
|
return reread?.mutmutco?.autoUpdate === false ? { ok: true, detail: "disabled Claude background autoUpdate (updater is the single writer)" } : { ok: false, detail: "Claude background autoUpdate write did not persist" };
|
|
660
827
|
} catch (error) {
|
|
661
828
|
return { ok: false, detail: `cannot disable Claude background autoUpdate: ${error?.message ?? error}` };
|
|
@@ -769,11 +936,11 @@ function codexArm(options) {
|
|
|
769
936
|
}
|
|
770
937
|
var KILO_PACKAGE = "@mutmutco/kilo-plugin";
|
|
771
938
|
function kiloConfigPath(env) {
|
|
772
|
-
return env.MMI_UPDATER_KILO_CONFIG || (0,
|
|
939
|
+
return env.MMI_UPDATER_KILO_CONFIG || (0, import_node_path7.join)((0, import_node_os3.homedir)(), ".config", "kilo", "opencode.json");
|
|
773
940
|
}
|
|
774
941
|
function readKiloConfig(env) {
|
|
775
942
|
try {
|
|
776
|
-
const config = JSON.parse((0,
|
|
943
|
+
const config = JSON.parse((0, import_node_fs8.readFileSync)(kiloConfigPath(env), "utf8"));
|
|
777
944
|
const specs = Array.isArray(config.plugin) && config.plugin.every((entry) => typeof entry === "string") ? config.plugin : [];
|
|
778
945
|
return { config, specs };
|
|
779
946
|
} catch {
|
|
@@ -793,8 +960,8 @@ function normalizeKiloConfig(env, exact) {
|
|
|
793
960
|
const path = kiloConfigPath(env);
|
|
794
961
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
795
962
|
try {
|
|
796
|
-
(0,
|
|
797
|
-
(0,
|
|
963
|
+
(0, import_node_fs8.writeFileSync)(tmp, JSON.stringify({ ...current.config, plugin: next }, null, 2) + "\n", "utf8");
|
|
964
|
+
(0, import_node_fs8.renameSync)(tmp, path);
|
|
798
965
|
return null;
|
|
799
966
|
} catch (error) {
|
|
800
967
|
return `cannot retire legacy Kilo registrations: ${error?.message ?? error}`;
|
|
@@ -857,15 +1024,15 @@ function kiloArm(options) {
|
|
|
857
1024
|
}
|
|
858
1025
|
|
|
859
1026
|
// src/arm-kimi.ts
|
|
860
|
-
var
|
|
861
|
-
var
|
|
862
|
-
var
|
|
1027
|
+
var import_node_child_process7 = require("node:child_process");
|
|
1028
|
+
var import_node_fs10 = require("node:fs");
|
|
1029
|
+
var import_node_path8 = require("node:path");
|
|
863
1030
|
|
|
864
1031
|
// src/compat.ts
|
|
865
|
-
var
|
|
1032
|
+
var import_node_fs9 = require("node:fs");
|
|
866
1033
|
function readManifestCompat(manifestPath) {
|
|
867
1034
|
try {
|
|
868
|
-
const parsed = JSON.parse((0,
|
|
1035
|
+
const parsed = JSON.parse((0, import_node_fs9.readFileSync)(manifestPath, "utf8"));
|
|
869
1036
|
return typeof parsed.mmiCompat === "string" ? parsed.mmiCompat : void 0;
|
|
870
1037
|
} catch {
|
|
871
1038
|
return void 0;
|
|
@@ -876,11 +1043,11 @@ function readManifestCompat(manifestPath) {
|
|
|
876
1043
|
var KIMI_PACKAGE = "@mutmutco/kimi-plugin";
|
|
877
1044
|
var TREE_MARKERS = [".kimi-plugin/plugin.json", "skills/mmi/SKILL.md", "scripts/hook-run.mjs"];
|
|
878
1045
|
function treeHealthy(root) {
|
|
879
|
-
return TREE_MARKERS.every((rel) => fileExists((0,
|
|
1046
|
+
return TREE_MARKERS.every((rel) => fileExists((0, import_node_path8.join)(root, ...rel.split("/"))));
|
|
880
1047
|
}
|
|
881
1048
|
function markerVersion(root) {
|
|
882
1049
|
try {
|
|
883
|
-
const version = JSON.parse((0,
|
|
1050
|
+
const version = JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path8.join)(root, ".kimi-plugin", "plugin.json"), "utf8")).version;
|
|
884
1051
|
return typeof version === "string" ? version : null;
|
|
885
1052
|
} catch {
|
|
886
1053
|
return null;
|
|
@@ -888,14 +1055,14 @@ function markerVersion(root) {
|
|
|
888
1055
|
}
|
|
889
1056
|
function packageVersion(root) {
|
|
890
1057
|
try {
|
|
891
|
-
const version = JSON.parse((0,
|
|
1058
|
+
const version = JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path8.join)(root, "package.json"), "utf8")).version;
|
|
892
1059
|
return typeof version === "string" ? version : null;
|
|
893
1060
|
} catch {
|
|
894
1061
|
return null;
|
|
895
1062
|
}
|
|
896
1063
|
}
|
|
897
1064
|
function probeKimiInstallation(env = process.env) {
|
|
898
|
-
const location = (0,
|
|
1065
|
+
const location = (0, import_node_path8.join)(kimiHome(env), "plugins", "managed", "mmi");
|
|
899
1066
|
const version = markerVersion(location);
|
|
900
1067
|
return { version, location, detail: version ? "installed Kimi plugin manifest" : "installed Kimi plugin manifest was unreadable" };
|
|
901
1068
|
}
|
|
@@ -903,14 +1070,14 @@ function tail2(output) {
|
|
|
903
1070
|
return output.split("\n").filter(Boolean).slice(-3).join(" | ");
|
|
904
1071
|
}
|
|
905
1072
|
function scratchRoot(env) {
|
|
906
|
-
return (0,
|
|
1073
|
+
return (0, import_node_path8.join)(kimiHome(env), ".mmi-updater");
|
|
907
1074
|
}
|
|
908
1075
|
function pruneScratchDir(root, keep) {
|
|
909
1076
|
try {
|
|
910
|
-
for (const name of (0,
|
|
1077
|
+
for (const name of (0, import_node_fs10.readdirSync)(root)) {
|
|
911
1078
|
if (name === keep) continue;
|
|
912
1079
|
try {
|
|
913
|
-
(0,
|
|
1080
|
+
(0, import_node_fs10.rmSync)((0, import_node_path8.join)(root, name), { recursive: true, force: true });
|
|
914
1081
|
} catch {
|
|
915
1082
|
}
|
|
916
1083
|
}
|
|
@@ -926,7 +1093,7 @@ function kimiProcessToken(line) {
|
|
|
926
1093
|
}
|
|
927
1094
|
function kimiHostRunning(env) {
|
|
928
1095
|
if (env.KIMI_PLUGIN_ROOT?.trim()) return { running: true, evidence: "KIMI_PLUGIN_ROOT is set \u2014 this process is itself inside a Kimi hook" };
|
|
929
|
-
const result = process.platform === "win32" ? (0,
|
|
1096
|
+
const result = process.platform === "win32" ? (0, import_node_child_process7.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.Name + ' ' + $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process7.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
|
|
930
1097
|
if (result.status !== 0 || !(result.stdout ?? "").trim()) return { running: true, evidence: "the process list is unreadable \u2014 unsafe to write" };
|
|
931
1098
|
for (const line of result.stdout.split(/\r?\n/)) {
|
|
932
1099
|
const token = kimiProcessToken(line);
|
|
@@ -936,11 +1103,11 @@ function kimiHostRunning(env) {
|
|
|
936
1103
|
}
|
|
937
1104
|
function restoreQuarantine(quarantine, live) {
|
|
938
1105
|
try {
|
|
939
|
-
(0,
|
|
1106
|
+
(0, import_node_fs10.renameSync)(quarantine, live);
|
|
940
1107
|
} catch (error) {
|
|
941
1108
|
return { restored: false, detail: `${error?.message ?? error}` };
|
|
942
1109
|
}
|
|
943
|
-
if (!(0,
|
|
1110
|
+
if (!(0, import_node_fs10.existsSync)(live)) return { restored: false, detail: `${live} is still absent after the restore rename` };
|
|
944
1111
|
return { restored: true, detail: `marker ${markerVersion(live) ?? "unreadable"}` };
|
|
945
1112
|
}
|
|
946
1113
|
function kimiArm(options) {
|
|
@@ -954,10 +1121,10 @@ function kimiArm(options) {
|
|
|
954
1121
|
detail,
|
|
955
1122
|
// C11: the constraint of the tree LEFT at `live` — read at result time, so ok carries the new
|
|
956
1123
|
// manifest's declaration and defer/fail carry the previous tree's (#4973 reconsult ruling).
|
|
957
|
-
compat: readManifestCompat((0,
|
|
1124
|
+
compat: readManifestCompat((0, import_node_path8.join)(live, ".kimi-plugin", "plugin.json"))
|
|
958
1125
|
});
|
|
959
|
-
const managedParent = (0,
|
|
960
|
-
const live = (0,
|
|
1126
|
+
const managedParent = (0, import_node_path8.join)(kimiHome(env), "plugins", "managed");
|
|
1127
|
+
const live = (0, import_node_path8.join)(managedParent, "mmi");
|
|
961
1128
|
const installed = markerVersion(live);
|
|
962
1129
|
if (installed && compareSemver(installed, target) > 0) {
|
|
963
1130
|
return result(installed, installed, "skip", `installed ${installed} is above candidate ${target} (monotonic)`);
|
|
@@ -972,11 +1139,11 @@ function kimiArm(options) {
|
|
|
972
1139
|
if (dryRun) {
|
|
973
1140
|
return result(installed, target, "ok", `dry-run: would converge ${installed ?? "absent"} -> ${target} (tarball generation switch while Kimi is absent)`);
|
|
974
1141
|
}
|
|
975
|
-
const stageRoot = (0,
|
|
976
|
-
const staged = (0,
|
|
1142
|
+
const stageRoot = (0, import_node_path8.join)(paths.staging, "kimi", target);
|
|
1143
|
+
const staged = (0, import_node_path8.join)(stageRoot, "node_modules", ...KIMI_PACKAGE.split("/"));
|
|
977
1144
|
if (!treeHealthy(staged)) {
|
|
978
|
-
(0,
|
|
979
|
-
(0,
|
|
1145
|
+
(0, import_node_fs10.mkdirSync)(stageRoot, { recursive: true });
|
|
1146
|
+
(0, import_node_fs10.writeFileSync)((0, import_node_path8.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
|
|
980
1147
|
const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `${KIMI_PACKAGE}@${target}`], env);
|
|
981
1148
|
if (install.status !== 0) {
|
|
982
1149
|
return result(installed, target, "defer", `staging install failed: ${tail2(install.stderr || install.stdout)}`);
|
|
@@ -995,75 +1162,79 @@ function kimiArm(options) {
|
|
|
995
1162
|
}
|
|
996
1163
|
const quiescence = kimiHostRunning(env);
|
|
997
1164
|
if (quiescence.running) {
|
|
998
|
-
|
|
1165
|
+
const pending = options.dryRun ? "" : ` ${noteBusyDefer(options.paths, env, { surface: "kimi", target, reason: quiescence.evidence, dirs: ["kimi"] })}`;
|
|
1166
|
+
return result(installed, target, "defer", `Kimi may be running \u2014 the managed tree is switched only while the host is absent: ${quiescence.evidence}${pending ? ` \u2014${pending}` : ""}`);
|
|
999
1167
|
}
|
|
1000
1168
|
const scratch = scratchRoot(env);
|
|
1001
|
-
const incomingRoot = (0,
|
|
1002
|
-
const incoming = (0,
|
|
1003
|
-
const quarantineRoot = (0,
|
|
1004
|
-
const rejectedRoot = (0,
|
|
1169
|
+
const incomingRoot = (0, import_node_path8.join)(scratch, "incoming");
|
|
1170
|
+
const incoming = (0, import_node_path8.join)(incomingRoot, target);
|
|
1171
|
+
const quarantineRoot = (0, import_node_path8.join)(scratch, "quarantine");
|
|
1172
|
+
const rejectedRoot = (0, import_node_path8.join)(scratch, "rejected");
|
|
1005
1173
|
try {
|
|
1006
|
-
(0,
|
|
1007
|
-
(0,
|
|
1008
|
-
(0,
|
|
1009
|
-
(0,
|
|
1174
|
+
(0, import_node_fs10.mkdirSync)(managedParent, { recursive: true });
|
|
1175
|
+
(0, import_node_fs10.mkdirSync)(quarantineRoot, { recursive: true });
|
|
1176
|
+
(0, import_node_fs10.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
1177
|
+
(0, import_node_fs10.cpSync)(staged, incoming, { recursive: true });
|
|
1010
1178
|
} catch (error) {
|
|
1011
1179
|
return result(installed, target, "defer", `cannot place the incoming generation under ${scratch}: ${error?.message ?? error}`);
|
|
1012
1180
|
}
|
|
1013
1181
|
if (markerVersion(incoming) !== target || !treeHealthy(incoming)) {
|
|
1014
1182
|
return result(installed, target, "defer", `incoming generation verifies as ${markerVersion(incoming) ?? "unreadable"}, expected ${target}`);
|
|
1015
1183
|
}
|
|
1016
|
-
const hadLive = (0,
|
|
1184
|
+
const hadLive = (0, import_node_fs10.existsSync)(live);
|
|
1017
1185
|
const generation = `${installed ?? "unknown"}-${Date.now()}`;
|
|
1018
|
-
const quarantine = (0,
|
|
1186
|
+
const quarantine = (0, import_node_path8.join)(quarantineRoot, generation);
|
|
1019
1187
|
if (hadLive) {
|
|
1020
1188
|
try {
|
|
1021
|
-
(0,
|
|
1189
|
+
(0, import_node_fs10.renameSync)(live, quarantine);
|
|
1022
1190
|
} catch (error) {
|
|
1023
|
-
|
|
1191
|
+
const reason = String(error?.message ?? error);
|
|
1192
|
+
const pending = lockShaped(reason) ? ` \u2014 ${noteBusyDefer(options.paths, env, { surface: "kimi", target, reason, dirs: ["kimi"] })}` : "";
|
|
1193
|
+
return result(installed, target, "defer", `cannot quarantine the live generation: ${reason}${pending}`);
|
|
1024
1194
|
}
|
|
1025
1195
|
}
|
|
1026
1196
|
try {
|
|
1027
|
-
(0,
|
|
1197
|
+
(0, import_node_fs10.renameSync)(incoming, live);
|
|
1028
1198
|
} catch (error) {
|
|
1029
1199
|
const failed = `cannot switch the new generation in: ${error?.message ?? error}`;
|
|
1030
1200
|
if (!hadLive) {
|
|
1031
1201
|
return result(installed, target, "defer", `${failed} \u2014 no generation was displaced; ${live} is still absent`);
|
|
1032
1202
|
}
|
|
1033
1203
|
const restore = restoreQuarantine(quarantine, live);
|
|
1034
|
-
|
|
1204
|
+
const pending = restore.restored && lockShaped(failed) ? ` \u2014 ${noteBusyDefer(options.paths, env, { surface: "kimi", target, reason: failed, dirs: ["kimi"] })}` : "";
|
|
1205
|
+
return restore.restored ? result(installed, installed ?? "unknown", "defer", `${failed} \u2014 the previous generation is back in place (${restore.detail})${pending}`) : result(installed, "none", "fail", `${failed}; ROLLBACK FAILED (${restore.detail}) \u2014 ${live} now has NO plugin and Kimi enforces nothing until this is repaired; the known-good generation is intact at ${quarantine}`);
|
|
1035
1206
|
}
|
|
1036
1207
|
const switched = markerVersion(live);
|
|
1037
1208
|
if (switched !== target || !treeHealthy(live)) {
|
|
1038
1209
|
const observed = `post-switch marker is ${switched ?? "unreadable"}, expected ${target}`;
|
|
1039
1210
|
const rejectedName = `${target}-${Date.now()}`;
|
|
1040
1211
|
try {
|
|
1041
|
-
(0,
|
|
1042
|
-
(0,
|
|
1212
|
+
(0, import_node_fs10.mkdirSync)(rejectedRoot, { recursive: true });
|
|
1213
|
+
(0, import_node_fs10.renameSync)(live, (0, import_node_path8.join)(rejectedRoot, rejectedName));
|
|
1043
1214
|
} catch (error) {
|
|
1044
1215
|
return result(installed, switched ?? "unknown", "fail", `${observed} \u2014 the unverified tree could NOT be moved aside (${error?.message ?? error}) and is STILL LIVE at ${live}${hadLive ? `; the previous generation is intact at ${quarantine}` : ""}`);
|
|
1045
1216
|
}
|
|
1046
1217
|
pruneScratchDir(rejectedRoot, rejectedName);
|
|
1047
1218
|
if (!hadLive) {
|
|
1048
|
-
return result(installed, "none", "fail", `${observed} \u2014 the unverified tree is parked at ${(0,
|
|
1219
|
+
return result(installed, "none", "fail", `${observed} \u2014 the unverified tree is parked at ${(0, import_node_path8.join)(rejectedRoot, rejectedName)}; ${live} is absent (there was no previous generation to restore)`);
|
|
1049
1220
|
}
|
|
1050
1221
|
const restore = restoreQuarantine(quarantine, live);
|
|
1051
|
-
return restore.restored ? result(installed, installed ?? "unknown", "fail", `${observed} \u2014 the unverified tree is parked at ${(0,
|
|
1222
|
+
return restore.restored ? result(installed, installed ?? "unknown", "fail", `${observed} \u2014 the unverified tree is parked at ${(0, import_node_path8.join)(rejectedRoot, rejectedName)}; rolled back to ${installed ?? "the previous generation"} and read back (${restore.detail})`) : result(installed, "none", "fail", `${observed}; ROLLBACK FAILED (${restore.detail}) \u2014 ${live} now has NO plugin and Kimi enforces nothing until this is repaired; the known-good generation is intact at ${quarantine}`);
|
|
1052
1223
|
}
|
|
1053
1224
|
pruneScratchDir(quarantineRoot, hadLive ? generation : null);
|
|
1054
1225
|
pruneScratchDir(rejectedRoot, null);
|
|
1055
1226
|
try {
|
|
1056
|
-
(0,
|
|
1227
|
+
(0, import_node_fs10.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
1057
1228
|
} catch {
|
|
1058
1229
|
}
|
|
1059
1230
|
return result(installed, target, "ok", `converged ${installed ?? "absent"} -> ${target} (staged, verified, switched${hadLive ? `; previous generation quarantined at ${quarantine}` : ""})`);
|
|
1060
1231
|
}
|
|
1061
1232
|
|
|
1062
1233
|
// src/arm-cursor.ts
|
|
1063
|
-
var
|
|
1064
|
-
var
|
|
1234
|
+
var import_node_fs11 = require("node:fs");
|
|
1235
|
+
var import_node_child_process8 = require("node:child_process");
|
|
1065
1236
|
var import_node_os4 = require("node:os");
|
|
1066
|
-
var
|
|
1237
|
+
var import_node_path9 = require("node:path");
|
|
1067
1238
|
var PACKAGE = "@mutmutco/cursor-plugin";
|
|
1068
1239
|
var MANIFEST = ".cursor-plugin/plugin.json";
|
|
1069
1240
|
var USER_HOOKS_FILE = "hooks.json";
|
|
@@ -1072,14 +1243,14 @@ var KEEP_GENERATIONS = 2;
|
|
|
1072
1243
|
var STRANDED_TMP_AGE_MS = 24 * 60 * 6e4;
|
|
1073
1244
|
var TREE_FILES = [MANIFEST, "skills/mmi/SKILL.md", "hooks/cursor-hooks.json", "scripts/hook-run.mjs", "scripts/hook-policy.mjs"];
|
|
1074
1245
|
function cursorPluginsRoot() {
|
|
1075
|
-
return (0,
|
|
1246
|
+
return (0, import_node_path9.join)((0, import_node_os4.homedir)(), ".cursor", "plugins");
|
|
1076
1247
|
}
|
|
1077
1248
|
function treeHealthy2(root) {
|
|
1078
|
-
return TREE_FILES.every((rel) => (0,
|
|
1249
|
+
return TREE_FILES.every((rel) => (0, import_node_fs11.existsSync)((0, import_node_path9.join)(root, ...rel.split("/"))));
|
|
1079
1250
|
}
|
|
1080
1251
|
function readJson(root, rel) {
|
|
1081
1252
|
try {
|
|
1082
|
-
return JSON.parse((0,
|
|
1253
|
+
return JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path9.join)(root, ...rel.split("/")), "utf8"));
|
|
1083
1254
|
} catch {
|
|
1084
1255
|
return null;
|
|
1085
1256
|
}
|
|
@@ -1088,7 +1259,7 @@ function manifest(root) {
|
|
|
1088
1259
|
return readJson(root, MANIFEST);
|
|
1089
1260
|
}
|
|
1090
1261
|
function probeCursorInstallation() {
|
|
1091
|
-
const location = (0,
|
|
1262
|
+
const location = (0, import_node_path9.join)(cursorPluginsRoot(), "local", "mmi");
|
|
1092
1263
|
const version = manifest(location)?.version ?? null;
|
|
1093
1264
|
return { version, location, detail: version ? "installed Cursor plugin manifest" : "installed Cursor plugin manifest was unreadable" };
|
|
1094
1265
|
}
|
|
@@ -1103,7 +1274,7 @@ function message(error) {
|
|
|
1103
1274
|
}
|
|
1104
1275
|
function discard(path) {
|
|
1105
1276
|
try {
|
|
1106
|
-
(0,
|
|
1277
|
+
(0, import_node_fs11.rmSync)(path, { recursive: true, force: true });
|
|
1107
1278
|
} catch {
|
|
1108
1279
|
}
|
|
1109
1280
|
}
|
|
@@ -1122,7 +1293,7 @@ function cursorHostEvidence(env) {
|
|
|
1122
1293
|
if (env[marker]?.trim()) return `env marker ${marker}`;
|
|
1123
1294
|
}
|
|
1124
1295
|
if (env.CURSOR_AGENT === "1") return "env marker CURSOR_AGENT=1";
|
|
1125
|
-
const result = process.platform === "win32" ? (0,
|
|
1296
|
+
const result = process.platform === "win32" ? (0, import_node_child_process8.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process8.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
|
|
1126
1297
|
if (result.status !== 0 || !(result.stdout ?? "").trim()) return "the process table is unreadable (unsafe to write)";
|
|
1127
1298
|
for (const line of result.stdout.split(/\r?\n/)) {
|
|
1128
1299
|
const executable = cursorExecutable(line);
|
|
@@ -1131,7 +1302,7 @@ function cursorHostEvidence(env) {
|
|
|
1131
1302
|
return null;
|
|
1132
1303
|
}
|
|
1133
1304
|
function resolvedPluginRoot(root) {
|
|
1134
|
-
return (0,
|
|
1305
|
+
return (0, import_node_path9.join)(root).replace(/\\/g, "/");
|
|
1135
1306
|
}
|
|
1136
1307
|
function isMmiEntry(entry, pluginRoot) {
|
|
1137
1308
|
return typeof entry === "object" && entry !== null && typeof entry.command === "string" && entry.command.includes(resolvedPluginRoot(pluginRoot));
|
|
@@ -1141,7 +1312,7 @@ function resolveCommand(command, pluginRoot) {
|
|
|
1141
1312
|
}
|
|
1142
1313
|
function desiredUserHooks(pluginRoot) {
|
|
1143
1314
|
try {
|
|
1144
|
-
const doc = JSON.parse((0,
|
|
1315
|
+
const doc = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path9.join)(pluginRoot, ...PLUGIN_HOOKS_REL), "utf8"));
|
|
1145
1316
|
const hooks = doc.hooks;
|
|
1146
1317
|
if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) return null;
|
|
1147
1318
|
const desired = /* @__PURE__ */ new Map();
|
|
@@ -1177,14 +1348,14 @@ function mergeUserHooks(existing, desired, pluginRoot) {
|
|
|
1177
1348
|
function reconcileCursorUserHooks(pluginRoot, homeDir) {
|
|
1178
1349
|
const desired = desiredUserHooks(pluginRoot);
|
|
1179
1350
|
if (!desired) return "source-unreadable";
|
|
1180
|
-
const path = (0,
|
|
1181
|
-
if (!(0,
|
|
1182
|
-
(0,
|
|
1351
|
+
const path = (0, import_node_path9.join)(homeDir, ".cursor", USER_HOOKS_FILE);
|
|
1352
|
+
if (!(0, import_node_fs11.existsSync)(path)) {
|
|
1353
|
+
(0, import_node_fs11.writeFileSync)(path, JSON.stringify({ version: 1, hooks: Object.fromEntries(desired) }, null, 2) + "\n", "utf8");
|
|
1183
1354
|
return "updated";
|
|
1184
1355
|
}
|
|
1185
1356
|
let existing;
|
|
1186
1357
|
try {
|
|
1187
|
-
existing = JSON.parse((0,
|
|
1358
|
+
existing = JSON.parse((0, import_node_fs11.readFileSync)(path, "utf8"));
|
|
1188
1359
|
} catch {
|
|
1189
1360
|
return "user-conflict";
|
|
1190
1361
|
}
|
|
@@ -1192,8 +1363,8 @@ function reconcileCursorUserHooks(pluginRoot, homeDir) {
|
|
|
1192
1363
|
if (!merged) return "user-conflict";
|
|
1193
1364
|
if (JSON.stringify(merged) === JSON.stringify(existing)) return "current";
|
|
1194
1365
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
1195
|
-
(0,
|
|
1196
|
-
(0,
|
|
1366
|
+
(0, import_node_fs11.writeFileSync)(tmp, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
1367
|
+
(0, import_node_fs11.renameSync)(tmp, path);
|
|
1197
1368
|
return "updated";
|
|
1198
1369
|
}
|
|
1199
1370
|
function userHooksDetail(pluginRoot, homeDir) {
|
|
@@ -1201,7 +1372,7 @@ function userHooksDetail(pluginRoot, homeDir) {
|
|
|
1201
1372
|
case "updated":
|
|
1202
1373
|
return "; user hooks bridged into ~/.cursor/hooks.json";
|
|
1203
1374
|
case "user-conflict":
|
|
1204
|
-
return `; USER HOOKS CONFLICT at ${(0,
|
|
1375
|
+
return `; USER HOOKS CONFLICT at ${(0, import_node_path9.join)(homeDir, ".cursor", USER_HOOKS_FILE)} \u2014 Cursor gates are NOT bridged; fix or remove that file and reconcile`;
|
|
1205
1376
|
case "source-unreadable":
|
|
1206
1377
|
return "; user hooks source unreadable \u2014 Cursor gates are NOT bridged";
|
|
1207
1378
|
default:
|
|
@@ -1209,35 +1380,35 @@ function userHooksDetail(pluginRoot, homeDir) {
|
|
|
1209
1380
|
}
|
|
1210
1381
|
}
|
|
1211
1382
|
function writeRollbackPointer(root, entry) {
|
|
1212
|
-
const path = (0,
|
|
1383
|
+
const path = (0, import_node_path9.join)(root, "quarantine", "rollback.json");
|
|
1213
1384
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
1214
1385
|
try {
|
|
1215
|
-
(0,
|
|
1216
|
-
(0,
|
|
1386
|
+
(0, import_node_fs11.writeFileSync)(tmp, JSON.stringify({ surface: "cursor", ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry }, null, 2) + "\n", "utf8");
|
|
1387
|
+
(0, import_node_fs11.renameSync)(tmp, path);
|
|
1217
1388
|
} catch {
|
|
1218
1389
|
discard(tmp);
|
|
1219
1390
|
}
|
|
1220
1391
|
}
|
|
1221
1392
|
function mtimeMs(path) {
|
|
1222
1393
|
try {
|
|
1223
|
-
return (0,
|
|
1394
|
+
return (0, import_node_fs11.statSync)(path).mtimeMs;
|
|
1224
1395
|
} catch {
|
|
1225
1396
|
return 0;
|
|
1226
1397
|
}
|
|
1227
1398
|
}
|
|
1228
1399
|
function pruneQuarantine(root, keep) {
|
|
1229
|
-
const dir = (0,
|
|
1400
|
+
const dir = (0, import_node_path9.join)(root, "quarantine");
|
|
1230
1401
|
let generations;
|
|
1231
1402
|
try {
|
|
1232
|
-
generations = (0,
|
|
1403
|
+
generations = (0, import_node_fs11.readdirSync)(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("mmi-")).map((entry) => (0, import_node_path9.join)(dir, entry.name)).sort((a, b) => mtimeMs(b) - mtimeMs(a));
|
|
1233
1404
|
} catch {
|
|
1234
1405
|
return 0;
|
|
1235
1406
|
}
|
|
1236
1407
|
let stranded = 0;
|
|
1237
1408
|
try {
|
|
1238
|
-
for (const entry of (0,
|
|
1409
|
+
for (const entry of (0, import_node_fs11.readdirSync)(dir, { withFileTypes: true })) {
|
|
1239
1410
|
if (!entry.isFile() || !/^rollback\.json\.tmp-\d+$/.test(entry.name)) continue;
|
|
1240
|
-
const path = (0,
|
|
1411
|
+
const path = (0, import_node_path9.join)(dir, entry.name);
|
|
1241
1412
|
if (Date.now() - mtimeMs(path) < STRANDED_TMP_AGE_MS) continue;
|
|
1242
1413
|
discard(path);
|
|
1243
1414
|
stranded += 1;
|
|
@@ -1255,16 +1426,16 @@ function pruneQuarantine(root, keep) {
|
|
|
1255
1426
|
}
|
|
1256
1427
|
function restorePrevious(live, quarantined, broken) {
|
|
1257
1428
|
try {
|
|
1258
|
-
(0,
|
|
1429
|
+
(0, import_node_fs11.renameSync)(live, broken);
|
|
1259
1430
|
} catch {
|
|
1260
1431
|
return "kept-broken";
|
|
1261
1432
|
}
|
|
1262
1433
|
try {
|
|
1263
|
-
(0,
|
|
1434
|
+
(0, import_node_fs11.renameSync)(quarantined, live);
|
|
1264
1435
|
return "restored";
|
|
1265
1436
|
} catch {
|
|
1266
1437
|
try {
|
|
1267
|
-
(0,
|
|
1438
|
+
(0, import_node_fs11.renameSync)(broken, live);
|
|
1268
1439
|
return "kept-broken";
|
|
1269
1440
|
} catch {
|
|
1270
1441
|
return "lost";
|
|
@@ -1275,7 +1446,7 @@ function cursorArm(options) {
|
|
|
1275
1446
|
const env = options.env ?? process.env;
|
|
1276
1447
|
const { target, dryRun, paths } = options;
|
|
1277
1448
|
const root = cursorPluginsRoot();
|
|
1278
|
-
const live = (0,
|
|
1449
|
+
const live = (0, import_node_path9.join)(root, "local", "mmi");
|
|
1279
1450
|
const result = (from, to, verdict, detail) => ({
|
|
1280
1451
|
surface: "cursor",
|
|
1281
1452
|
from,
|
|
@@ -1284,7 +1455,7 @@ function cursorArm(options) {
|
|
|
1284
1455
|
detail,
|
|
1285
1456
|
// C11: the constraint of the generation LEFT at `live` — read at result time, so a failed
|
|
1286
1457
|
// switch reports the restored generation's declaration, not the rejected one's (#4973).
|
|
1287
|
-
compat: readManifestCompat((0,
|
|
1458
|
+
compat: readManifestCompat((0, import_node_path9.join)(live, MANIFEST))
|
|
1288
1459
|
});
|
|
1289
1460
|
const current = manifest(live);
|
|
1290
1461
|
const installed = current?.version ?? null;
|
|
@@ -1298,7 +1469,7 @@ function cursorArm(options) {
|
|
|
1298
1469
|
if (installed === target && treeHealthy2(live)) {
|
|
1299
1470
|
return result(installed, target, "ok", `already at target ${target}${userHooksDetail(live, (0, import_node_os4.homedir)())}`);
|
|
1300
1471
|
}
|
|
1301
|
-
if ((0,
|
|
1472
|
+
if ((0, import_node_fs11.existsSync)(live) && !mmiOwned(live)) {
|
|
1302
1473
|
return result(installed, target, "defer", `${live} carries no MMI ownership marker (plugin manifest name, or a ${PACKAGE} package.json) \u2014 refusing to displace an unmanaged directory (mmi-cli plugin heal adopts it)`);
|
|
1303
1474
|
}
|
|
1304
1475
|
if (dryRun) {
|
|
@@ -1314,15 +1485,16 @@ function cursorArm(options) {
|
|
|
1314
1485
|
}
|
|
1315
1486
|
const busy = cursorHostEvidence(env);
|
|
1316
1487
|
if (busy) {
|
|
1317
|
-
|
|
1488
|
+
const pending = dryRun ? "" : ` \u2014 ${noteBusyDefer(paths, env, { surface: "cursor", target, reason: busy, images: ["Cursor"] })}`;
|
|
1489
|
+
return result(installed, target, "defer", `Cursor is running (${busy}) \u2014 the plugin tree is only swapped while the host is absent${pending}`);
|
|
1318
1490
|
}
|
|
1319
1491
|
const stamp = Date.now();
|
|
1320
|
-
const incoming = (0,
|
|
1321
|
-
const quarantined = (0,
|
|
1322
|
-
for (const dir of ["local", "staging", "quarantine"]) (0,
|
|
1492
|
+
const incoming = (0, import_node_path9.join)(root, "staging", `mmi-${safeSegment(target)}-${stamp}`);
|
|
1493
|
+
const quarantined = (0, import_node_path9.join)(root, "quarantine", `mmi-${safeSegment(installed ?? "unknown")}-${stamp}`);
|
|
1494
|
+
for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs11.mkdirSync)((0, import_node_path9.join)(root, dir), { recursive: true });
|
|
1323
1495
|
try {
|
|
1324
1496
|
discard(incoming);
|
|
1325
|
-
(0,
|
|
1497
|
+
(0, import_node_fs11.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
1326
1498
|
} catch (error) {
|
|
1327
1499
|
discard(incoming);
|
|
1328
1500
|
return result(installed, target, "defer", `copy into ${root} failed: ${message(error)}`);
|
|
@@ -1334,28 +1506,31 @@ function cursorArm(options) {
|
|
|
1334
1506
|
const started = cursorHostEvidence(env);
|
|
1335
1507
|
if (started) {
|
|
1336
1508
|
discard(incoming);
|
|
1337
|
-
|
|
1509
|
+
const pending = noteBusyDefer(paths, env, { surface: "cursor", target, reason: started, images: ["Cursor"] });
|
|
1510
|
+
return result(installed, target, "defer", `Cursor started during staging (${started}) \u2014 the plugin tree is only swapped while the host is absent \u2014 ${pending}`);
|
|
1338
1511
|
}
|
|
1339
1512
|
let displaced = false;
|
|
1340
1513
|
try {
|
|
1341
|
-
if ((0,
|
|
1342
|
-
(0,
|
|
1514
|
+
if ((0, import_node_fs11.existsSync)(live)) {
|
|
1515
|
+
(0, import_node_fs11.renameSync)(live, quarantined);
|
|
1343
1516
|
displaced = true;
|
|
1344
1517
|
}
|
|
1345
|
-
(0,
|
|
1518
|
+
(0, import_node_fs11.renameSync)(incoming, live);
|
|
1346
1519
|
} catch (error) {
|
|
1347
|
-
if (displaced && !(0,
|
|
1520
|
+
if (displaced && !(0, import_node_fs11.existsSync)(live)) {
|
|
1348
1521
|
try {
|
|
1349
|
-
(0,
|
|
1522
|
+
(0, import_node_fs11.renameSync)(quarantined, live);
|
|
1350
1523
|
} catch {
|
|
1351
1524
|
}
|
|
1352
1525
|
}
|
|
1353
1526
|
discard(incoming);
|
|
1354
|
-
|
|
1527
|
+
const reason = message(error);
|
|
1528
|
+
const pending = lockShaped(reason) ? ` \u2014 ${noteBusyDefer(paths, env, { surface: "cursor", target, reason, images: ["Cursor"] })}` : "";
|
|
1529
|
+
return result(installed, target, "defer", `generation switch failed, previous generation retained: ${reason}${pending}`);
|
|
1355
1530
|
}
|
|
1356
1531
|
const evidence = manifest(live)?.version;
|
|
1357
1532
|
if (evidence !== target || !treeHealthy2(live)) {
|
|
1358
|
-
const broken = `${(0,
|
|
1533
|
+
const broken = `${(0, import_node_path9.join)(root, "quarantine", `mmi-${safeSegment(evidence ?? "broken")}-${stamp}`)}-failed`;
|
|
1359
1534
|
const outcome = displaced ? restorePrevious(live, quarantined, broken) : "none";
|
|
1360
1535
|
const detail = outcome === "restored" ? `restored the previous generation ${installed ?? "unknown"}, unverified tree kept at ${broken}` : outcome === "lost" ? `restore failed and ${live} is missing \u2014 the previous generation is at ${quarantined}` : displaced ? `restore failed, the unverified tree is still live and the previous generation is at ${quarantined}` : "nothing was displaced \u2014 there is no previous generation to restore";
|
|
1361
1536
|
writeRollbackPointer(root, outcome === "restored" ? { installed: installed ?? "unknown", previous: null, generation: null } : { installed: evidence ?? "unknown", previous: installed, generation: displaced ? quarantined : null });
|
|
@@ -1368,8 +1543,8 @@ function cursorArm(options) {
|
|
|
1368
1543
|
}
|
|
1369
1544
|
|
|
1370
1545
|
// src/arm-jervcode.ts
|
|
1371
|
-
var
|
|
1372
|
-
var
|
|
1546
|
+
var import_node_fs12 = require("node:fs");
|
|
1547
|
+
var import_node_path10 = require("node:path");
|
|
1373
1548
|
var PI_PACKAGE = "@mutmutco/pi-plugin";
|
|
1374
1549
|
function hostEnv(env) {
|
|
1375
1550
|
const dir = jervcodeAgentDir(env);
|
|
@@ -1380,7 +1555,7 @@ function hostCommands(_env) {
|
|
|
1380
1555
|
}
|
|
1381
1556
|
function readPackageSpecs(env) {
|
|
1382
1557
|
try {
|
|
1383
|
-
const settings = JSON.parse((0,
|
|
1558
|
+
const settings = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path10.join)(jervcodeAgentDir(env), "settings.json"), "utf8"));
|
|
1384
1559
|
return Array.isArray(settings.packages) ? settings.packages.filter((entry) => typeof entry === "string") : [];
|
|
1385
1560
|
} catch {
|
|
1386
1561
|
return null;
|
|
@@ -1388,14 +1563,14 @@ function readPackageSpecs(env) {
|
|
|
1388
1563
|
}
|
|
1389
1564
|
function installedVersion(env) {
|
|
1390
1565
|
try {
|
|
1391
|
-
const manifest2 = JSON.parse((0,
|
|
1566
|
+
const manifest2 = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"), "utf8"));
|
|
1392
1567
|
return typeof manifest2.version === "string" ? manifest2.version : null;
|
|
1393
1568
|
} catch {
|
|
1394
1569
|
return null;
|
|
1395
1570
|
}
|
|
1396
1571
|
}
|
|
1397
1572
|
function probeJervcodeInstallation(env = process.env) {
|
|
1398
|
-
const location = (0,
|
|
1573
|
+
const location = (0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"));
|
|
1399
1574
|
const version = installedVersion(env);
|
|
1400
1575
|
return { version, location, detail: version ? "materialized pi package manifest" : "materialized pi package manifest was unreadable" };
|
|
1401
1576
|
}
|
|
@@ -1417,32 +1592,32 @@ function jervcodeArm(options) {
|
|
|
1417
1592
|
detail,
|
|
1418
1593
|
// C11: the constraint of the package pi LEFT materialised — read at result time, so defer/fail
|
|
1419
1594
|
// carry the previous payload's declaration (#4973 reconsult ruling).
|
|
1420
|
-
compat: readManifestCompat((0,
|
|
1595
|
+
compat: readManifestCompat((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"))
|
|
1421
1596
|
});
|
|
1422
1597
|
const before = readPackageSpecs(env);
|
|
1423
|
-
if (!before) return result(null, options.target, "defer", `cannot read ${(0,
|
|
1598
|
+
if (!before) return result(null, options.target, "defer", `cannot read ${(0, import_node_path10.join)(jervcodeAgentDir(env), "settings.json")}`);
|
|
1424
1599
|
const from = installedVersion(env);
|
|
1425
1600
|
const legacy = before.filter(isLegacyMmiPiPath);
|
|
1426
1601
|
const highWater = journalHighWater(options.paths.journalPath, "jervcode");
|
|
1427
1602
|
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;
|
|
1428
1603
|
if (options.dryRun) {
|
|
1429
1604
|
const action = floor ? `hold ${from ?? "the install"} and skip the install (${floor})` : before.includes(exact) && from === options.target ? "keep exact pin" : `install ${exact}`;
|
|
1430
|
-
return { surface: "jervcode", from, to: options.target, verdict: "ok", detail: `dry-run: would ${action}${legacy.length ? ` and retire ${legacy.length} legacy claude-cache path registration${legacy.length === 1 ? "" : "s"}` : ""}`, compat: readManifestCompat((0,
|
|
1605
|
+
return { surface: "jervcode", from, to: options.target, verdict: "ok", detail: `dry-run: would ${action}${legacy.length ? ` and retire ${legacy.length} legacy claude-cache path registration${legacy.length === 1 ? "" : "s"}` : ""}`, compat: readManifestCompat((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
1431
1606
|
}
|
|
1432
1607
|
const hosted = hostEnv(env);
|
|
1433
1608
|
const commands = hostCommands(env);
|
|
1434
1609
|
const command = commands.find((bin) => runner(bin, ["--version"], hosted).status === 0);
|
|
1435
|
-
if (!command) return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: "neither `jervcode` nor `pi` answered --version \u2014 the host CLI is not on PATH", compat: readManifestCompat((0,
|
|
1610
|
+
if (!command) return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: "neither `jervcode` nor `pi` answered --version \u2014 the host CLI is not on PATH", compat: readManifestCompat((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
1436
1611
|
if (!floor && (!before.includes(exact) || from !== options.target)) {
|
|
1437
1612
|
const install = runner(command, ["install", exact], hosted);
|
|
1438
1613
|
if (install.status !== 0) {
|
|
1439
|
-
return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} install ${exact} failed: ${failure2(install)}`, compat: readManifestCompat((0,
|
|
1614
|
+
return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} install ${exact} failed: ${failure2(install)}`, compat: readManifestCompat((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
1440
1615
|
}
|
|
1441
1616
|
}
|
|
1442
1617
|
for (const stale of legacy) {
|
|
1443
1618
|
const removed = runner(command, ["remove", stale], hosted);
|
|
1444
1619
|
if (removed.status !== 0) {
|
|
1445
|
-
return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} remove of the legacy registration failed: ${failure2(removed)}`, compat: readManifestCompat((0,
|
|
1620
|
+
return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} remove of the legacy registration failed: ${failure2(removed)}`, compat: readManifestCompat((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
1446
1621
|
}
|
|
1447
1622
|
}
|
|
1448
1623
|
const after = readPackageSpecs(env);
|
|
@@ -1450,7 +1625,7 @@ function jervcodeArm(options) {
|
|
|
1450
1625
|
const legacyAfter = after?.filter(isLegacyMmiPiPath) ?? [];
|
|
1451
1626
|
const retired = legacy.length ? `; retired ${legacy.length} legacy claude-cache path registration${legacy.length === 1 ? "" : "s"}` : "";
|
|
1452
1627
|
if (floor && !legacyAfter.length) {
|
|
1453
|
-
return { surface: "jervcode", from, to: converged ?? highWater ?? options.target, verdict: "skip", detail: `${floor}${retired}`, compat: readManifestCompat((0,
|
|
1628
|
+
return { surface: "jervcode", from, to: converged ?? highWater ?? options.target, verdict: "skip", detail: `${floor}${retired}`, compat: readManifestCompat((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
1454
1629
|
}
|
|
1455
1630
|
if (!after?.includes(exact) || converged !== options.target || legacyAfter.length) {
|
|
1456
1631
|
return {
|
|
@@ -1471,14 +1646,15 @@ function jervcodeArm(options) {
|
|
|
1471
1646
|
}
|
|
1472
1647
|
|
|
1473
1648
|
// src/arm-hermes.ts
|
|
1474
|
-
var
|
|
1475
|
-
var
|
|
1649
|
+
var import_node_fs13 = require("node:fs");
|
|
1650
|
+
var import_node_child_process9 = require("node:child_process");
|
|
1651
|
+
var import_node_path11 = require("node:path");
|
|
1476
1652
|
var PACKAGE2 = "@mutmutco/hermes-plugin";
|
|
1477
1653
|
var MANIFEST2 = "plugin.yaml";
|
|
1478
1654
|
var TREE_MARKERS2 = [MANIFEST2, "__init__.py", "skills"];
|
|
1479
1655
|
function yamlVersion(root) {
|
|
1480
1656
|
try {
|
|
1481
|
-
const text = (0,
|
|
1657
|
+
const text = (0, import_node_fs13.readFileSync)((0, import_node_path11.join)(root, MANIFEST2), "utf8");
|
|
1482
1658
|
try {
|
|
1483
1659
|
const version = JSON.parse(text).version;
|
|
1484
1660
|
if (typeof version === "string" && version.trim()) return version.trim();
|
|
@@ -1491,9 +1667,9 @@ function yamlVersion(root) {
|
|
|
1491
1667
|
}
|
|
1492
1668
|
}
|
|
1493
1669
|
function treeHealthy3(root) {
|
|
1494
|
-
return TREE_MARKERS2.slice(0, 2).every((file) => (0,
|
|
1670
|
+
return TREE_MARKERS2.slice(0, 2).every((file) => (0, import_node_fs13.existsSync)((0, import_node_path11.join)(root, file))) && (() => {
|
|
1495
1671
|
try {
|
|
1496
|
-
return (0,
|
|
1672
|
+
return (0, import_node_fs13.statSync)((0, import_node_path11.join)(root, "skills")).isDirectory();
|
|
1497
1673
|
} catch {
|
|
1498
1674
|
return false;
|
|
1499
1675
|
}
|
|
@@ -1501,13 +1677,13 @@ function treeHealthy3(root) {
|
|
|
1501
1677
|
}
|
|
1502
1678
|
function packageIdentity(root) {
|
|
1503
1679
|
try {
|
|
1504
|
-
return JSON.parse((0,
|
|
1680
|
+
return JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path11.join)(root, "package.json"), "utf8")).name === PACKAGE2;
|
|
1505
1681
|
} catch {
|
|
1506
1682
|
return false;
|
|
1507
1683
|
}
|
|
1508
1684
|
}
|
|
1509
1685
|
function probeHermesInstallation(env = process.env) {
|
|
1510
|
-
const location = (0,
|
|
1686
|
+
const location = (0, import_node_path11.join)(hermesHome(env), "plugins", "mmi");
|
|
1511
1687
|
const version = yamlVersion(location);
|
|
1512
1688
|
return { version, location, detail: version ? "installed Hermes plugin manifest" : "installed Hermes plugin manifest was unreadable" };
|
|
1513
1689
|
}
|
|
@@ -1516,27 +1692,45 @@ function mmiOwned2(root) {
|
|
|
1516
1692
|
}
|
|
1517
1693
|
function discard2(path) {
|
|
1518
1694
|
try {
|
|
1519
|
-
(0,
|
|
1695
|
+
(0, import_node_fs13.rmSync)(path, { recursive: true, force: true });
|
|
1520
1696
|
} catch {
|
|
1521
1697
|
}
|
|
1522
1698
|
}
|
|
1523
1699
|
function message2(error) {
|
|
1524
1700
|
return String(error?.message ?? error).replace(/\s+/g, " ").slice(0, 200);
|
|
1525
1701
|
}
|
|
1702
|
+
var HERMES_PROCESS_SEGMENT = /^hermes(-cli|-code)?(\.(exe|cmd|bat|ps1|js|mjs|cjs|py))?$/i;
|
|
1703
|
+
function hermesProcessToken(line, home) {
|
|
1704
|
+
const normalized = line.replace(/["']/g, " ");
|
|
1705
|
+
if (home && normalized.toLowerCase().includes(home.toLowerCase())) return home;
|
|
1706
|
+
for (const token of normalized.split(/\s+/)) {
|
|
1707
|
+
if (token && token.split(/[/\\]/).some((segment) => HERMES_PROCESS_SEGMENT.test(segment))) return token;
|
|
1708
|
+
}
|
|
1709
|
+
return null;
|
|
1710
|
+
}
|
|
1711
|
+
function hermesHostEvidence(env, home) {
|
|
1712
|
+
const result = process.platform === "win32" ? (0, import_node_child_process9.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.Name + ' ' + $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process9.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
|
|
1713
|
+
if (result.status !== 0 || !(result.stdout ?? "").trim()) return "the process list is unreadable (unsafe to write)";
|
|
1714
|
+
for (const line of result.stdout.split(/\r?\n/)) {
|
|
1715
|
+
const token = hermesProcessToken(line, home);
|
|
1716
|
+
if (token) return `a live process names hermes (${token})`;
|
|
1717
|
+
}
|
|
1718
|
+
return null;
|
|
1719
|
+
}
|
|
1526
1720
|
function materializeHermesSkills(home) {
|
|
1527
|
-
const pluginSkills = (0,
|
|
1528
|
-
const skillsRoot = (0,
|
|
1529
|
-
const targetRoot = (0,
|
|
1721
|
+
const pluginSkills = (0, import_node_path11.join)(home, "plugins", "mmi", "skills");
|
|
1722
|
+
const skillsRoot = (0, import_node_path11.join)(home, "skills");
|
|
1723
|
+
const targetRoot = (0, import_node_path11.join)(skillsRoot, "mmi");
|
|
1530
1724
|
try {
|
|
1531
|
-
if (!(0,
|
|
1532
|
-
const names = (0,
|
|
1725
|
+
if (!(0, import_node_fs13.existsSync)(pluginSkills)) return { ok: false, detail: `plugin skills tree missing at ${pluginSkills}` };
|
|
1726
|
+
const names = (0, import_node_fs13.readdirSync)(pluginSkills, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name);
|
|
1533
1727
|
if (!names.length) return { ok: false, detail: `plugin skills tree is empty at ${pluginSkills}` };
|
|
1534
|
-
(0,
|
|
1535
|
-
const incoming = (0,
|
|
1728
|
+
(0, import_node_fs13.mkdirSync)(skillsRoot, { recursive: true });
|
|
1729
|
+
const incoming = (0, import_node_path11.join)(skillsRoot, ".mmi-incoming");
|
|
1536
1730
|
discard2(incoming);
|
|
1537
|
-
(0,
|
|
1731
|
+
(0, import_node_fs13.cpSync)(pluginSkills, incoming, { recursive: true });
|
|
1538
1732
|
discard2(targetRoot);
|
|
1539
|
-
(0,
|
|
1733
|
+
(0, import_node_fs13.renameSync)(incoming, targetRoot);
|
|
1540
1734
|
return { ok: true, detail: `provisioned ${names.length} skills to ${targetRoot}` };
|
|
1541
1735
|
} catch (error) {
|
|
1542
1736
|
return { ok: false, detail: message2(error) };
|
|
@@ -1545,8 +1739,8 @@ function materializeHermesSkills(home) {
|
|
|
1545
1739
|
function hermesArm(options) {
|
|
1546
1740
|
const env = options.env ?? process.env;
|
|
1547
1741
|
const home = hermesHome(env);
|
|
1548
|
-
const parent = (0,
|
|
1549
|
-
const live = (0,
|
|
1742
|
+
const parent = (0, import_node_path11.join)(home, "plugins");
|
|
1743
|
+
const live = (0, import_node_path11.join)(parent, "mmi");
|
|
1550
1744
|
const installed = yamlVersion(live);
|
|
1551
1745
|
const result = (from, to, verdict, detail) => ({
|
|
1552
1746
|
surface: "hermes",
|
|
@@ -1555,8 +1749,8 @@ function hermesArm(options) {
|
|
|
1555
1749
|
verdict,
|
|
1556
1750
|
detail
|
|
1557
1751
|
});
|
|
1558
|
-
if (!(0,
|
|
1559
|
-
if ((0,
|
|
1752
|
+
if (!(0, import_node_fs13.existsSync)(home)) return result(null, options.target, "skip", `absent (${home}) \u2014 skipped without writes`);
|
|
1753
|
+
if ((0, import_node_fs13.existsSync)(live) && !mmiOwned2(live)) {
|
|
1560
1754
|
return result(installed, options.target, "defer", `${live} carries no ${PACKAGE2} package identity \u2014 refusing to displace an unmanaged Hermes mmi plugin`);
|
|
1561
1755
|
}
|
|
1562
1756
|
if (installed && compareSemver(installed, options.target) > 0) return result(installed, installed, "skip", `installed ${installed} is above candidate ${options.target} (monotonic)`);
|
|
@@ -1567,21 +1761,21 @@ function hermesArm(options) {
|
|
|
1567
1761
|
if (!skills2.ok) return result(installed, options.target, "fail", `plugin at target ${options.target}, but skills provisioning failed: ${skills2.detail}`);
|
|
1568
1762
|
return result(installed, options.target, "ok", `already at target ${options.target}; ${skills2.detail}`);
|
|
1569
1763
|
}
|
|
1570
|
-
if (options.dryRun) return result(installed, options.target, "ok", `dry-run: would stage ${PACKAGE2}@${options.target}, atomically switch ${installed ?? "absent"} -> ${options.target}, then provision skills into ${(0,
|
|
1764
|
+
if (options.dryRun) return result(installed, options.target, "ok", `dry-run: would stage ${PACKAGE2}@${options.target}, atomically switch ${installed ?? "absent"} -> ${options.target}, then provision skills into ${(0, import_node_path11.join)(home, "skills", "mmi")}`);
|
|
1571
1765
|
const staged = stagePackage({ surface: "hermes", packageName: PACKAGE2, target: options.target, witness: MANIFEST2, paths: options.paths, env });
|
|
1572
1766
|
if ("defer" in staged) return result(installed, options.target, "defer", staged.defer);
|
|
1573
1767
|
if (yamlVersion(staged.packageRoot) !== options.target || !treeHealthy3(staged.packageRoot)) {
|
|
1574
1768
|
return result(installed, options.target, "defer", `staged Hermes manifest is ${yamlVersion(staged.packageRoot) ?? "unreadable"} or its tree is incomplete (expected ${options.target})`);
|
|
1575
1769
|
}
|
|
1576
|
-
const scratch = (0,
|
|
1577
|
-
const incomingRoot = (0,
|
|
1578
|
-
const incoming = (0,
|
|
1579
|
-
const quarantine = (0,
|
|
1770
|
+
const scratch = (0, import_node_path11.join)(home, ".mmi-updater");
|
|
1771
|
+
const incomingRoot = (0, import_node_path11.join)(scratch, "incoming");
|
|
1772
|
+
const incoming = (0, import_node_path11.join)(incomingRoot, options.target);
|
|
1773
|
+
const quarantine = (0, import_node_path11.join)(scratch, "quarantine", `${installed ?? "unknown"}-${Date.now()}`);
|
|
1580
1774
|
try {
|
|
1581
|
-
(0,
|
|
1582
|
-
(0,
|
|
1775
|
+
(0, import_node_fs13.mkdirSync)(parent, { recursive: true });
|
|
1776
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path11.join)(scratch, "quarantine"), { recursive: true });
|
|
1583
1777
|
discard2(incomingRoot);
|
|
1584
|
-
(0,
|
|
1778
|
+
(0, import_node_fs13.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
1585
1779
|
} catch (error) {
|
|
1586
1780
|
return result(installed, options.target, "defer", `cannot place incoming Hermes generation: ${message2(error)}`);
|
|
1587
1781
|
}
|
|
@@ -1589,24 +1783,32 @@ function hermesArm(options) {
|
|
|
1589
1783
|
discard2(incomingRoot);
|
|
1590
1784
|
return result(installed, options.target, "defer", "incoming Hermes generation did not reproduce the verified payload");
|
|
1591
1785
|
}
|
|
1592
|
-
const
|
|
1786
|
+
const busy = hermesHostEvidence(env, home);
|
|
1787
|
+
if (busy) {
|
|
1788
|
+
discard2(incomingRoot);
|
|
1789
|
+
const pending = noteBusyDefer(options.paths, env, { surface: "hermes", target: options.target, reason: busy, images: ["hermes"], dirs: [home] });
|
|
1790
|
+
return result(installed, options.target, "defer", `Hermes may be running \u2014 the plugin tree is only swapped while the host is absent: ${busy} \u2014 ${pending}`);
|
|
1791
|
+
}
|
|
1792
|
+
const displaced = (0, import_node_fs13.existsSync)(live);
|
|
1593
1793
|
try {
|
|
1594
|
-
if (displaced) (0,
|
|
1595
|
-
(0,
|
|
1794
|
+
if (displaced) (0, import_node_fs13.renameSync)(live, quarantine);
|
|
1795
|
+
(0, import_node_fs13.renameSync)(incoming, live);
|
|
1596
1796
|
} catch (error) {
|
|
1597
|
-
if (displaced && !(0,
|
|
1598
|
-
(0,
|
|
1797
|
+
if (displaced && !(0, import_node_fs13.existsSync)(live)) try {
|
|
1798
|
+
(0, import_node_fs13.renameSync)(quarantine, live);
|
|
1599
1799
|
} catch {
|
|
1600
1800
|
}
|
|
1601
1801
|
discard2(incomingRoot);
|
|
1602
|
-
|
|
1802
|
+
const reason = message2(error);
|
|
1803
|
+
const pending = lockShaped(reason) ? ` \u2014 ${noteBusyDefer(options.paths, env, { surface: "hermes", target: options.target, reason, images: ["hermes"], dirs: [home] })}` : "";
|
|
1804
|
+
return result(installed, options.target, "defer", `Hermes generation switch failed; previous generation retained: ${reason}${pending}`);
|
|
1603
1805
|
}
|
|
1604
1806
|
if (yamlVersion(live) !== options.target || !treeHealthy3(live)) {
|
|
1605
|
-
const broken = (0,
|
|
1807
|
+
const broken = (0, import_node_path11.join)(scratch, "rejected", `${options.target}-${Date.now()}`);
|
|
1606
1808
|
try {
|
|
1607
|
-
(0,
|
|
1608
|
-
(0,
|
|
1609
|
-
if (displaced) (0,
|
|
1809
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path11.join)(scratch, "rejected"), { recursive: true });
|
|
1810
|
+
(0, import_node_fs13.renameSync)(live, broken);
|
|
1811
|
+
if (displaced) (0, import_node_fs13.renameSync)(quarantine, live);
|
|
1610
1812
|
} catch (error) {
|
|
1611
1813
|
return result(installed, yamlVersion(live) ?? "unknown", "fail", `post-switch Hermes verification failed and recovery failed: ${message2(error)}`);
|
|
1612
1814
|
}
|
|
@@ -1619,9 +1821,9 @@ function hermesArm(options) {
|
|
|
1619
1821
|
}
|
|
1620
1822
|
|
|
1621
1823
|
// src/reap.ts
|
|
1622
|
-
var
|
|
1623
|
-
var
|
|
1624
|
-
var
|
|
1824
|
+
var import_node_child_process10 = require("node:child_process");
|
|
1825
|
+
var import_node_fs14 = require("node:fs");
|
|
1826
|
+
var import_node_path12 = require("node:path");
|
|
1625
1827
|
var KEEP_GENERATIONS2 = 2;
|
|
1626
1828
|
var SCRATCH_AGE_MS = 24 * 60 * 6e4;
|
|
1627
1829
|
var ORPHAN_MIN_AGE_MS = 10 * 6e4;
|
|
@@ -1629,7 +1831,7 @@ var SNAPSHOT_TIMEOUT_MS = 2e4;
|
|
|
1629
1831
|
var MAX_UNWIND_ROUNDS = 4;
|
|
1630
1832
|
function listDirs(path) {
|
|
1631
1833
|
try {
|
|
1632
|
-
return (0,
|
|
1834
|
+
return (0, import_node_fs14.readdirSync)(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
1633
1835
|
} catch {
|
|
1634
1836
|
return [];
|
|
1635
1837
|
}
|
|
@@ -1637,7 +1839,7 @@ function listDirs(path) {
|
|
|
1637
1839
|
function generationSets(root) {
|
|
1638
1840
|
const children = listDirs(root);
|
|
1639
1841
|
if (children.some((name) => parseSemver(name))) return [root];
|
|
1640
|
-
return children.map((name) => (0,
|
|
1842
|
+
return children.map((name) => (0, import_node_path12.join)(root, name)).filter((path) => listDirs(path).some((name) => parseSemver(name)));
|
|
1641
1843
|
}
|
|
1642
1844
|
function pruneGenerations(root, category, dryRun) {
|
|
1643
1845
|
let reaped = 0;
|
|
@@ -1650,7 +1852,7 @@ function pruneGenerations(root, category, dryRun) {
|
|
|
1650
1852
|
continue;
|
|
1651
1853
|
}
|
|
1652
1854
|
try {
|
|
1653
|
-
(0,
|
|
1855
|
+
(0, import_node_fs14.rmSync)((0, import_node_path12.join)(set, version), { recursive: true, force: true });
|
|
1654
1856
|
reaped += 1;
|
|
1655
1857
|
} catch (error) {
|
|
1656
1858
|
failures.push(`${version}: ${error?.code ?? error?.message ?? error}`);
|
|
@@ -1664,8 +1866,8 @@ function pruneGenerations(root, category, dryRun) {
|
|
|
1664
1866
|
}
|
|
1665
1867
|
var LEASE_STEAL_SCRATCH = /^lease\.lock\.stale-\d+$/;
|
|
1666
1868
|
function atomicWriteScratch(path) {
|
|
1667
|
-
const literal = (0,
|
|
1668
|
-
return { dir: (0,
|
|
1869
|
+
const literal = (0, import_node_path12.basename)(path).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1870
|
+
return { dir: (0, import_node_path12.dirname)(path), pattern: new RegExp(`^${literal}\\.tmp-\\d+$`) };
|
|
1669
1871
|
}
|
|
1670
1872
|
function reapScratch(context, dryRun) {
|
|
1671
1873
|
const category = "scratch";
|
|
@@ -1683,7 +1885,7 @@ function reapScratch(context, dryRun) {
|
|
|
1683
1885
|
for (const [dir, patterns] of scanned) {
|
|
1684
1886
|
let entries;
|
|
1685
1887
|
try {
|
|
1686
|
-
entries = (0,
|
|
1888
|
+
entries = (0, import_node_fs14.readdirSync)(dir, { withFileTypes: true });
|
|
1687
1889
|
} catch {
|
|
1688
1890
|
unreadable.push(dir);
|
|
1689
1891
|
continue;
|
|
@@ -1691,10 +1893,10 @@ function reapScratch(context, dryRun) {
|
|
|
1691
1893
|
for (const entry of entries) {
|
|
1692
1894
|
if (!patterns.some((pattern) => pattern.test(entry.name))) continue;
|
|
1693
1895
|
if (!entry.isFile()) continue;
|
|
1694
|
-
const path = (0,
|
|
1896
|
+
const path = (0, import_node_path12.join)(dir, entry.name);
|
|
1695
1897
|
let ageMs;
|
|
1696
1898
|
try {
|
|
1697
|
-
ageMs = now - (0,
|
|
1899
|
+
ageMs = now - (0, import_node_fs14.statSync)(path).mtimeMs;
|
|
1698
1900
|
} catch {
|
|
1699
1901
|
continue;
|
|
1700
1902
|
}
|
|
@@ -1704,7 +1906,7 @@ function reapScratch(context, dryRun) {
|
|
|
1704
1906
|
continue;
|
|
1705
1907
|
}
|
|
1706
1908
|
try {
|
|
1707
|
-
(0,
|
|
1909
|
+
(0, import_node_fs14.rmSync)(path, { force: true });
|
|
1708
1910
|
reaped += 1;
|
|
1709
1911
|
} catch (error) {
|
|
1710
1912
|
failures.push(`${entry.name}: ${error?.code ?? error?.message ?? error}`);
|
|
@@ -1820,7 +2022,7 @@ function stillTheProvenProcess(rows, kill) {
|
|
|
1820
2022
|
}
|
|
1821
2023
|
var SNAPSHOT_COMMAND = "Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,Name,CommandLine,@{n='Started';e={if ($_.CreationDate) { $_.CreationDate.ToUniversalTime().ToString('o') }}} | ConvertTo-Json -Compress";
|
|
1822
2024
|
function processSnapshot() {
|
|
1823
|
-
const result = (0,
|
|
2025
|
+
const result = (0, import_node_child_process10.spawnSync)("powershell.exe", ["-NoProfile", "-Command", SNAPSHOT_COMMAND], { encoding: "utf8", windowsHide: true, timeout: SNAPSHOT_TIMEOUT_MS });
|
|
1824
2026
|
if (result.status !== 0 || !(result.stdout ?? "").trim()) return null;
|
|
1825
2027
|
try {
|
|
1826
2028
|
const parsed = JSON.parse(result.stdout);
|
|
@@ -1944,12 +2146,20 @@ function reconcile(options) {
|
|
|
1944
2146
|
if (arm.verdict === "fail") arm.repeatFailure = journalSurfaceRepeatedFail(paths.journalPath, arm.surface, run);
|
|
1945
2147
|
journal({ kind: "arm", surface: arm.surface, from: arm.from, to: arm.to, verdict: arm.verdict, detail: arm.detail, compat: arm.compat });
|
|
1946
2148
|
options.onArm?.(arm);
|
|
2149
|
+
if (arm.verdict === "ok" && !options.dryRun) clearPending(paths, arm.surface);
|
|
1947
2150
|
if (arm.verdict === "defer") summary.exit = Math.max(summary.exit, 2);
|
|
1948
2151
|
if (arm.verdict === "fail") summary.exit = Math.max(summary.exit, 3);
|
|
1949
2152
|
};
|
|
1950
2153
|
try {
|
|
1951
2154
|
say("acquiring single-writer lease");
|
|
1952
2155
|
return withLease(paths.leasePath, () => {
|
|
2156
|
+
if (!options.dryRun) {
|
|
2157
|
+
const pendingBefore = readPending(paths);
|
|
2158
|
+
if (pendingBefore.length) {
|
|
2159
|
+
const resumed = resumePendingWatcher(paths, env);
|
|
2160
|
+
journal({ kind: "pending", verdict: "ok", count: pendingBefore.length, detail: resumed.scheduled ? `${pendingBefore.length} pending appl${pendingBefore.length === 1 ? "y" : "ies"}; post-exit watcher pid ${resumed.pid} waiting` : `${pendingBefore.length} pending appl${pendingBefore.length === 1 ? "y" : "ies"}; ${resumed.reason}` });
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
1953
2163
|
summary.reap = reap({ paths, dryRun: options.dryRun, env });
|
|
1954
2164
|
for (const row of summary.reap) journal({ kind: "reap", surface: row.category, verdict: row.verdict, count: row.reaped, detail: row.detail });
|
|
1955
2165
|
for (const row of summary.reap.filter((entry) => entry.verdict !== "skip")) say(`reap ${row.category}: ${row.verdict}${row.detail ? " \u2014 " + row.detail : ""}`);
|
|
@@ -2043,10 +2253,10 @@ function reconcile(options) {
|
|
|
2043
2253
|
}
|
|
2044
2254
|
|
|
2045
2255
|
// src/scheduler.ts
|
|
2046
|
-
var
|
|
2047
|
-
var
|
|
2256
|
+
var import_node_child_process11 = require("node:child_process");
|
|
2257
|
+
var import_node_fs15 = require("node:fs");
|
|
2048
2258
|
var import_node_os5 = require("node:os");
|
|
2049
|
-
var
|
|
2259
|
+
var import_node_path13 = require("node:path");
|
|
2050
2260
|
var TASK_NAME = "MMI Fleet Updater";
|
|
2051
2261
|
var LAUNCHER_FILE = "reconcile-hidden.vbs";
|
|
2052
2262
|
var RUN_BOUND = "PT15M";
|
|
@@ -2062,15 +2272,15 @@ var SYSTEMD_UNIT = "mmi-hub-updater";
|
|
|
2062
2272
|
var SYSTEMD_SERVICE_NAME = `${SYSTEMD_UNIT}.service`;
|
|
2063
2273
|
var SYSTEMD_TIMER_NAME = `${SYSTEMD_UNIT}.timer`;
|
|
2064
2274
|
function schtasks(args) {
|
|
2065
|
-
const result = (0,
|
|
2275
|
+
const result = (0, import_node_child_process11.spawnSync)("schtasks.exe", args, { encoding: "utf8", windowsHide: true });
|
|
2066
2276
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
2067
2277
|
}
|
|
2068
2278
|
function launchctl(args) {
|
|
2069
|
-
const result = (0,
|
|
2279
|
+
const result = (0, import_node_child_process11.spawnSync)("launchctl", args, { encoding: "utf8", windowsHide: true });
|
|
2070
2280
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
2071
2281
|
}
|
|
2072
2282
|
function systemctlUser(args) {
|
|
2073
|
-
const result = (0,
|
|
2283
|
+
const result = (0, import_node_child_process11.spawnSync)("systemctl", ["--user", ...args], { encoding: "utf8", windowsHide: true });
|
|
2074
2284
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
2075
2285
|
}
|
|
2076
2286
|
function uid() {
|
|
@@ -2095,7 +2305,7 @@ WScript.Quit code
|
|
|
2095
2305
|
`;
|
|
2096
2306
|
}
|
|
2097
2307
|
function loginPath(fallback = process.env.PATH ?? "") {
|
|
2098
|
-
const probe = (0,
|
|
2308
|
+
const probe = (0, import_node_child_process11.spawnSync)("/bin/sh", ["-lc", 'printf %s "$PATH"'], { encoding: "utf8", windowsHide: true, timeout: 1e4 });
|
|
2099
2309
|
const captured = (probe.status === 0 ? probe.stdout ?? "" : "").trim();
|
|
2100
2310
|
const source = captured || fallback;
|
|
2101
2311
|
const durable = source.split(":").filter((entry) => entry && !/^\/(?:private\/)?(?:tmp|var\/folders)\//.test(entry));
|
|
@@ -2204,7 +2414,7 @@ WantedBy=timers.target
|
|
|
2204
2414
|
}
|
|
2205
2415
|
function readFileOrNull(path) {
|
|
2206
2416
|
try {
|
|
2207
|
-
return (0,
|
|
2417
|
+
return (0, import_node_fs15.readFileSync)(path, "utf8");
|
|
2208
2418
|
} catch {
|
|
2209
2419
|
return null;
|
|
2210
2420
|
}
|
|
@@ -2216,7 +2426,7 @@ function elevatedRelaunch(mode, env) {
|
|
|
2216
2426
|
if (env.MMI_UPDATER_ELEVATED_RETRY === "1") return false;
|
|
2217
2427
|
const self = process.argv[1];
|
|
2218
2428
|
const command = self ? `"${process.execPath}" "${self}" autoupdate ${mode}` : `mmi-hub autoupdate ${mode}`;
|
|
2219
|
-
const relaunch = (0,
|
|
2429
|
+
const relaunch = (0, import_node_child_process11.spawnSync)(
|
|
2220
2430
|
"powershell.exe",
|
|
2221
2431
|
["-NoProfile", "-Command", `Start-Process -Verb RunAs -Wait -WindowStyle Hidden -FilePath 'cmd.exe' -ArgumentList '/c',${psQuote(`set MMI_UPDATER_ELEVATED_RETRY=1&& ${command}`)}`],
|
|
2222
2432
|
{ encoding: "utf8", windowsHide: true }
|
|
@@ -2227,7 +2437,7 @@ var NEVER_RUN_RESULT = 267011;
|
|
|
2227
2437
|
var NEVER_RUN_YEAR = 1999;
|
|
2228
2438
|
var EMPTY_RUN_INFO = { lastRunTime: null, lastTaskResult: null, nextRunTime: null };
|
|
2229
2439
|
function taskRunInfo() {
|
|
2230
|
-
const result = (0,
|
|
2440
|
+
const result = (0, import_node_child_process11.spawnSync)(
|
|
2231
2441
|
"powershell.exe",
|
|
2232
2442
|
["-NoProfile", "-Command", `$stamp = { param($t) if ($t) { $t.ToString('yyyy-MM-dd HH:mm:ss') } }; Get-ScheduledTaskInfo -TaskName '${TASK_NAME}' | Select-Object @{n='LastRunTime';e={& $stamp $_.LastRunTime}},LastTaskResult,@{n='NextRunTime';e={& $stamp $_.NextRunTime}} | ConvertTo-Json -Compress`],
|
|
2233
2443
|
{ encoding: "utf8", windowsHide: true, timeout: 3e4 }
|
|
@@ -2253,7 +2463,7 @@ function windowsSchedulerStatus(env) {
|
|
|
2253
2463
|
return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${TASK_NAME}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
|
|
2254
2464
|
}
|
|
2255
2465
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2256
|
-
const launcherPath = (0,
|
|
2466
|
+
const launcherPath = (0, import_node_path13.join)(statePaths(env).root, LAUNCHER_FILE);
|
|
2257
2467
|
const info = taskRunInfo();
|
|
2258
2468
|
const runInfo = info ?? EMPTY_RUN_INFO;
|
|
2259
2469
|
const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...runInfo, detail });
|
|
@@ -2278,13 +2488,13 @@ function windowsSchedulerStatus(env) {
|
|
|
2278
2488
|
return { ok: true, supported: true, enabled: true, ...runInfo, detail: `"${TASK_NAME}" registered ${CADENCE}; ${points}; ${ran}; next ${runInfo.nextRunTime ?? "unscheduled"}` };
|
|
2279
2489
|
}
|
|
2280
2490
|
function darwinSchedulerStatus(env) {
|
|
2281
|
-
const plistPath = (0,
|
|
2491
|
+
const plistPath = (0, import_node_path13.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
|
|
2282
2492
|
const plist = readFileOrNull(plistPath);
|
|
2283
2493
|
if (!plist) {
|
|
2284
2494
|
return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
|
|
2285
2495
|
}
|
|
2286
2496
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2287
|
-
const launcherPath = (0,
|
|
2497
|
+
const launcherPath = (0, import_node_path13.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
|
|
2288
2498
|
const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
|
|
2289
2499
|
if (!plist.includes(launcherPath)) {
|
|
2290
2500
|
return fail(`"${LAUNCHD_LABEL}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
@@ -2304,15 +2514,15 @@ function darwinSchedulerStatus(env) {
|
|
|
2304
2514
|
return { ok: true, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" registered ${POSIX_CADENCE}; ${points}` };
|
|
2305
2515
|
}
|
|
2306
2516
|
function linuxSchedulerStatus(env) {
|
|
2307
|
-
const unitDir = (0,
|
|
2308
|
-
const timerPath = (0,
|
|
2517
|
+
const unitDir = (0, import_node_path13.join)((0, import_node_os5.homedir)(), ".config", "systemd", "user");
|
|
2518
|
+
const timerPath = (0, import_node_path13.join)(unitDir, SYSTEMD_TIMER_NAME);
|
|
2309
2519
|
const timer = readFileOrNull(timerPath);
|
|
2310
|
-
const service = readFileOrNull((0,
|
|
2520
|
+
const service = readFileOrNull((0, import_node_path13.join)(unitDir, SYSTEMD_SERVICE_NAME));
|
|
2311
2521
|
if (!timer || !service) {
|
|
2312
2522
|
return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${SYSTEMD_TIMER_NAME}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
|
|
2313
2523
|
}
|
|
2314
2524
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2315
|
-
const launcherPath = (0,
|
|
2525
|
+
const launcherPath = (0, import_node_path13.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
|
|
2316
2526
|
const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
|
|
2317
2527
|
if (!service.includes(launcherPath)) {
|
|
2318
2528
|
return fail(`"${SYSTEMD_SERVICE_NAME}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
@@ -2351,10 +2561,10 @@ function installWindowsTask(env) {
|
|
|
2351
2561
|
const paths = ensureState(env);
|
|
2352
2562
|
const userId = `${env.USERDOMAIN || env.COMPUTERNAME || ""}\\${env.USERNAME || ""}`.replace(/^\\/, "");
|
|
2353
2563
|
if (!env.USERNAME) return { ok: false, detail: "cannot resolve the registering user (USERNAME unset) \u2014 the task principal must be explicit" };
|
|
2354
|
-
const launcherPath = (0,
|
|
2355
|
-
(0,
|
|
2356
|
-
const xmlPath = (0,
|
|
2357
|
-
(0,
|
|
2564
|
+
const launcherPath = (0, import_node_path13.join)(paths.root, LAUNCHER_FILE);
|
|
2565
|
+
(0, import_node_fs15.writeFileSync)(launcherPath, launcherVbs(process.execPath, hubDist), "utf8");
|
|
2566
|
+
const xmlPath = (0, import_node_path13.join)(paths.root, "task.xml");
|
|
2567
|
+
(0, import_node_fs15.writeFileSync)(xmlPath, "\uFEFF" + taskXml(launcherPath, paths.root, userId, localStartBoundary(/* @__PURE__ */ new Date())), "utf16le");
|
|
2358
2568
|
const create = schtasks(["/create", "/tn", TASK_NAME, "/xml", xmlPath, "/f"]);
|
|
2359
2569
|
if (create.status !== 0) {
|
|
2360
2570
|
const denied = /access is denied/i.test(create.stderr + create.stdout);
|
|
@@ -2370,11 +2580,11 @@ function refreshPosixLauncher(env) {
|
|
|
2370
2580
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2371
2581
|
if (!hubDist) return null;
|
|
2372
2582
|
const paths = ensureState(env);
|
|
2373
|
-
const launcherPath = (0,
|
|
2583
|
+
const launcherPath = (0, import_node_path13.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2374
2584
|
const desired = launcherSh(process.execPath, hubDist);
|
|
2375
2585
|
if (readFileOrNull(launcherPath) === desired) return null;
|
|
2376
|
-
(0,
|
|
2377
|
-
(0,
|
|
2586
|
+
(0, import_node_fs15.writeFileSync)(launcherPath, desired, "utf8");
|
|
2587
|
+
(0, import_node_fs15.chmodSync)(launcherPath, 493);
|
|
2378
2588
|
return launcherPath;
|
|
2379
2589
|
}
|
|
2380
2590
|
function installLaunchd(env) {
|
|
@@ -2386,13 +2596,13 @@ function installLaunchd(env) {
|
|
|
2386
2596
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2387
2597
|
if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
|
|
2388
2598
|
const paths = ensureState(env);
|
|
2389
|
-
const launcherPath = (0,
|
|
2390
|
-
(0,
|
|
2391
|
-
(0,
|
|
2392
|
-
const agentsDir = (0,
|
|
2393
|
-
(0,
|
|
2394
|
-
const plistPath = (0,
|
|
2395
|
-
(0,
|
|
2599
|
+
const launcherPath = (0, import_node_path13.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2600
|
+
(0, import_node_fs15.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
|
|
2601
|
+
(0, import_node_fs15.chmodSync)(launcherPath, 493);
|
|
2602
|
+
const agentsDir = (0, import_node_path13.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents");
|
|
2603
|
+
(0, import_node_fs15.mkdirSync)(agentsDir, { recursive: true });
|
|
2604
|
+
const plistPath = (0, import_node_path13.join)(agentsDir, LAUNCHD_PLIST_NAME);
|
|
2605
|
+
(0, import_node_fs15.writeFileSync)(plistPath, launchdPlist(launcherPath, paths.root), "utf8");
|
|
2396
2606
|
launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
2397
2607
|
const load = launchctl(["bootstrap", `gui/${uid()}`, plistPath]);
|
|
2398
2608
|
if (load.status !== 0) {
|
|
@@ -2410,13 +2620,13 @@ function installSystemdTimer(env) {
|
|
|
2410
2620
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2411
2621
|
if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
|
|
2412
2622
|
const paths = ensureState(env);
|
|
2413
|
-
const launcherPath = (0,
|
|
2414
|
-
(0,
|
|
2415
|
-
(0,
|
|
2416
|
-
const unitDir = (0,
|
|
2417
|
-
(0,
|
|
2418
|
-
(0,
|
|
2419
|
-
(0,
|
|
2623
|
+
const launcherPath = (0, import_node_path13.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2624
|
+
(0, import_node_fs15.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
|
|
2625
|
+
(0, import_node_fs15.chmodSync)(launcherPath, 493);
|
|
2626
|
+
const unitDir = (0, import_node_path13.join)((0, import_node_os5.homedir)(), ".config", "systemd", "user");
|
|
2627
|
+
(0, import_node_fs15.mkdirSync)(unitDir, { recursive: true });
|
|
2628
|
+
(0, import_node_fs15.writeFileSync)((0, import_node_path13.join)(unitDir, SYSTEMD_SERVICE_NAME), systemdServiceUnit(launcherPath, paths.root), "utf8");
|
|
2629
|
+
(0, import_node_fs15.writeFileSync)((0, import_node_path13.join)(unitDir, SYSTEMD_TIMER_NAME), systemdTimerUnit(), "utf8");
|
|
2420
2630
|
const reload = systemctlUser(["daemon-reload"]);
|
|
2421
2631
|
if (reload.status !== 0) {
|
|
2422
2632
|
return { ok: false, detail: `systemctl --user daemon-reload failed: ${(reload.stderr || reload.stdout).trim() || "unknown error \u2014 is a user systemd instance running?"}` };
|
|
@@ -2454,28 +2664,28 @@ function uninstallWindowsTask(env) {
|
|
|
2454
2664
|
return { ok: true, detail: `automatic updates off \u2014 removed "${TASK_NAME}"; installed tooling was kept` };
|
|
2455
2665
|
}
|
|
2456
2666
|
function uninstallLaunchd(env) {
|
|
2457
|
-
const plistPath = (0,
|
|
2667
|
+
const plistPath = (0, import_node_path13.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
|
|
2458
2668
|
const boot = launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
2459
|
-
if (boot.status !== 0 && (0,
|
|
2669
|
+
if (boot.status !== 0 && (0, import_node_fs15.existsSync)(plistPath)) {
|
|
2460
2670
|
return { ok: false, detail: `launchctl bootout failed: ${(boot.stderr || boot.stdout).trim() || "unknown error"}` };
|
|
2461
2671
|
}
|
|
2462
2672
|
try {
|
|
2463
|
-
(0,
|
|
2673
|
+
(0, import_node_fs15.rmSync)(plistPath, { force: true });
|
|
2464
2674
|
} catch {
|
|
2465
2675
|
}
|
|
2466
2676
|
dropLauncher(env, POSIX_LAUNCHER_FILE);
|
|
2467
2677
|
return { ok: true, detail: `automatic updates off \u2014 removed "${LAUNCHD_LABEL}"; installed tooling was kept` };
|
|
2468
2678
|
}
|
|
2469
2679
|
function uninstallSystemdTimer(env) {
|
|
2470
|
-
const unitDir = (0,
|
|
2471
|
-
const timerPath = (0,
|
|
2680
|
+
const unitDir = (0, import_node_path13.join)((0, import_node_os5.homedir)(), ".config", "systemd", "user");
|
|
2681
|
+
const timerPath = (0, import_node_path13.join)(unitDir, SYSTEMD_TIMER_NAME);
|
|
2472
2682
|
const disable = systemctlUser(["disable", "--now", SYSTEMD_TIMER_NAME]);
|
|
2473
|
-
if (disable.status !== 0 && (0,
|
|
2683
|
+
if (disable.status !== 0 && (0, import_node_fs15.existsSync)(timerPath)) {
|
|
2474
2684
|
return { ok: false, detail: `systemctl --user disable --now ${SYSTEMD_TIMER_NAME} failed: ${(disable.stderr || disable.stdout).trim() || "unknown error"}` };
|
|
2475
2685
|
}
|
|
2476
2686
|
try {
|
|
2477
|
-
(0,
|
|
2478
|
-
(0,
|
|
2687
|
+
(0, import_node_fs15.rmSync)(timerPath, { force: true });
|
|
2688
|
+
(0, import_node_fs15.rmSync)((0, import_node_path13.join)(unitDir, SYSTEMD_SERVICE_NAME), { force: true });
|
|
2479
2689
|
} catch {
|
|
2480
2690
|
}
|
|
2481
2691
|
systemctlUser(["daemon-reload"]);
|
|
@@ -2490,16 +2700,16 @@ function uninstallTask(env = process.env) {
|
|
|
2490
2700
|
}
|
|
2491
2701
|
function dropLauncher(env, file) {
|
|
2492
2702
|
try {
|
|
2493
|
-
(0,
|
|
2703
|
+
(0, import_node_fs15.rmSync)((0, import_node_path13.join)(statePaths(env).root, file), { force: true });
|
|
2494
2704
|
} catch {
|
|
2495
2705
|
}
|
|
2496
2706
|
}
|
|
2497
2707
|
|
|
2498
2708
|
// src/status.ts
|
|
2499
|
-
var
|
|
2709
|
+
var import_node_fs16 = require("node:fs");
|
|
2500
2710
|
function readJournal(path) {
|
|
2501
2711
|
try {
|
|
2502
|
-
const events = (0,
|
|
2712
|
+
const events = (0, import_node_fs16.readFileSync)(path, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
2503
2713
|
try {
|
|
2504
2714
|
return JSON.parse(line);
|
|
2505
2715
|
} catch {
|
|
@@ -2618,6 +2828,7 @@ var YELLOW = "\x1B[33m";
|
|
|
2618
2828
|
var RED = "\x1B[31m";
|
|
2619
2829
|
var RESET = "\x1B[0m";
|
|
2620
2830
|
var RETRY_HINTS = [
|
|
2831
|
+
[/applies when the host exits|applies on a quiet tick/, "installs when the app closes"],
|
|
2621
2832
|
[/in flight|changed while/, "rerun update \u2014 host race"],
|
|
2622
2833
|
[/is running|live process|inside a .* hook/, "close app, rerun update"],
|
|
2623
2834
|
[/staging install failed|etarget|e404|network|fetch failed/, "check network/login, rerun"],
|
|
@@ -2876,7 +3087,7 @@ function startSpinner(enabled) {
|
|
|
2876
3087
|
// src/index.ts
|
|
2877
3088
|
function ownVersion() {
|
|
2878
3089
|
try {
|
|
2879
|
-
return JSON.parse((0,
|
|
3090
|
+
return JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path14.join)(__dirname, "..", "package.json"), "utf8")).version;
|
|
2880
3091
|
} catch {
|
|
2881
3092
|
return "0.0.0";
|
|
2882
3093
|
}
|
|
@@ -3002,6 +3213,8 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3002
3213
|
}
|
|
3003
3214
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3004
3215
|
0 && (module.exports = {
|
|
3216
|
+
buildWatcherCommand,
|
|
3217
|
+
clearPending,
|
|
3005
3218
|
enumerateSurfaces,
|
|
3006
3219
|
failConsequence,
|
|
3007
3220
|
formatReconcileReport,
|
|
@@ -3012,18 +3225,26 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3012
3225
|
launcherSh,
|
|
3013
3226
|
launcherVbs,
|
|
3014
3227
|
localStartBoundary,
|
|
3228
|
+
lockShaped,
|
|
3015
3229
|
main,
|
|
3230
|
+
noteBusyDefer,
|
|
3016
3231
|
ownVersion,
|
|
3232
|
+
pendingPath,
|
|
3233
|
+
pidAlive,
|
|
3017
3234
|
probeHermesInstallation,
|
|
3018
3235
|
progressiveRow,
|
|
3236
|
+
readPending,
|
|
3019
3237
|
reconcileCursorUserHooks,
|
|
3020
3238
|
reportFooterLines,
|
|
3021
3239
|
retryHint,
|
|
3240
|
+
schedulePendingWatcher,
|
|
3022
3241
|
schedulerStatus,
|
|
3023
3242
|
startSpinner,
|
|
3024
3243
|
systemdServiceUnit,
|
|
3025
3244
|
systemdTimerUnit,
|
|
3026
3245
|
taskXml,
|
|
3027
3246
|
updateHeader,
|
|
3028
|
-
|
|
3247
|
+
watcherLockPath,
|
|
3248
|
+
wrapWords,
|
|
3249
|
+
writePending
|
|
3029
3250
|
});
|