@mutmutco/hub 4.0.15 → 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 +460 -243
- 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");
|
|
@@ -400,16 +410,160 @@ function enumerateSurfaces(env = process.env) {
|
|
|
400
410
|
}
|
|
401
411
|
|
|
402
412
|
// src/arm-cli.ts
|
|
403
|
-
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
|
|
404
418
|
var import_node_child_process3 = require("node:child_process");
|
|
419
|
+
var import_node_fs5 = require("node:fs");
|
|
405
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
|
|
406
560
|
function globalCliDist(env) {
|
|
407
561
|
const root = globalRoot(env);
|
|
408
|
-
return root ? (0,
|
|
562
|
+
return root ? (0, import_node_path5.join)(root, "@mutmutco", "cli", "dist", "index.cjs") : null;
|
|
409
563
|
}
|
|
410
564
|
function probeCliVersion(distPath) {
|
|
411
565
|
if (!fileExists(distPath)) return null;
|
|
412
|
-
const probe = (0,
|
|
566
|
+
const probe = (0, import_node_child_process4.spawnSync)(process.execPath, [distPath, "--version"], { encoding: "utf8", windowsHide: true });
|
|
413
567
|
const version = (probe.stdout || "").trim();
|
|
414
568
|
return probe.status === 0 && /^\d+\.\d+\.\d+/.test(version) ? version : null;
|
|
415
569
|
}
|
|
@@ -436,11 +590,11 @@ function cliArm(options) {
|
|
|
436
590
|
if (dryRun) {
|
|
437
591
|
return { surface: "cli", from: installed, to: target, verdict: "ok", detail: `dry-run: would converge ${installed ?? "absent"} -> ${target}` };
|
|
438
592
|
}
|
|
439
|
-
const stageRoot = (0,
|
|
440
|
-
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");
|
|
441
595
|
if (!fileExists(stageDist)) {
|
|
442
|
-
(0,
|
|
443
|
-
(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");
|
|
444
598
|
const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `@mutmutco/cli@${target}`], env);
|
|
445
599
|
if (install.status !== 0) {
|
|
446
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(" | ")}` };
|
|
@@ -448,7 +602,7 @@ function cliArm(options) {
|
|
|
448
602
|
}
|
|
449
603
|
let stagedManifestVersion;
|
|
450
604
|
try {
|
|
451
|
-
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;
|
|
452
606
|
} catch {
|
|
453
607
|
}
|
|
454
608
|
if (stagedManifestVersion !== target) {
|
|
@@ -460,7 +614,10 @@ function cliArm(options) {
|
|
|
460
614
|
}
|
|
461
615
|
const globalInstall = runNpm(["install", "-g", "--no-audit", "--no-fund", `@mutmutco/cli@${target}`], env);
|
|
462
616
|
if (globalInstall.status !== 0) {
|
|
463
|
-
|
|
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}` };
|
|
464
621
|
}
|
|
465
622
|
const switchedDist = globalCliDist(env);
|
|
466
623
|
const switched = probeCliVersion(switchedDist ?? "");
|
|
@@ -471,16 +628,16 @@ function cliArm(options) {
|
|
|
471
628
|
}
|
|
472
629
|
|
|
473
630
|
// src/arm-npm-global.ts
|
|
474
|
-
var
|
|
475
|
-
var
|
|
476
|
-
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");
|
|
477
634
|
function globalDist(env, packageName, distRel) {
|
|
478
635
|
const root = globalRoot(env);
|
|
479
|
-
return root ? (0,
|
|
636
|
+
return root ? (0, import_node_path6.join)(root, ...packageName.split("/"), ...distRel.split("/")) : null;
|
|
480
637
|
}
|
|
481
638
|
function probeVersion(distPath) {
|
|
482
639
|
if (!fileExists(distPath)) return null;
|
|
483
|
-
const probe = (0,
|
|
640
|
+
const probe = (0, import_node_child_process5.spawnSync)(process.execPath, [distPath, "--version"], { encoding: "utf8", windowsHide: true });
|
|
484
641
|
const version = (probe.stdout || "").trim();
|
|
485
642
|
return probe.status === 0 && /^\d+\.\d+\.\d+/.test(version) ? version : null;
|
|
486
643
|
}
|
|
@@ -489,11 +646,11 @@ function tail(output) {
|
|
|
489
646
|
}
|
|
490
647
|
function stagePackage(options) {
|
|
491
648
|
const { surface, packageName, target, witness, paths, env } = options;
|
|
492
|
-
const stageRoot = (0,
|
|
493
|
-
const packageRoot = (0,
|
|
494
|
-
if (!fileExists((0,
|
|
495
|
-
(0,
|
|
496
|
-
(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");
|
|
497
654
|
const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `${packageName}@${target}`], env);
|
|
498
655
|
if (install.status !== 0) {
|
|
499
656
|
return { defer: `staging install failed: ${tail(install.stderr || install.stdout)}` };
|
|
@@ -501,7 +658,7 @@ function stagePackage(options) {
|
|
|
501
658
|
}
|
|
502
659
|
let stagedManifestVersion;
|
|
503
660
|
try {
|
|
504
|
-
stagedManifestVersion = JSON.parse((0,
|
|
661
|
+
stagedManifestVersion = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path6.join)(packageRoot, "package.json"), "utf8")).version;
|
|
505
662
|
} catch {
|
|
506
663
|
}
|
|
507
664
|
if (stagedManifestVersion !== target) {
|
|
@@ -533,14 +690,17 @@ function npmGlobalArm(options) {
|
|
|
533
690
|
if ("defer" in staged) {
|
|
534
691
|
return result(installed, target, "defer", staged.defer);
|
|
535
692
|
}
|
|
536
|
-
const stageDist = (0,
|
|
693
|
+
const stageDist = (0, import_node_path6.join)(staged.packageRoot, ...distRel.split("/"));
|
|
537
694
|
const stagedVersion = probeVersion(stageDist);
|
|
538
695
|
if (stagedVersion !== target) {
|
|
539
696
|
return result(installed, target, "defer", `staged binary probed ${stagedVersion ?? "dead"}, expected ${target}`);
|
|
540
697
|
}
|
|
541
698
|
const globalInstall = runNpm(["install", "-g", "--no-audit", "--no-fund", spec], env);
|
|
542
699
|
if (globalInstall.status !== 0) {
|
|
543
|
-
|
|
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}`);
|
|
544
704
|
}
|
|
545
705
|
const switchedDist = globalDist(env, packageName, distRel);
|
|
546
706
|
const switched = probeVersion(switchedDist ?? "");
|
|
@@ -561,10 +721,10 @@ function selfArm(options) {
|
|
|
561
721
|
}
|
|
562
722
|
|
|
563
723
|
// src/arm-host-plugins.ts
|
|
564
|
-
var
|
|
565
|
-
var
|
|
724
|
+
var import_node_child_process6 = require("node:child_process");
|
|
725
|
+
var import_node_fs8 = require("node:fs");
|
|
566
726
|
var import_node_os3 = require("node:os");
|
|
567
|
-
var
|
|
727
|
+
var import_node_path7 = require("node:path");
|
|
568
728
|
var REPO_URL_DEFAULT = "https://github.com/mutmutco/MMI-Hub.git";
|
|
569
729
|
var MMI_MARKETPLACE_REPO = "mutmutco/MMI-Hub";
|
|
570
730
|
function convergedAtOrAbove(installed, target, healthy) {
|
|
@@ -574,13 +734,13 @@ function cmdArg(value) {
|
|
|
574
734
|
return /^[A-Za-z0-9_@./:\\=+-]+$/.test(value) ? value : `"${value.replaceAll('"', '""')}"`;
|
|
575
735
|
}
|
|
576
736
|
var runHostCommand = (command, args, env) => {
|
|
577
|
-
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(" ")], {
|
|
578
738
|
encoding: "utf8",
|
|
579
739
|
env,
|
|
580
740
|
input: "",
|
|
581
741
|
timeout: 12e4,
|
|
582
742
|
windowsHide: true
|
|
583
|
-
}) : (0,
|
|
743
|
+
}) : (0, import_node_child_process6.spawnSync)(command, args, {
|
|
584
744
|
encoding: "utf8",
|
|
585
745
|
env,
|
|
586
746
|
input: "",
|
|
@@ -607,7 +767,7 @@ function claudeMmi(runner, env) {
|
|
|
607
767
|
return { row: parsed.find((row) => row.id === "mmi@mutmutco") ?? null };
|
|
608
768
|
}
|
|
609
769
|
function claudeMarketplacesPath(env) {
|
|
610
|
-
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");
|
|
611
771
|
}
|
|
612
772
|
var CLAUDE_PROCESS_SEGMENT = /^claude(-code|-cli)?(\.(exe|cmd|bat|ps1|js|mjs|cjs|py))?$/;
|
|
613
773
|
function claudeProcessSegment(line) {
|
|
@@ -621,7 +781,7 @@ function claudeProcessSegment(line) {
|
|
|
621
781
|
function claudeHostRunning(env) {
|
|
622
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;
|
|
623
783
|
if (marker) return { running: true, evidence: `${marker} is set \u2014 this process is itself inside a Claude hook` };
|
|
624
|
-
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 });
|
|
625
785
|
if (result.status !== 0 || !(result.stdout ?? "").trim()) return { running: true, evidence: "the process list is unreadable \u2014 unsafe to write" };
|
|
626
786
|
for (const line of result.stdout.split(/\r?\n/)) {
|
|
627
787
|
const segment = claudeProcessSegment(line);
|
|
@@ -633,7 +793,7 @@ function ensureClaudeSingleWriter(env, dryRun) {
|
|
|
633
793
|
const path = claudeMarketplacesPath(env);
|
|
634
794
|
let body;
|
|
635
795
|
try {
|
|
636
|
-
body = JSON.parse((0,
|
|
796
|
+
body = JSON.parse((0, import_node_fs8.readFileSync)(path, "utf8"));
|
|
637
797
|
} catch (error) {
|
|
638
798
|
if (error?.code === "ENOENT") return { ok: false, detail: `${path} does not exist \u2014 mutmutco marketplace is not registered`, unregistered: true };
|
|
639
799
|
return { ok: false, detail: `cannot read ${path}` };
|
|
@@ -645,7 +805,10 @@ function ensureClaudeSingleWriter(env, dryRun) {
|
|
|
645
805
|
if (registeredRepo !== MMI_MARKETPLACE_REPO) {
|
|
646
806
|
return {
|
|
647
807
|
ok: false,
|
|
648
|
-
|
|
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`
|
|
649
812
|
};
|
|
650
813
|
}
|
|
651
814
|
if (registration.autoUpdate !== true) return { ok: true };
|
|
@@ -657,9 +820,9 @@ function ensureClaudeSingleWriter(env, dryRun) {
|
|
|
657
820
|
registration.autoUpdate = false;
|
|
658
821
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
659
822
|
try {
|
|
660
|
-
(0,
|
|
661
|
-
(0,
|
|
662
|
-
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"));
|
|
663
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" };
|
|
664
827
|
} catch (error) {
|
|
665
828
|
return { ok: false, detail: `cannot disable Claude background autoUpdate: ${error?.message ?? error}` };
|
|
@@ -773,11 +936,11 @@ function codexArm(options) {
|
|
|
773
936
|
}
|
|
774
937
|
var KILO_PACKAGE = "@mutmutco/kilo-plugin";
|
|
775
938
|
function kiloConfigPath(env) {
|
|
776
|
-
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");
|
|
777
940
|
}
|
|
778
941
|
function readKiloConfig(env) {
|
|
779
942
|
try {
|
|
780
|
-
const config = JSON.parse((0,
|
|
943
|
+
const config = JSON.parse((0, import_node_fs8.readFileSync)(kiloConfigPath(env), "utf8"));
|
|
781
944
|
const specs = Array.isArray(config.plugin) && config.plugin.every((entry) => typeof entry === "string") ? config.plugin : [];
|
|
782
945
|
return { config, specs };
|
|
783
946
|
} catch {
|
|
@@ -797,8 +960,8 @@ function normalizeKiloConfig(env, exact) {
|
|
|
797
960
|
const path = kiloConfigPath(env);
|
|
798
961
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
799
962
|
try {
|
|
800
|
-
(0,
|
|
801
|
-
(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);
|
|
802
965
|
return null;
|
|
803
966
|
} catch (error) {
|
|
804
967
|
return `cannot retire legacy Kilo registrations: ${error?.message ?? error}`;
|
|
@@ -861,15 +1024,15 @@ function kiloArm(options) {
|
|
|
861
1024
|
}
|
|
862
1025
|
|
|
863
1026
|
// src/arm-kimi.ts
|
|
864
|
-
var
|
|
865
|
-
var
|
|
866
|
-
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");
|
|
867
1030
|
|
|
868
1031
|
// src/compat.ts
|
|
869
|
-
var
|
|
1032
|
+
var import_node_fs9 = require("node:fs");
|
|
870
1033
|
function readManifestCompat(manifestPath) {
|
|
871
1034
|
try {
|
|
872
|
-
const parsed = JSON.parse((0,
|
|
1035
|
+
const parsed = JSON.parse((0, import_node_fs9.readFileSync)(manifestPath, "utf8"));
|
|
873
1036
|
return typeof parsed.mmiCompat === "string" ? parsed.mmiCompat : void 0;
|
|
874
1037
|
} catch {
|
|
875
1038
|
return void 0;
|
|
@@ -880,11 +1043,11 @@ function readManifestCompat(manifestPath) {
|
|
|
880
1043
|
var KIMI_PACKAGE = "@mutmutco/kimi-plugin";
|
|
881
1044
|
var TREE_MARKERS = [".kimi-plugin/plugin.json", "skills/mmi/SKILL.md", "scripts/hook-run.mjs"];
|
|
882
1045
|
function treeHealthy(root) {
|
|
883
|
-
return TREE_MARKERS.every((rel) => fileExists((0,
|
|
1046
|
+
return TREE_MARKERS.every((rel) => fileExists((0, import_node_path8.join)(root, ...rel.split("/"))));
|
|
884
1047
|
}
|
|
885
1048
|
function markerVersion(root) {
|
|
886
1049
|
try {
|
|
887
|
-
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;
|
|
888
1051
|
return typeof version === "string" ? version : null;
|
|
889
1052
|
} catch {
|
|
890
1053
|
return null;
|
|
@@ -892,14 +1055,14 @@ function markerVersion(root) {
|
|
|
892
1055
|
}
|
|
893
1056
|
function packageVersion(root) {
|
|
894
1057
|
try {
|
|
895
|
-
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;
|
|
896
1059
|
return typeof version === "string" ? version : null;
|
|
897
1060
|
} catch {
|
|
898
1061
|
return null;
|
|
899
1062
|
}
|
|
900
1063
|
}
|
|
901
1064
|
function probeKimiInstallation(env = process.env) {
|
|
902
|
-
const location = (0,
|
|
1065
|
+
const location = (0, import_node_path8.join)(kimiHome(env), "plugins", "managed", "mmi");
|
|
903
1066
|
const version = markerVersion(location);
|
|
904
1067
|
return { version, location, detail: version ? "installed Kimi plugin manifest" : "installed Kimi plugin manifest was unreadable" };
|
|
905
1068
|
}
|
|
@@ -907,14 +1070,14 @@ function tail2(output) {
|
|
|
907
1070
|
return output.split("\n").filter(Boolean).slice(-3).join(" | ");
|
|
908
1071
|
}
|
|
909
1072
|
function scratchRoot(env) {
|
|
910
|
-
return (0,
|
|
1073
|
+
return (0, import_node_path8.join)(kimiHome(env), ".mmi-updater");
|
|
911
1074
|
}
|
|
912
1075
|
function pruneScratchDir(root, keep) {
|
|
913
1076
|
try {
|
|
914
|
-
for (const name of (0,
|
|
1077
|
+
for (const name of (0, import_node_fs10.readdirSync)(root)) {
|
|
915
1078
|
if (name === keep) continue;
|
|
916
1079
|
try {
|
|
917
|
-
(0,
|
|
1080
|
+
(0, import_node_fs10.rmSync)((0, import_node_path8.join)(root, name), { recursive: true, force: true });
|
|
918
1081
|
} catch {
|
|
919
1082
|
}
|
|
920
1083
|
}
|
|
@@ -930,7 +1093,7 @@ function kimiProcessToken(line) {
|
|
|
930
1093
|
}
|
|
931
1094
|
function kimiHostRunning(env) {
|
|
932
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" };
|
|
933
|
-
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 });
|
|
934
1097
|
if (result.status !== 0 || !(result.stdout ?? "").trim()) return { running: true, evidence: "the process list is unreadable \u2014 unsafe to write" };
|
|
935
1098
|
for (const line of result.stdout.split(/\r?\n/)) {
|
|
936
1099
|
const token = kimiProcessToken(line);
|
|
@@ -940,11 +1103,11 @@ function kimiHostRunning(env) {
|
|
|
940
1103
|
}
|
|
941
1104
|
function restoreQuarantine(quarantine, live) {
|
|
942
1105
|
try {
|
|
943
|
-
(0,
|
|
1106
|
+
(0, import_node_fs10.renameSync)(quarantine, live);
|
|
944
1107
|
} catch (error) {
|
|
945
1108
|
return { restored: false, detail: `${error?.message ?? error}` };
|
|
946
1109
|
}
|
|
947
|
-
if (!(0,
|
|
1110
|
+
if (!(0, import_node_fs10.existsSync)(live)) return { restored: false, detail: `${live} is still absent after the restore rename` };
|
|
948
1111
|
return { restored: true, detail: `marker ${markerVersion(live) ?? "unreadable"}` };
|
|
949
1112
|
}
|
|
950
1113
|
function kimiArm(options) {
|
|
@@ -958,10 +1121,10 @@ function kimiArm(options) {
|
|
|
958
1121
|
detail,
|
|
959
1122
|
// C11: the constraint of the tree LEFT at `live` — read at result time, so ok carries the new
|
|
960
1123
|
// manifest's declaration and defer/fail carry the previous tree's (#4973 reconsult ruling).
|
|
961
|
-
compat: readManifestCompat((0,
|
|
1124
|
+
compat: readManifestCompat((0, import_node_path8.join)(live, ".kimi-plugin", "plugin.json"))
|
|
962
1125
|
});
|
|
963
|
-
const managedParent = (0,
|
|
964
|
-
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");
|
|
965
1128
|
const installed = markerVersion(live);
|
|
966
1129
|
if (installed && compareSemver(installed, target) > 0) {
|
|
967
1130
|
return result(installed, installed, "skip", `installed ${installed} is above candidate ${target} (monotonic)`);
|
|
@@ -976,11 +1139,11 @@ function kimiArm(options) {
|
|
|
976
1139
|
if (dryRun) {
|
|
977
1140
|
return result(installed, target, "ok", `dry-run: would converge ${installed ?? "absent"} -> ${target} (tarball generation switch while Kimi is absent)`);
|
|
978
1141
|
}
|
|
979
|
-
const stageRoot = (0,
|
|
980
|
-
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("/"));
|
|
981
1144
|
if (!treeHealthy(staged)) {
|
|
982
|
-
(0,
|
|
983
|
-
(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");
|
|
984
1147
|
const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `${KIMI_PACKAGE}@${target}`], env);
|
|
985
1148
|
if (install.status !== 0) {
|
|
986
1149
|
return result(installed, target, "defer", `staging install failed: ${tail2(install.stderr || install.stdout)}`);
|
|
@@ -999,75 +1162,79 @@ function kimiArm(options) {
|
|
|
999
1162
|
}
|
|
1000
1163
|
const quiescence = kimiHostRunning(env);
|
|
1001
1164
|
if (quiescence.running) {
|
|
1002
|
-
|
|
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}` : ""}`);
|
|
1003
1167
|
}
|
|
1004
1168
|
const scratch = scratchRoot(env);
|
|
1005
|
-
const incomingRoot = (0,
|
|
1006
|
-
const incoming = (0,
|
|
1007
|
-
const quarantineRoot = (0,
|
|
1008
|
-
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");
|
|
1009
1173
|
try {
|
|
1010
|
-
(0,
|
|
1011
|
-
(0,
|
|
1012
|
-
(0,
|
|
1013
|
-
(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 });
|
|
1014
1178
|
} catch (error) {
|
|
1015
1179
|
return result(installed, target, "defer", `cannot place the incoming generation under ${scratch}: ${error?.message ?? error}`);
|
|
1016
1180
|
}
|
|
1017
1181
|
if (markerVersion(incoming) !== target || !treeHealthy(incoming)) {
|
|
1018
1182
|
return result(installed, target, "defer", `incoming generation verifies as ${markerVersion(incoming) ?? "unreadable"}, expected ${target}`);
|
|
1019
1183
|
}
|
|
1020
|
-
const hadLive = (0,
|
|
1184
|
+
const hadLive = (0, import_node_fs10.existsSync)(live);
|
|
1021
1185
|
const generation = `${installed ?? "unknown"}-${Date.now()}`;
|
|
1022
|
-
const quarantine = (0,
|
|
1186
|
+
const quarantine = (0, import_node_path8.join)(quarantineRoot, generation);
|
|
1023
1187
|
if (hadLive) {
|
|
1024
1188
|
try {
|
|
1025
|
-
(0,
|
|
1189
|
+
(0, import_node_fs10.renameSync)(live, quarantine);
|
|
1026
1190
|
} catch (error) {
|
|
1027
|
-
|
|
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}`);
|
|
1028
1194
|
}
|
|
1029
1195
|
}
|
|
1030
1196
|
try {
|
|
1031
|
-
(0,
|
|
1197
|
+
(0, import_node_fs10.renameSync)(incoming, live);
|
|
1032
1198
|
} catch (error) {
|
|
1033
1199
|
const failed = `cannot switch the new generation in: ${error?.message ?? error}`;
|
|
1034
1200
|
if (!hadLive) {
|
|
1035
1201
|
return result(installed, target, "defer", `${failed} \u2014 no generation was displaced; ${live} is still absent`);
|
|
1036
1202
|
}
|
|
1037
1203
|
const restore = restoreQuarantine(quarantine, live);
|
|
1038
|
-
|
|
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}`);
|
|
1039
1206
|
}
|
|
1040
1207
|
const switched = markerVersion(live);
|
|
1041
1208
|
if (switched !== target || !treeHealthy(live)) {
|
|
1042
1209
|
const observed = `post-switch marker is ${switched ?? "unreadable"}, expected ${target}`;
|
|
1043
1210
|
const rejectedName = `${target}-${Date.now()}`;
|
|
1044
1211
|
try {
|
|
1045
|
-
(0,
|
|
1046
|
-
(0,
|
|
1212
|
+
(0, import_node_fs10.mkdirSync)(rejectedRoot, { recursive: true });
|
|
1213
|
+
(0, import_node_fs10.renameSync)(live, (0, import_node_path8.join)(rejectedRoot, rejectedName));
|
|
1047
1214
|
} catch (error) {
|
|
1048
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}` : ""}`);
|
|
1049
1216
|
}
|
|
1050
1217
|
pruneScratchDir(rejectedRoot, rejectedName);
|
|
1051
1218
|
if (!hadLive) {
|
|
1052
|
-
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)`);
|
|
1053
1220
|
}
|
|
1054
1221
|
const restore = restoreQuarantine(quarantine, live);
|
|
1055
|
-
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}`);
|
|
1056
1223
|
}
|
|
1057
1224
|
pruneScratchDir(quarantineRoot, hadLive ? generation : null);
|
|
1058
1225
|
pruneScratchDir(rejectedRoot, null);
|
|
1059
1226
|
try {
|
|
1060
|
-
(0,
|
|
1227
|
+
(0, import_node_fs10.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
1061
1228
|
} catch {
|
|
1062
1229
|
}
|
|
1063
1230
|
return result(installed, target, "ok", `converged ${installed ?? "absent"} -> ${target} (staged, verified, switched${hadLive ? `; previous generation quarantined at ${quarantine}` : ""})`);
|
|
1064
1231
|
}
|
|
1065
1232
|
|
|
1066
1233
|
// src/arm-cursor.ts
|
|
1067
|
-
var
|
|
1068
|
-
var
|
|
1234
|
+
var import_node_fs11 = require("node:fs");
|
|
1235
|
+
var import_node_child_process8 = require("node:child_process");
|
|
1069
1236
|
var import_node_os4 = require("node:os");
|
|
1070
|
-
var
|
|
1237
|
+
var import_node_path9 = require("node:path");
|
|
1071
1238
|
var PACKAGE = "@mutmutco/cursor-plugin";
|
|
1072
1239
|
var MANIFEST = ".cursor-plugin/plugin.json";
|
|
1073
1240
|
var USER_HOOKS_FILE = "hooks.json";
|
|
@@ -1076,14 +1243,14 @@ var KEEP_GENERATIONS = 2;
|
|
|
1076
1243
|
var STRANDED_TMP_AGE_MS = 24 * 60 * 6e4;
|
|
1077
1244
|
var TREE_FILES = [MANIFEST, "skills/mmi/SKILL.md", "hooks/cursor-hooks.json", "scripts/hook-run.mjs", "scripts/hook-policy.mjs"];
|
|
1078
1245
|
function cursorPluginsRoot() {
|
|
1079
|
-
return (0,
|
|
1246
|
+
return (0, import_node_path9.join)((0, import_node_os4.homedir)(), ".cursor", "plugins");
|
|
1080
1247
|
}
|
|
1081
1248
|
function treeHealthy2(root) {
|
|
1082
|
-
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("/"))));
|
|
1083
1250
|
}
|
|
1084
1251
|
function readJson(root, rel) {
|
|
1085
1252
|
try {
|
|
1086
|
-
return JSON.parse((0,
|
|
1253
|
+
return JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path9.join)(root, ...rel.split("/")), "utf8"));
|
|
1087
1254
|
} catch {
|
|
1088
1255
|
return null;
|
|
1089
1256
|
}
|
|
@@ -1092,7 +1259,7 @@ function manifest(root) {
|
|
|
1092
1259
|
return readJson(root, MANIFEST);
|
|
1093
1260
|
}
|
|
1094
1261
|
function probeCursorInstallation() {
|
|
1095
|
-
const location = (0,
|
|
1262
|
+
const location = (0, import_node_path9.join)(cursorPluginsRoot(), "local", "mmi");
|
|
1096
1263
|
const version = manifest(location)?.version ?? null;
|
|
1097
1264
|
return { version, location, detail: version ? "installed Cursor plugin manifest" : "installed Cursor plugin manifest was unreadable" };
|
|
1098
1265
|
}
|
|
@@ -1107,7 +1274,7 @@ function message(error) {
|
|
|
1107
1274
|
}
|
|
1108
1275
|
function discard(path) {
|
|
1109
1276
|
try {
|
|
1110
|
-
(0,
|
|
1277
|
+
(0, import_node_fs11.rmSync)(path, { recursive: true, force: true });
|
|
1111
1278
|
} catch {
|
|
1112
1279
|
}
|
|
1113
1280
|
}
|
|
@@ -1126,7 +1293,7 @@ function cursorHostEvidence(env) {
|
|
|
1126
1293
|
if (env[marker]?.trim()) return `env marker ${marker}`;
|
|
1127
1294
|
}
|
|
1128
1295
|
if (env.CURSOR_AGENT === "1") return "env marker CURSOR_AGENT=1";
|
|
1129
|
-
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 });
|
|
1130
1297
|
if (result.status !== 0 || !(result.stdout ?? "").trim()) return "the process table is unreadable (unsafe to write)";
|
|
1131
1298
|
for (const line of result.stdout.split(/\r?\n/)) {
|
|
1132
1299
|
const executable = cursorExecutable(line);
|
|
@@ -1135,7 +1302,7 @@ function cursorHostEvidence(env) {
|
|
|
1135
1302
|
return null;
|
|
1136
1303
|
}
|
|
1137
1304
|
function resolvedPluginRoot(root) {
|
|
1138
|
-
return (0,
|
|
1305
|
+
return (0, import_node_path9.join)(root).replace(/\\/g, "/");
|
|
1139
1306
|
}
|
|
1140
1307
|
function isMmiEntry(entry, pluginRoot) {
|
|
1141
1308
|
return typeof entry === "object" && entry !== null && typeof entry.command === "string" && entry.command.includes(resolvedPluginRoot(pluginRoot));
|
|
@@ -1145,7 +1312,7 @@ function resolveCommand(command, pluginRoot) {
|
|
|
1145
1312
|
}
|
|
1146
1313
|
function desiredUserHooks(pluginRoot) {
|
|
1147
1314
|
try {
|
|
1148
|
-
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"));
|
|
1149
1316
|
const hooks = doc.hooks;
|
|
1150
1317
|
if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) return null;
|
|
1151
1318
|
const desired = /* @__PURE__ */ new Map();
|
|
@@ -1181,14 +1348,14 @@ function mergeUserHooks(existing, desired, pluginRoot) {
|
|
|
1181
1348
|
function reconcileCursorUserHooks(pluginRoot, homeDir) {
|
|
1182
1349
|
const desired = desiredUserHooks(pluginRoot);
|
|
1183
1350
|
if (!desired) return "source-unreadable";
|
|
1184
|
-
const path = (0,
|
|
1185
|
-
if (!(0,
|
|
1186
|
-
(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");
|
|
1187
1354
|
return "updated";
|
|
1188
1355
|
}
|
|
1189
1356
|
let existing;
|
|
1190
1357
|
try {
|
|
1191
|
-
existing = JSON.parse((0,
|
|
1358
|
+
existing = JSON.parse((0, import_node_fs11.readFileSync)(path, "utf8"));
|
|
1192
1359
|
} catch {
|
|
1193
1360
|
return "user-conflict";
|
|
1194
1361
|
}
|
|
@@ -1196,8 +1363,8 @@ function reconcileCursorUserHooks(pluginRoot, homeDir) {
|
|
|
1196
1363
|
if (!merged) return "user-conflict";
|
|
1197
1364
|
if (JSON.stringify(merged) === JSON.stringify(existing)) return "current";
|
|
1198
1365
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
1199
|
-
(0,
|
|
1200
|
-
(0,
|
|
1366
|
+
(0, import_node_fs11.writeFileSync)(tmp, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
1367
|
+
(0, import_node_fs11.renameSync)(tmp, path);
|
|
1201
1368
|
return "updated";
|
|
1202
1369
|
}
|
|
1203
1370
|
function userHooksDetail(pluginRoot, homeDir) {
|
|
@@ -1205,7 +1372,7 @@ function userHooksDetail(pluginRoot, homeDir) {
|
|
|
1205
1372
|
case "updated":
|
|
1206
1373
|
return "; user hooks bridged into ~/.cursor/hooks.json";
|
|
1207
1374
|
case "user-conflict":
|
|
1208
|
-
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`;
|
|
1209
1376
|
case "source-unreadable":
|
|
1210
1377
|
return "; user hooks source unreadable \u2014 Cursor gates are NOT bridged";
|
|
1211
1378
|
default:
|
|
@@ -1213,35 +1380,35 @@ function userHooksDetail(pluginRoot, homeDir) {
|
|
|
1213
1380
|
}
|
|
1214
1381
|
}
|
|
1215
1382
|
function writeRollbackPointer(root, entry) {
|
|
1216
|
-
const path = (0,
|
|
1383
|
+
const path = (0, import_node_path9.join)(root, "quarantine", "rollback.json");
|
|
1217
1384
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
1218
1385
|
try {
|
|
1219
|
-
(0,
|
|
1220
|
-
(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);
|
|
1221
1388
|
} catch {
|
|
1222
1389
|
discard(tmp);
|
|
1223
1390
|
}
|
|
1224
1391
|
}
|
|
1225
1392
|
function mtimeMs(path) {
|
|
1226
1393
|
try {
|
|
1227
|
-
return (0,
|
|
1394
|
+
return (0, import_node_fs11.statSync)(path).mtimeMs;
|
|
1228
1395
|
} catch {
|
|
1229
1396
|
return 0;
|
|
1230
1397
|
}
|
|
1231
1398
|
}
|
|
1232
1399
|
function pruneQuarantine(root, keep) {
|
|
1233
|
-
const dir = (0,
|
|
1400
|
+
const dir = (0, import_node_path9.join)(root, "quarantine");
|
|
1234
1401
|
let generations;
|
|
1235
1402
|
try {
|
|
1236
|
-
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));
|
|
1237
1404
|
} catch {
|
|
1238
1405
|
return 0;
|
|
1239
1406
|
}
|
|
1240
1407
|
let stranded = 0;
|
|
1241
1408
|
try {
|
|
1242
|
-
for (const entry of (0,
|
|
1409
|
+
for (const entry of (0, import_node_fs11.readdirSync)(dir, { withFileTypes: true })) {
|
|
1243
1410
|
if (!entry.isFile() || !/^rollback\.json\.tmp-\d+$/.test(entry.name)) continue;
|
|
1244
|
-
const path = (0,
|
|
1411
|
+
const path = (0, import_node_path9.join)(dir, entry.name);
|
|
1245
1412
|
if (Date.now() - mtimeMs(path) < STRANDED_TMP_AGE_MS) continue;
|
|
1246
1413
|
discard(path);
|
|
1247
1414
|
stranded += 1;
|
|
@@ -1259,16 +1426,16 @@ function pruneQuarantine(root, keep) {
|
|
|
1259
1426
|
}
|
|
1260
1427
|
function restorePrevious(live, quarantined, broken) {
|
|
1261
1428
|
try {
|
|
1262
|
-
(0,
|
|
1429
|
+
(0, import_node_fs11.renameSync)(live, broken);
|
|
1263
1430
|
} catch {
|
|
1264
1431
|
return "kept-broken";
|
|
1265
1432
|
}
|
|
1266
1433
|
try {
|
|
1267
|
-
(0,
|
|
1434
|
+
(0, import_node_fs11.renameSync)(quarantined, live);
|
|
1268
1435
|
return "restored";
|
|
1269
1436
|
} catch {
|
|
1270
1437
|
try {
|
|
1271
|
-
(0,
|
|
1438
|
+
(0, import_node_fs11.renameSync)(broken, live);
|
|
1272
1439
|
return "kept-broken";
|
|
1273
1440
|
} catch {
|
|
1274
1441
|
return "lost";
|
|
@@ -1279,7 +1446,7 @@ function cursorArm(options) {
|
|
|
1279
1446
|
const env = options.env ?? process.env;
|
|
1280
1447
|
const { target, dryRun, paths } = options;
|
|
1281
1448
|
const root = cursorPluginsRoot();
|
|
1282
|
-
const live = (0,
|
|
1449
|
+
const live = (0, import_node_path9.join)(root, "local", "mmi");
|
|
1283
1450
|
const result = (from, to, verdict, detail) => ({
|
|
1284
1451
|
surface: "cursor",
|
|
1285
1452
|
from,
|
|
@@ -1288,7 +1455,7 @@ function cursorArm(options) {
|
|
|
1288
1455
|
detail,
|
|
1289
1456
|
// C11: the constraint of the generation LEFT at `live` — read at result time, so a failed
|
|
1290
1457
|
// switch reports the restored generation's declaration, not the rejected one's (#4973).
|
|
1291
|
-
compat: readManifestCompat((0,
|
|
1458
|
+
compat: readManifestCompat((0, import_node_path9.join)(live, MANIFEST))
|
|
1292
1459
|
});
|
|
1293
1460
|
const current = manifest(live);
|
|
1294
1461
|
const installed = current?.version ?? null;
|
|
@@ -1302,7 +1469,7 @@ function cursorArm(options) {
|
|
|
1302
1469
|
if (installed === target && treeHealthy2(live)) {
|
|
1303
1470
|
return result(installed, target, "ok", `already at target ${target}${userHooksDetail(live, (0, import_node_os4.homedir)())}`);
|
|
1304
1471
|
}
|
|
1305
|
-
if ((0,
|
|
1472
|
+
if ((0, import_node_fs11.existsSync)(live) && !mmiOwned(live)) {
|
|
1306
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)`);
|
|
1307
1474
|
}
|
|
1308
1475
|
if (dryRun) {
|
|
@@ -1318,15 +1485,16 @@ function cursorArm(options) {
|
|
|
1318
1485
|
}
|
|
1319
1486
|
const busy = cursorHostEvidence(env);
|
|
1320
1487
|
if (busy) {
|
|
1321
|
-
|
|
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}`);
|
|
1322
1490
|
}
|
|
1323
1491
|
const stamp = Date.now();
|
|
1324
|
-
const incoming = (0,
|
|
1325
|
-
const quarantined = (0,
|
|
1326
|
-
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 });
|
|
1327
1495
|
try {
|
|
1328
1496
|
discard(incoming);
|
|
1329
|
-
(0,
|
|
1497
|
+
(0, import_node_fs11.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
1330
1498
|
} catch (error) {
|
|
1331
1499
|
discard(incoming);
|
|
1332
1500
|
return result(installed, target, "defer", `copy into ${root} failed: ${message(error)}`);
|
|
@@ -1338,28 +1506,31 @@ function cursorArm(options) {
|
|
|
1338
1506
|
const started = cursorHostEvidence(env);
|
|
1339
1507
|
if (started) {
|
|
1340
1508
|
discard(incoming);
|
|
1341
|
-
|
|
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}`);
|
|
1342
1511
|
}
|
|
1343
1512
|
let displaced = false;
|
|
1344
1513
|
try {
|
|
1345
|
-
if ((0,
|
|
1346
|
-
(0,
|
|
1514
|
+
if ((0, import_node_fs11.existsSync)(live)) {
|
|
1515
|
+
(0, import_node_fs11.renameSync)(live, quarantined);
|
|
1347
1516
|
displaced = true;
|
|
1348
1517
|
}
|
|
1349
|
-
(0,
|
|
1518
|
+
(0, import_node_fs11.renameSync)(incoming, live);
|
|
1350
1519
|
} catch (error) {
|
|
1351
|
-
if (displaced && !(0,
|
|
1520
|
+
if (displaced && !(0, import_node_fs11.existsSync)(live)) {
|
|
1352
1521
|
try {
|
|
1353
|
-
(0,
|
|
1522
|
+
(0, import_node_fs11.renameSync)(quarantined, live);
|
|
1354
1523
|
} catch {
|
|
1355
1524
|
}
|
|
1356
1525
|
}
|
|
1357
1526
|
discard(incoming);
|
|
1358
|
-
|
|
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}`);
|
|
1359
1530
|
}
|
|
1360
1531
|
const evidence = manifest(live)?.version;
|
|
1361
1532
|
if (evidence !== target || !treeHealthy2(live)) {
|
|
1362
|
-
const broken = `${(0,
|
|
1533
|
+
const broken = `${(0, import_node_path9.join)(root, "quarantine", `mmi-${safeSegment(evidence ?? "broken")}-${stamp}`)}-failed`;
|
|
1363
1534
|
const outcome = displaced ? restorePrevious(live, quarantined, broken) : "none";
|
|
1364
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";
|
|
1365
1536
|
writeRollbackPointer(root, outcome === "restored" ? { installed: installed ?? "unknown", previous: null, generation: null } : { installed: evidence ?? "unknown", previous: installed, generation: displaced ? quarantined : null });
|
|
@@ -1372,8 +1543,8 @@ function cursorArm(options) {
|
|
|
1372
1543
|
}
|
|
1373
1544
|
|
|
1374
1545
|
// src/arm-jervcode.ts
|
|
1375
|
-
var
|
|
1376
|
-
var
|
|
1546
|
+
var import_node_fs12 = require("node:fs");
|
|
1547
|
+
var import_node_path10 = require("node:path");
|
|
1377
1548
|
var PI_PACKAGE = "@mutmutco/pi-plugin";
|
|
1378
1549
|
function hostEnv(env) {
|
|
1379
1550
|
const dir = jervcodeAgentDir(env);
|
|
@@ -1384,7 +1555,7 @@ function hostCommands(_env) {
|
|
|
1384
1555
|
}
|
|
1385
1556
|
function readPackageSpecs(env) {
|
|
1386
1557
|
try {
|
|
1387
|
-
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"));
|
|
1388
1559
|
return Array.isArray(settings.packages) ? settings.packages.filter((entry) => typeof entry === "string") : [];
|
|
1389
1560
|
} catch {
|
|
1390
1561
|
return null;
|
|
@@ -1392,14 +1563,14 @@ function readPackageSpecs(env) {
|
|
|
1392
1563
|
}
|
|
1393
1564
|
function installedVersion(env) {
|
|
1394
1565
|
try {
|
|
1395
|
-
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"));
|
|
1396
1567
|
return typeof manifest2.version === "string" ? manifest2.version : null;
|
|
1397
1568
|
} catch {
|
|
1398
1569
|
return null;
|
|
1399
1570
|
}
|
|
1400
1571
|
}
|
|
1401
1572
|
function probeJervcodeInstallation(env = process.env) {
|
|
1402
|
-
const location = (0,
|
|
1573
|
+
const location = (0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"));
|
|
1403
1574
|
const version = installedVersion(env);
|
|
1404
1575
|
return { version, location, detail: version ? "materialized pi package manifest" : "materialized pi package manifest was unreadable" };
|
|
1405
1576
|
}
|
|
@@ -1421,32 +1592,32 @@ function jervcodeArm(options) {
|
|
|
1421
1592
|
detail,
|
|
1422
1593
|
// C11: the constraint of the package pi LEFT materialised — read at result time, so defer/fail
|
|
1423
1594
|
// carry the previous payload's declaration (#4973 reconsult ruling).
|
|
1424
|
-
compat: readManifestCompat((0,
|
|
1595
|
+
compat: readManifestCompat((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"))
|
|
1425
1596
|
});
|
|
1426
1597
|
const before = readPackageSpecs(env);
|
|
1427
|
-
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")}`);
|
|
1428
1599
|
const from = installedVersion(env);
|
|
1429
1600
|
const legacy = before.filter(isLegacyMmiPiPath);
|
|
1430
1601
|
const highWater = journalHighWater(options.paths.journalPath, "jervcode");
|
|
1431
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;
|
|
1432
1603
|
if (options.dryRun) {
|
|
1433
1604
|
const action = floor ? `hold ${from ?? "the install"} and skip the install (${floor})` : before.includes(exact) && from === options.target ? "keep exact pin" : `install ${exact}`;
|
|
1434
|
-
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")) };
|
|
1435
1606
|
}
|
|
1436
1607
|
const hosted = hostEnv(env);
|
|
1437
1608
|
const commands = hostCommands(env);
|
|
1438
1609
|
const command = commands.find((bin) => runner(bin, ["--version"], hosted).status === 0);
|
|
1439
|
-
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")) };
|
|
1440
1611
|
if (!floor && (!before.includes(exact) || from !== options.target)) {
|
|
1441
1612
|
const install = runner(command, ["install", exact], hosted);
|
|
1442
1613
|
if (install.status !== 0) {
|
|
1443
|
-
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")) };
|
|
1444
1615
|
}
|
|
1445
1616
|
}
|
|
1446
1617
|
for (const stale of legacy) {
|
|
1447
1618
|
const removed = runner(command, ["remove", stale], hosted);
|
|
1448
1619
|
if (removed.status !== 0) {
|
|
1449
|
-
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")) };
|
|
1450
1621
|
}
|
|
1451
1622
|
}
|
|
1452
1623
|
const after = readPackageSpecs(env);
|
|
@@ -1454,7 +1625,7 @@ function jervcodeArm(options) {
|
|
|
1454
1625
|
const legacyAfter = after?.filter(isLegacyMmiPiPath) ?? [];
|
|
1455
1626
|
const retired = legacy.length ? `; retired ${legacy.length} legacy claude-cache path registration${legacy.length === 1 ? "" : "s"}` : "";
|
|
1456
1627
|
if (floor && !legacyAfter.length) {
|
|
1457
|
-
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")) };
|
|
1458
1629
|
}
|
|
1459
1630
|
if (!after?.includes(exact) || converged !== options.target || legacyAfter.length) {
|
|
1460
1631
|
return {
|
|
@@ -1475,14 +1646,15 @@ function jervcodeArm(options) {
|
|
|
1475
1646
|
}
|
|
1476
1647
|
|
|
1477
1648
|
// src/arm-hermes.ts
|
|
1478
|
-
var
|
|
1479
|
-
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");
|
|
1480
1652
|
var PACKAGE2 = "@mutmutco/hermes-plugin";
|
|
1481
1653
|
var MANIFEST2 = "plugin.yaml";
|
|
1482
1654
|
var TREE_MARKERS2 = [MANIFEST2, "__init__.py", "skills"];
|
|
1483
1655
|
function yamlVersion(root) {
|
|
1484
1656
|
try {
|
|
1485
|
-
const text = (0,
|
|
1657
|
+
const text = (0, import_node_fs13.readFileSync)((0, import_node_path11.join)(root, MANIFEST2), "utf8");
|
|
1486
1658
|
try {
|
|
1487
1659
|
const version = JSON.parse(text).version;
|
|
1488
1660
|
if (typeof version === "string" && version.trim()) return version.trim();
|
|
@@ -1495,9 +1667,9 @@ function yamlVersion(root) {
|
|
|
1495
1667
|
}
|
|
1496
1668
|
}
|
|
1497
1669
|
function treeHealthy3(root) {
|
|
1498
|
-
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))) && (() => {
|
|
1499
1671
|
try {
|
|
1500
|
-
return (0,
|
|
1672
|
+
return (0, import_node_fs13.statSync)((0, import_node_path11.join)(root, "skills")).isDirectory();
|
|
1501
1673
|
} catch {
|
|
1502
1674
|
return false;
|
|
1503
1675
|
}
|
|
@@ -1505,13 +1677,13 @@ function treeHealthy3(root) {
|
|
|
1505
1677
|
}
|
|
1506
1678
|
function packageIdentity(root) {
|
|
1507
1679
|
try {
|
|
1508
|
-
return JSON.parse((0,
|
|
1680
|
+
return JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path11.join)(root, "package.json"), "utf8")).name === PACKAGE2;
|
|
1509
1681
|
} catch {
|
|
1510
1682
|
return false;
|
|
1511
1683
|
}
|
|
1512
1684
|
}
|
|
1513
1685
|
function probeHermesInstallation(env = process.env) {
|
|
1514
|
-
const location = (0,
|
|
1686
|
+
const location = (0, import_node_path11.join)(hermesHome(env), "plugins", "mmi");
|
|
1515
1687
|
const version = yamlVersion(location);
|
|
1516
1688
|
return { version, location, detail: version ? "installed Hermes plugin manifest" : "installed Hermes plugin manifest was unreadable" };
|
|
1517
1689
|
}
|
|
@@ -1520,27 +1692,45 @@ function mmiOwned2(root) {
|
|
|
1520
1692
|
}
|
|
1521
1693
|
function discard2(path) {
|
|
1522
1694
|
try {
|
|
1523
|
-
(0,
|
|
1695
|
+
(0, import_node_fs13.rmSync)(path, { recursive: true, force: true });
|
|
1524
1696
|
} catch {
|
|
1525
1697
|
}
|
|
1526
1698
|
}
|
|
1527
1699
|
function message2(error) {
|
|
1528
1700
|
return String(error?.message ?? error).replace(/\s+/g, " ").slice(0, 200);
|
|
1529
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
|
+
}
|
|
1530
1720
|
function materializeHermesSkills(home) {
|
|
1531
|
-
const pluginSkills = (0,
|
|
1532
|
-
const skillsRoot = (0,
|
|
1533
|
-
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");
|
|
1534
1724
|
try {
|
|
1535
|
-
if (!(0,
|
|
1536
|
-
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);
|
|
1537
1727
|
if (!names.length) return { ok: false, detail: `plugin skills tree is empty at ${pluginSkills}` };
|
|
1538
|
-
(0,
|
|
1539
|
-
const incoming = (0,
|
|
1728
|
+
(0, import_node_fs13.mkdirSync)(skillsRoot, { recursive: true });
|
|
1729
|
+
const incoming = (0, import_node_path11.join)(skillsRoot, ".mmi-incoming");
|
|
1540
1730
|
discard2(incoming);
|
|
1541
|
-
(0,
|
|
1731
|
+
(0, import_node_fs13.cpSync)(pluginSkills, incoming, { recursive: true });
|
|
1542
1732
|
discard2(targetRoot);
|
|
1543
|
-
(0,
|
|
1733
|
+
(0, import_node_fs13.renameSync)(incoming, targetRoot);
|
|
1544
1734
|
return { ok: true, detail: `provisioned ${names.length} skills to ${targetRoot}` };
|
|
1545
1735
|
} catch (error) {
|
|
1546
1736
|
return { ok: false, detail: message2(error) };
|
|
@@ -1549,8 +1739,8 @@ function materializeHermesSkills(home) {
|
|
|
1549
1739
|
function hermesArm(options) {
|
|
1550
1740
|
const env = options.env ?? process.env;
|
|
1551
1741
|
const home = hermesHome(env);
|
|
1552
|
-
const parent = (0,
|
|
1553
|
-
const live = (0,
|
|
1742
|
+
const parent = (0, import_node_path11.join)(home, "plugins");
|
|
1743
|
+
const live = (0, import_node_path11.join)(parent, "mmi");
|
|
1554
1744
|
const installed = yamlVersion(live);
|
|
1555
1745
|
const result = (from, to, verdict, detail) => ({
|
|
1556
1746
|
surface: "hermes",
|
|
@@ -1559,8 +1749,8 @@ function hermesArm(options) {
|
|
|
1559
1749
|
verdict,
|
|
1560
1750
|
detail
|
|
1561
1751
|
});
|
|
1562
|
-
if (!(0,
|
|
1563
|
-
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)) {
|
|
1564
1754
|
return result(installed, options.target, "defer", `${live} carries no ${PACKAGE2} package identity \u2014 refusing to displace an unmanaged Hermes mmi plugin`);
|
|
1565
1755
|
}
|
|
1566
1756
|
if (installed && compareSemver(installed, options.target) > 0) return result(installed, installed, "skip", `installed ${installed} is above candidate ${options.target} (monotonic)`);
|
|
@@ -1571,21 +1761,21 @@ function hermesArm(options) {
|
|
|
1571
1761
|
if (!skills2.ok) return result(installed, options.target, "fail", `plugin at target ${options.target}, but skills provisioning failed: ${skills2.detail}`);
|
|
1572
1762
|
return result(installed, options.target, "ok", `already at target ${options.target}; ${skills2.detail}`);
|
|
1573
1763
|
}
|
|
1574
|
-
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")}`);
|
|
1575
1765
|
const staged = stagePackage({ surface: "hermes", packageName: PACKAGE2, target: options.target, witness: MANIFEST2, paths: options.paths, env });
|
|
1576
1766
|
if ("defer" in staged) return result(installed, options.target, "defer", staged.defer);
|
|
1577
1767
|
if (yamlVersion(staged.packageRoot) !== options.target || !treeHealthy3(staged.packageRoot)) {
|
|
1578
1768
|
return result(installed, options.target, "defer", `staged Hermes manifest is ${yamlVersion(staged.packageRoot) ?? "unreadable"} or its tree is incomplete (expected ${options.target})`);
|
|
1579
1769
|
}
|
|
1580
|
-
const scratch = (0,
|
|
1581
|
-
const incomingRoot = (0,
|
|
1582
|
-
const incoming = (0,
|
|
1583
|
-
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()}`);
|
|
1584
1774
|
try {
|
|
1585
|
-
(0,
|
|
1586
|
-
(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 });
|
|
1587
1777
|
discard2(incomingRoot);
|
|
1588
|
-
(0,
|
|
1778
|
+
(0, import_node_fs13.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
1589
1779
|
} catch (error) {
|
|
1590
1780
|
return result(installed, options.target, "defer", `cannot place incoming Hermes generation: ${message2(error)}`);
|
|
1591
1781
|
}
|
|
@@ -1593,24 +1783,32 @@ function hermesArm(options) {
|
|
|
1593
1783
|
discard2(incomingRoot);
|
|
1594
1784
|
return result(installed, options.target, "defer", "incoming Hermes generation did not reproduce the verified payload");
|
|
1595
1785
|
}
|
|
1596
|
-
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);
|
|
1597
1793
|
try {
|
|
1598
|
-
if (displaced) (0,
|
|
1599
|
-
(0,
|
|
1794
|
+
if (displaced) (0, import_node_fs13.renameSync)(live, quarantine);
|
|
1795
|
+
(0, import_node_fs13.renameSync)(incoming, live);
|
|
1600
1796
|
} catch (error) {
|
|
1601
|
-
if (displaced && !(0,
|
|
1602
|
-
(0,
|
|
1797
|
+
if (displaced && !(0, import_node_fs13.existsSync)(live)) try {
|
|
1798
|
+
(0, import_node_fs13.renameSync)(quarantine, live);
|
|
1603
1799
|
} catch {
|
|
1604
1800
|
}
|
|
1605
1801
|
discard2(incomingRoot);
|
|
1606
|
-
|
|
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}`);
|
|
1607
1805
|
}
|
|
1608
1806
|
if (yamlVersion(live) !== options.target || !treeHealthy3(live)) {
|
|
1609
|
-
const broken = (0,
|
|
1807
|
+
const broken = (0, import_node_path11.join)(scratch, "rejected", `${options.target}-${Date.now()}`);
|
|
1610
1808
|
try {
|
|
1611
|
-
(0,
|
|
1612
|
-
(0,
|
|
1613
|
-
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);
|
|
1614
1812
|
} catch (error) {
|
|
1615
1813
|
return result(installed, yamlVersion(live) ?? "unknown", "fail", `post-switch Hermes verification failed and recovery failed: ${message2(error)}`);
|
|
1616
1814
|
}
|
|
@@ -1623,9 +1821,9 @@ function hermesArm(options) {
|
|
|
1623
1821
|
}
|
|
1624
1822
|
|
|
1625
1823
|
// src/reap.ts
|
|
1626
|
-
var
|
|
1627
|
-
var
|
|
1628
|
-
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");
|
|
1629
1827
|
var KEEP_GENERATIONS2 = 2;
|
|
1630
1828
|
var SCRATCH_AGE_MS = 24 * 60 * 6e4;
|
|
1631
1829
|
var ORPHAN_MIN_AGE_MS = 10 * 6e4;
|
|
@@ -1633,7 +1831,7 @@ var SNAPSHOT_TIMEOUT_MS = 2e4;
|
|
|
1633
1831
|
var MAX_UNWIND_ROUNDS = 4;
|
|
1634
1832
|
function listDirs(path) {
|
|
1635
1833
|
try {
|
|
1636
|
-
return (0,
|
|
1834
|
+
return (0, import_node_fs14.readdirSync)(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
1637
1835
|
} catch {
|
|
1638
1836
|
return [];
|
|
1639
1837
|
}
|
|
@@ -1641,7 +1839,7 @@ function listDirs(path) {
|
|
|
1641
1839
|
function generationSets(root) {
|
|
1642
1840
|
const children = listDirs(root);
|
|
1643
1841
|
if (children.some((name) => parseSemver(name))) return [root];
|
|
1644
|
-
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)));
|
|
1645
1843
|
}
|
|
1646
1844
|
function pruneGenerations(root, category, dryRun) {
|
|
1647
1845
|
let reaped = 0;
|
|
@@ -1654,7 +1852,7 @@ function pruneGenerations(root, category, dryRun) {
|
|
|
1654
1852
|
continue;
|
|
1655
1853
|
}
|
|
1656
1854
|
try {
|
|
1657
|
-
(0,
|
|
1855
|
+
(0, import_node_fs14.rmSync)((0, import_node_path12.join)(set, version), { recursive: true, force: true });
|
|
1658
1856
|
reaped += 1;
|
|
1659
1857
|
} catch (error) {
|
|
1660
1858
|
failures.push(`${version}: ${error?.code ?? error?.message ?? error}`);
|
|
@@ -1668,8 +1866,8 @@ function pruneGenerations(root, category, dryRun) {
|
|
|
1668
1866
|
}
|
|
1669
1867
|
var LEASE_STEAL_SCRATCH = /^lease\.lock\.stale-\d+$/;
|
|
1670
1868
|
function atomicWriteScratch(path) {
|
|
1671
|
-
const literal = (0,
|
|
1672
|
-
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+$`) };
|
|
1673
1871
|
}
|
|
1674
1872
|
function reapScratch(context, dryRun) {
|
|
1675
1873
|
const category = "scratch";
|
|
@@ -1687,7 +1885,7 @@ function reapScratch(context, dryRun) {
|
|
|
1687
1885
|
for (const [dir, patterns] of scanned) {
|
|
1688
1886
|
let entries;
|
|
1689
1887
|
try {
|
|
1690
|
-
entries = (0,
|
|
1888
|
+
entries = (0, import_node_fs14.readdirSync)(dir, { withFileTypes: true });
|
|
1691
1889
|
} catch {
|
|
1692
1890
|
unreadable.push(dir);
|
|
1693
1891
|
continue;
|
|
@@ -1695,10 +1893,10 @@ function reapScratch(context, dryRun) {
|
|
|
1695
1893
|
for (const entry of entries) {
|
|
1696
1894
|
if (!patterns.some((pattern) => pattern.test(entry.name))) continue;
|
|
1697
1895
|
if (!entry.isFile()) continue;
|
|
1698
|
-
const path = (0,
|
|
1896
|
+
const path = (0, import_node_path12.join)(dir, entry.name);
|
|
1699
1897
|
let ageMs;
|
|
1700
1898
|
try {
|
|
1701
|
-
ageMs = now - (0,
|
|
1899
|
+
ageMs = now - (0, import_node_fs14.statSync)(path).mtimeMs;
|
|
1702
1900
|
} catch {
|
|
1703
1901
|
continue;
|
|
1704
1902
|
}
|
|
@@ -1708,7 +1906,7 @@ function reapScratch(context, dryRun) {
|
|
|
1708
1906
|
continue;
|
|
1709
1907
|
}
|
|
1710
1908
|
try {
|
|
1711
|
-
(0,
|
|
1909
|
+
(0, import_node_fs14.rmSync)(path, { force: true });
|
|
1712
1910
|
reaped += 1;
|
|
1713
1911
|
} catch (error) {
|
|
1714
1912
|
failures.push(`${entry.name}: ${error?.code ?? error?.message ?? error}`);
|
|
@@ -1824,7 +2022,7 @@ function stillTheProvenProcess(rows, kill) {
|
|
|
1824
2022
|
}
|
|
1825
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";
|
|
1826
2024
|
function processSnapshot() {
|
|
1827
|
-
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 });
|
|
1828
2026
|
if (result.status !== 0 || !(result.stdout ?? "").trim()) return null;
|
|
1829
2027
|
try {
|
|
1830
2028
|
const parsed = JSON.parse(result.stdout);
|
|
@@ -1948,12 +2146,20 @@ function reconcile(options) {
|
|
|
1948
2146
|
if (arm.verdict === "fail") arm.repeatFailure = journalSurfaceRepeatedFail(paths.journalPath, arm.surface, run);
|
|
1949
2147
|
journal({ kind: "arm", surface: arm.surface, from: arm.from, to: arm.to, verdict: arm.verdict, detail: arm.detail, compat: arm.compat });
|
|
1950
2148
|
options.onArm?.(arm);
|
|
2149
|
+
if (arm.verdict === "ok" && !options.dryRun) clearPending(paths, arm.surface);
|
|
1951
2150
|
if (arm.verdict === "defer") summary.exit = Math.max(summary.exit, 2);
|
|
1952
2151
|
if (arm.verdict === "fail") summary.exit = Math.max(summary.exit, 3);
|
|
1953
2152
|
};
|
|
1954
2153
|
try {
|
|
1955
2154
|
say("acquiring single-writer lease");
|
|
1956
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
|
+
}
|
|
1957
2163
|
summary.reap = reap({ paths, dryRun: options.dryRun, env });
|
|
1958
2164
|
for (const row of summary.reap) journal({ kind: "reap", surface: row.category, verdict: row.verdict, count: row.reaped, detail: row.detail });
|
|
1959
2165
|
for (const row of summary.reap.filter((entry) => entry.verdict !== "skip")) say(`reap ${row.category}: ${row.verdict}${row.detail ? " \u2014 " + row.detail : ""}`);
|
|
@@ -2047,10 +2253,10 @@ function reconcile(options) {
|
|
|
2047
2253
|
}
|
|
2048
2254
|
|
|
2049
2255
|
// src/scheduler.ts
|
|
2050
|
-
var
|
|
2051
|
-
var
|
|
2256
|
+
var import_node_child_process11 = require("node:child_process");
|
|
2257
|
+
var import_node_fs15 = require("node:fs");
|
|
2052
2258
|
var import_node_os5 = require("node:os");
|
|
2053
|
-
var
|
|
2259
|
+
var import_node_path13 = require("node:path");
|
|
2054
2260
|
var TASK_NAME = "MMI Fleet Updater";
|
|
2055
2261
|
var LAUNCHER_FILE = "reconcile-hidden.vbs";
|
|
2056
2262
|
var RUN_BOUND = "PT15M";
|
|
@@ -2066,15 +2272,15 @@ var SYSTEMD_UNIT = "mmi-hub-updater";
|
|
|
2066
2272
|
var SYSTEMD_SERVICE_NAME = `${SYSTEMD_UNIT}.service`;
|
|
2067
2273
|
var SYSTEMD_TIMER_NAME = `${SYSTEMD_UNIT}.timer`;
|
|
2068
2274
|
function schtasks(args) {
|
|
2069
|
-
const result = (0,
|
|
2275
|
+
const result = (0, import_node_child_process11.spawnSync)("schtasks.exe", args, { encoding: "utf8", windowsHide: true });
|
|
2070
2276
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
2071
2277
|
}
|
|
2072
2278
|
function launchctl(args) {
|
|
2073
|
-
const result = (0,
|
|
2279
|
+
const result = (0, import_node_child_process11.spawnSync)("launchctl", args, { encoding: "utf8", windowsHide: true });
|
|
2074
2280
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
2075
2281
|
}
|
|
2076
2282
|
function systemctlUser(args) {
|
|
2077
|
-
const result = (0,
|
|
2283
|
+
const result = (0, import_node_child_process11.spawnSync)("systemctl", ["--user", ...args], { encoding: "utf8", windowsHide: true });
|
|
2078
2284
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
2079
2285
|
}
|
|
2080
2286
|
function uid() {
|
|
@@ -2099,7 +2305,7 @@ WScript.Quit code
|
|
|
2099
2305
|
`;
|
|
2100
2306
|
}
|
|
2101
2307
|
function loginPath(fallback = process.env.PATH ?? "") {
|
|
2102
|
-
const probe = (0,
|
|
2308
|
+
const probe = (0, import_node_child_process11.spawnSync)("/bin/sh", ["-lc", 'printf %s "$PATH"'], { encoding: "utf8", windowsHide: true, timeout: 1e4 });
|
|
2103
2309
|
const captured = (probe.status === 0 ? probe.stdout ?? "" : "").trim();
|
|
2104
2310
|
const source = captured || fallback;
|
|
2105
2311
|
const durable = source.split(":").filter((entry) => entry && !/^\/(?:private\/)?(?:tmp|var\/folders)\//.test(entry));
|
|
@@ -2208,7 +2414,7 @@ WantedBy=timers.target
|
|
|
2208
2414
|
}
|
|
2209
2415
|
function readFileOrNull(path) {
|
|
2210
2416
|
try {
|
|
2211
|
-
return (0,
|
|
2417
|
+
return (0, import_node_fs15.readFileSync)(path, "utf8");
|
|
2212
2418
|
} catch {
|
|
2213
2419
|
return null;
|
|
2214
2420
|
}
|
|
@@ -2220,7 +2426,7 @@ function elevatedRelaunch(mode, env) {
|
|
|
2220
2426
|
if (env.MMI_UPDATER_ELEVATED_RETRY === "1") return false;
|
|
2221
2427
|
const self = process.argv[1];
|
|
2222
2428
|
const command = self ? `"${process.execPath}" "${self}" autoupdate ${mode}` : `mmi-hub autoupdate ${mode}`;
|
|
2223
|
-
const relaunch = (0,
|
|
2429
|
+
const relaunch = (0, import_node_child_process11.spawnSync)(
|
|
2224
2430
|
"powershell.exe",
|
|
2225
2431
|
["-NoProfile", "-Command", `Start-Process -Verb RunAs -Wait -WindowStyle Hidden -FilePath 'cmd.exe' -ArgumentList '/c',${psQuote(`set MMI_UPDATER_ELEVATED_RETRY=1&& ${command}`)}`],
|
|
2226
2432
|
{ encoding: "utf8", windowsHide: true }
|
|
@@ -2231,7 +2437,7 @@ var NEVER_RUN_RESULT = 267011;
|
|
|
2231
2437
|
var NEVER_RUN_YEAR = 1999;
|
|
2232
2438
|
var EMPTY_RUN_INFO = { lastRunTime: null, lastTaskResult: null, nextRunTime: null };
|
|
2233
2439
|
function taskRunInfo() {
|
|
2234
|
-
const result = (0,
|
|
2440
|
+
const result = (0, import_node_child_process11.spawnSync)(
|
|
2235
2441
|
"powershell.exe",
|
|
2236
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`],
|
|
2237
2443
|
{ encoding: "utf8", windowsHide: true, timeout: 3e4 }
|
|
@@ -2257,7 +2463,7 @@ function windowsSchedulerStatus(env) {
|
|
|
2257
2463
|
return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${TASK_NAME}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
|
|
2258
2464
|
}
|
|
2259
2465
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2260
|
-
const launcherPath = (0,
|
|
2466
|
+
const launcherPath = (0, import_node_path13.join)(statePaths(env).root, LAUNCHER_FILE);
|
|
2261
2467
|
const info = taskRunInfo();
|
|
2262
2468
|
const runInfo = info ?? EMPTY_RUN_INFO;
|
|
2263
2469
|
const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...runInfo, detail });
|
|
@@ -2282,13 +2488,13 @@ function windowsSchedulerStatus(env) {
|
|
|
2282
2488
|
return { ok: true, supported: true, enabled: true, ...runInfo, detail: `"${TASK_NAME}" registered ${CADENCE}; ${points}; ${ran}; next ${runInfo.nextRunTime ?? "unscheduled"}` };
|
|
2283
2489
|
}
|
|
2284
2490
|
function darwinSchedulerStatus(env) {
|
|
2285
|
-
const plistPath = (0,
|
|
2491
|
+
const plistPath = (0, import_node_path13.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
|
|
2286
2492
|
const plist = readFileOrNull(plistPath);
|
|
2287
2493
|
if (!plist) {
|
|
2288
2494
|
return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
|
|
2289
2495
|
}
|
|
2290
2496
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2291
|
-
const launcherPath = (0,
|
|
2497
|
+
const launcherPath = (0, import_node_path13.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
|
|
2292
2498
|
const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
|
|
2293
2499
|
if (!plist.includes(launcherPath)) {
|
|
2294
2500
|
return fail(`"${LAUNCHD_LABEL}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
@@ -2308,15 +2514,15 @@ function darwinSchedulerStatus(env) {
|
|
|
2308
2514
|
return { ok: true, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" registered ${POSIX_CADENCE}; ${points}` };
|
|
2309
2515
|
}
|
|
2310
2516
|
function linuxSchedulerStatus(env) {
|
|
2311
|
-
const unitDir = (0,
|
|
2312
|
-
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);
|
|
2313
2519
|
const timer = readFileOrNull(timerPath);
|
|
2314
|
-
const service = readFileOrNull((0,
|
|
2520
|
+
const service = readFileOrNull((0, import_node_path13.join)(unitDir, SYSTEMD_SERVICE_NAME));
|
|
2315
2521
|
if (!timer || !service) {
|
|
2316
2522
|
return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${SYSTEMD_TIMER_NAME}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
|
|
2317
2523
|
}
|
|
2318
2524
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2319
|
-
const launcherPath = (0,
|
|
2525
|
+
const launcherPath = (0, import_node_path13.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
|
|
2320
2526
|
const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
|
|
2321
2527
|
if (!service.includes(launcherPath)) {
|
|
2322
2528
|
return fail(`"${SYSTEMD_SERVICE_NAME}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
@@ -2355,10 +2561,10 @@ function installWindowsTask(env) {
|
|
|
2355
2561
|
const paths = ensureState(env);
|
|
2356
2562
|
const userId = `${env.USERDOMAIN || env.COMPUTERNAME || ""}\\${env.USERNAME || ""}`.replace(/^\\/, "");
|
|
2357
2563
|
if (!env.USERNAME) return { ok: false, detail: "cannot resolve the registering user (USERNAME unset) \u2014 the task principal must be explicit" };
|
|
2358
|
-
const launcherPath = (0,
|
|
2359
|
-
(0,
|
|
2360
|
-
const xmlPath = (0,
|
|
2361
|
-
(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");
|
|
2362
2568
|
const create = schtasks(["/create", "/tn", TASK_NAME, "/xml", xmlPath, "/f"]);
|
|
2363
2569
|
if (create.status !== 0) {
|
|
2364
2570
|
const denied = /access is denied/i.test(create.stderr + create.stdout);
|
|
@@ -2374,11 +2580,11 @@ function refreshPosixLauncher(env) {
|
|
|
2374
2580
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2375
2581
|
if (!hubDist) return null;
|
|
2376
2582
|
const paths = ensureState(env);
|
|
2377
|
-
const launcherPath = (0,
|
|
2583
|
+
const launcherPath = (0, import_node_path13.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2378
2584
|
const desired = launcherSh(process.execPath, hubDist);
|
|
2379
2585
|
if (readFileOrNull(launcherPath) === desired) return null;
|
|
2380
|
-
(0,
|
|
2381
|
-
(0,
|
|
2586
|
+
(0, import_node_fs15.writeFileSync)(launcherPath, desired, "utf8");
|
|
2587
|
+
(0, import_node_fs15.chmodSync)(launcherPath, 493);
|
|
2382
2588
|
return launcherPath;
|
|
2383
2589
|
}
|
|
2384
2590
|
function installLaunchd(env) {
|
|
@@ -2390,13 +2596,13 @@ function installLaunchd(env) {
|
|
|
2390
2596
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2391
2597
|
if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
|
|
2392
2598
|
const paths = ensureState(env);
|
|
2393
|
-
const launcherPath = (0,
|
|
2394
|
-
(0,
|
|
2395
|
-
(0,
|
|
2396
|
-
const agentsDir = (0,
|
|
2397
|
-
(0,
|
|
2398
|
-
const plistPath = (0,
|
|
2399
|
-
(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");
|
|
2400
2606
|
launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
2401
2607
|
const load = launchctl(["bootstrap", `gui/${uid()}`, plistPath]);
|
|
2402
2608
|
if (load.status !== 0) {
|
|
@@ -2414,13 +2620,13 @@ function installSystemdTimer(env) {
|
|
|
2414
2620
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2415
2621
|
if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
|
|
2416
2622
|
const paths = ensureState(env);
|
|
2417
|
-
const launcherPath = (0,
|
|
2418
|
-
(0,
|
|
2419
|
-
(0,
|
|
2420
|
-
const unitDir = (0,
|
|
2421
|
-
(0,
|
|
2422
|
-
(0,
|
|
2423
|
-
(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");
|
|
2424
2630
|
const reload = systemctlUser(["daemon-reload"]);
|
|
2425
2631
|
if (reload.status !== 0) {
|
|
2426
2632
|
return { ok: false, detail: `systemctl --user daemon-reload failed: ${(reload.stderr || reload.stdout).trim() || "unknown error \u2014 is a user systemd instance running?"}` };
|
|
@@ -2458,28 +2664,28 @@ function uninstallWindowsTask(env) {
|
|
|
2458
2664
|
return { ok: true, detail: `automatic updates off \u2014 removed "${TASK_NAME}"; installed tooling was kept` };
|
|
2459
2665
|
}
|
|
2460
2666
|
function uninstallLaunchd(env) {
|
|
2461
|
-
const plistPath = (0,
|
|
2667
|
+
const plistPath = (0, import_node_path13.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
|
|
2462
2668
|
const boot = launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
2463
|
-
if (boot.status !== 0 && (0,
|
|
2669
|
+
if (boot.status !== 0 && (0, import_node_fs15.existsSync)(plistPath)) {
|
|
2464
2670
|
return { ok: false, detail: `launchctl bootout failed: ${(boot.stderr || boot.stdout).trim() || "unknown error"}` };
|
|
2465
2671
|
}
|
|
2466
2672
|
try {
|
|
2467
|
-
(0,
|
|
2673
|
+
(0, import_node_fs15.rmSync)(plistPath, { force: true });
|
|
2468
2674
|
} catch {
|
|
2469
2675
|
}
|
|
2470
2676
|
dropLauncher(env, POSIX_LAUNCHER_FILE);
|
|
2471
2677
|
return { ok: true, detail: `automatic updates off \u2014 removed "${LAUNCHD_LABEL}"; installed tooling was kept` };
|
|
2472
2678
|
}
|
|
2473
2679
|
function uninstallSystemdTimer(env) {
|
|
2474
|
-
const unitDir = (0,
|
|
2475
|
-
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);
|
|
2476
2682
|
const disable = systemctlUser(["disable", "--now", SYSTEMD_TIMER_NAME]);
|
|
2477
|
-
if (disable.status !== 0 && (0,
|
|
2683
|
+
if (disable.status !== 0 && (0, import_node_fs15.existsSync)(timerPath)) {
|
|
2478
2684
|
return { ok: false, detail: `systemctl --user disable --now ${SYSTEMD_TIMER_NAME} failed: ${(disable.stderr || disable.stdout).trim() || "unknown error"}` };
|
|
2479
2685
|
}
|
|
2480
2686
|
try {
|
|
2481
|
-
(0,
|
|
2482
|
-
(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 });
|
|
2483
2689
|
} catch {
|
|
2484
2690
|
}
|
|
2485
2691
|
systemctlUser(["daemon-reload"]);
|
|
@@ -2494,16 +2700,16 @@ function uninstallTask(env = process.env) {
|
|
|
2494
2700
|
}
|
|
2495
2701
|
function dropLauncher(env, file) {
|
|
2496
2702
|
try {
|
|
2497
|
-
(0,
|
|
2703
|
+
(0, import_node_fs15.rmSync)((0, import_node_path13.join)(statePaths(env).root, file), { force: true });
|
|
2498
2704
|
} catch {
|
|
2499
2705
|
}
|
|
2500
2706
|
}
|
|
2501
2707
|
|
|
2502
2708
|
// src/status.ts
|
|
2503
|
-
var
|
|
2709
|
+
var import_node_fs16 = require("node:fs");
|
|
2504
2710
|
function readJournal(path) {
|
|
2505
2711
|
try {
|
|
2506
|
-
const events = (0,
|
|
2712
|
+
const events = (0, import_node_fs16.readFileSync)(path, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
2507
2713
|
try {
|
|
2508
2714
|
return JSON.parse(line);
|
|
2509
2715
|
} catch {
|
|
@@ -2622,6 +2828,7 @@ var YELLOW = "\x1B[33m";
|
|
|
2622
2828
|
var RED = "\x1B[31m";
|
|
2623
2829
|
var RESET = "\x1B[0m";
|
|
2624
2830
|
var RETRY_HINTS = [
|
|
2831
|
+
[/applies when the host exits|applies on a quiet tick/, "installs when the app closes"],
|
|
2625
2832
|
[/in flight|changed while/, "rerun update \u2014 host race"],
|
|
2626
2833
|
[/is running|live process|inside a .* hook/, "close app, rerun update"],
|
|
2627
2834
|
[/staging install failed|etarget|e404|network|fetch failed/, "check network/login, rerun"],
|
|
@@ -2880,7 +3087,7 @@ function startSpinner(enabled) {
|
|
|
2880
3087
|
// src/index.ts
|
|
2881
3088
|
function ownVersion() {
|
|
2882
3089
|
try {
|
|
2883
|
-
return JSON.parse((0,
|
|
3090
|
+
return JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path14.join)(__dirname, "..", "package.json"), "utf8")).version;
|
|
2884
3091
|
} catch {
|
|
2885
3092
|
return "0.0.0";
|
|
2886
3093
|
}
|
|
@@ -3006,6 +3213,8 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3006
3213
|
}
|
|
3007
3214
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3008
3215
|
0 && (module.exports = {
|
|
3216
|
+
buildWatcherCommand,
|
|
3217
|
+
clearPending,
|
|
3009
3218
|
enumerateSurfaces,
|
|
3010
3219
|
failConsequence,
|
|
3011
3220
|
formatReconcileReport,
|
|
@@ -3016,18 +3225,26 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3016
3225
|
launcherSh,
|
|
3017
3226
|
launcherVbs,
|
|
3018
3227
|
localStartBoundary,
|
|
3228
|
+
lockShaped,
|
|
3019
3229
|
main,
|
|
3230
|
+
noteBusyDefer,
|
|
3020
3231
|
ownVersion,
|
|
3232
|
+
pendingPath,
|
|
3233
|
+
pidAlive,
|
|
3021
3234
|
probeHermesInstallation,
|
|
3022
3235
|
progressiveRow,
|
|
3236
|
+
readPending,
|
|
3023
3237
|
reconcileCursorUserHooks,
|
|
3024
3238
|
reportFooterLines,
|
|
3025
3239
|
retryHint,
|
|
3240
|
+
schedulePendingWatcher,
|
|
3026
3241
|
schedulerStatus,
|
|
3027
3242
|
startSpinner,
|
|
3028
3243
|
systemdServiceUnit,
|
|
3029
3244
|
systemdTimerUnit,
|
|
3030
3245
|
taskXml,
|
|
3031
3246
|
updateHeader,
|
|
3032
|
-
|
|
3247
|
+
watcherLockPath,
|
|
3248
|
+
wrapWords,
|
|
3249
|
+
writePending
|
|
3033
3250
|
});
|