@mutmutco/hub 4.0.13 → 4.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.cjs +38 -8
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -382,7 +382,11 @@ function enumerateSurfaces(env = process.env) {
382
382
  return [
383
383
  { id: "claude", marker: (0, import_node_path3.join)(home, ".claude", "plugins", "installed_plugins.json") },
384
384
  { id: "codex", marker: (0, import_node_path3.join)(codexHome, "plugins", "installed_plugins.json") },
385
- { id: "cursor", marker: (0, import_node_path3.join)(home, ".cursor", "plugins", "local", "mmi") },
385
+ // The host, not the payload (#4951, #5356): this marker used to be plugins/local/mmi — the MMI
386
+ // plugin tree itself — so a machine running Cursor WITHOUT the plugin enumerated as "no Cursor"
387
+ // and the arm could never make the first install. The plugin's absence is a converge action, not
388
+ // a skip. The host root mirrors surfaceConfigRoot('cursor') in cli/src/plugin-guard-io.ts.
389
+ { id: "cursor", marker: (0, import_node_path3.join)(home, ".cursor") },
386
390
  { id: "kilo", marker: (0, import_node_path3.join)(home, ".config", "kilo") },
387
391
  // The host, not the payload (#4951): this marker used to be plugins/managed/mmi — the MMI plugin
388
392
  // itself — so a machine running Kimi WITHOUT the plugin enumerated as "no Kimi" and the arm
@@ -561,6 +565,8 @@ var import_node_child_process5 = require("node:child_process");
561
565
  var import_node_fs7 = require("node:fs");
562
566
  var import_node_os3 = require("node:os");
563
567
  var import_node_path6 = require("node:path");
568
+ var REPO_URL_DEFAULT = "https://github.com/mutmutco/MMI-Hub.git";
569
+ var MMI_MARKETPLACE_REPO = "mutmutco/MMI-Hub";
564
570
  function convergedAtOrAbove(installed, target, healthy) {
565
571
  return healthy && typeof installed === "string" && compareSemver(installed, target) >= 0;
566
572
  }
@@ -628,11 +634,20 @@ function ensureClaudeSingleWriter(env, dryRun) {
628
634
  let body;
629
635
  try {
630
636
  body = JSON.parse((0, import_node_fs7.readFileSync)(path, "utf8"));
631
- } catch {
637
+ } catch (error) {
638
+ if (error?.code === "ENOENT") return { ok: false, detail: `${path} does not exist \u2014 mutmutco marketplace is not registered`, unregistered: true };
632
639
  return { ok: false, detail: `cannot read ${path}` };
633
640
  }
634
641
  const registration = body.mutmutco;
635
- if (!registration || typeof registration !== "object") return { ok: false, detail: "mutmutco marketplace is not registered" };
642
+ if (!registration || typeof registration !== "object") return { ok: false, detail: "mutmutco marketplace is not registered", unregistered: true };
643
+ const source = registration.source;
644
+ const registeredRepo = source && typeof source === "object" && source.source === "github" ? source.repo : null;
645
+ if (registeredRepo !== MMI_MARKETPLACE_REPO) {
646
+ return {
647
+ ok: false,
648
+ 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 ${REPO_URL_DEFAULT}) before the updater can trust it`
649
+ };
650
+ }
636
651
  if (registration.autoUpdate !== true) return { ok: true };
637
652
  if (dryRun) return { ok: true, detail: "would disable Claude background autoUpdate (updater is the single writer)" };
638
653
  const quiescence = claudeHostRunning(env);
@@ -653,10 +668,16 @@ function ensureClaudeSingleWriter(env, dryRun) {
653
668
  function claudeArm(options) {
654
669
  const env = options.env ?? process.env;
655
670
  const runner = options.runner ?? runHostCommand;
671
+ const repoUrl = options.repoUrl ?? (env.MMI_UPDATER_REPO || REPO_URL_DEFAULT);
656
672
  const before = claudeMmi(runner, env);
657
673
  if (before.error) return { surface: "claude", from: null, to: options.target, verdict: "defer", detail: before.error };
658
674
  const from = before.row?.version ?? null;
659
- const singleWriter = ensureClaudeSingleWriter(env, options.dryRun);
675
+ let singleWriter = ensureClaudeSingleWriter(env, options.dryRun);
676
+ if (!singleWriter.ok && singleWriter.unregistered) {
677
+ const add = runner("claude", ["plugin", "marketplace", "add", repoUrl], env);
678
+ if (add.status !== 0) return { surface: "claude", from, to: options.target, verdict: "defer", detail: `mutmutco marketplace was not registered and marketplace add failed: ${failure(add)}` };
679
+ singleWriter = ensureClaudeSingleWriter(env, options.dryRun);
680
+ }
660
681
  if (!singleWriter.ok) return { surface: "claude", from, to: options.target, verdict: "defer", detail: singleWriter.detail ?? "cannot establish updater as the single writer" };
661
682
  if (options.dryRun) {
662
683
  const writer = singleWriter.detail ? `; ${singleWriter.detail}` : "";
@@ -702,9 +723,11 @@ function codexMmi(runner, env) {
702
723
  if (!parsed || !Array.isArray(parsed.installed)) return { row: null, error: `codex plugin list --json failed: ${failure(result)}` };
703
724
  return { row: parsed.installed.find((row) => row.pluginId === "mmi@mutmutco") ?? null };
704
725
  }
726
+ var NOT_CONFIGURED = /not configured|is not registered|not a Git marketplace/i;
705
727
  function codexArm(options) {
706
728
  const env = options.env ?? process.env;
707
729
  const runner = options.runner ?? runHostCommand;
730
+ const repoUrl = options.repoUrl ?? (env.MMI_UPDATER_REPO || REPO_URL_DEFAULT);
708
731
  let before = codexMmi(runner, env);
709
732
  if (before.error && SNAPSHOT_MISSING.test(before.error) && !options.dryRun) {
710
733
  const restore = runner("codex", ["plugin", "marketplace", "upgrade"], env);
@@ -719,8 +742,16 @@ function codexArm(options) {
719
742
  return { surface: "codex", from, to: options.target, verdict: "ok", detail: `dry-run: would upgrade mutmutco and converge mmi@mutmutco ${from ?? "absent"} -> ${options.target}` };
720
743
  }
721
744
  const upgrade = runner("codex", ["plugin", "marketplace", "upgrade", "mutmutco", "--json"], env);
722
- const upgradeJson = parseJson(upgrade);
723
- if (upgrade.status !== 0 || !upgradeJson || (upgradeJson.errors?.length ?? 0) > 0) {
745
+ let upgradeJson = parseJson(upgrade);
746
+ if ((upgrade.status !== 0 || !upgradeJson || (upgradeJson.errors?.length ?? 0) > 0) && NOT_CONFIGURED.test(failure(upgrade))) {
747
+ const add = runner("codex", ["plugin", "marketplace", "add", repoUrl], env);
748
+ if (add.status !== 0) return { surface: "codex", from, to: options.target, verdict: "defer", detail: `mutmutco marketplace was not configured and marketplace add failed: ${failure(add)}` };
749
+ const retry = runner("codex", ["plugin", "marketplace", "upgrade", "mutmutco", "--json"], env);
750
+ upgradeJson = parseJson(retry);
751
+ if (retry.status !== 0 || !upgradeJson || (upgradeJson.errors?.length ?? 0) > 0) {
752
+ return { surface: "codex", from, to: options.target, verdict: "defer", detail: `marketplace upgrade failed after registration: ${failure(retry)}` };
753
+ }
754
+ } else if (upgrade.status !== 0 || !upgradeJson || (upgradeJson.errors?.length ?? 0) > 0) {
724
755
  return { surface: "codex", from, to: options.target, verdict: "defer", detail: `marketplace upgrade failed: ${failure(upgrade)}` };
725
756
  }
726
757
  let observed = codexMmi(runner, env);
@@ -1897,7 +1928,6 @@ function reap(options) {
1897
1928
  }
1898
1929
 
1899
1930
  // src/reconcile.ts
1900
- var REPO_URL_DEFAULT = "https://github.com/mutmutco/MMI-Hub.git";
1901
1931
  var CANDIDATE_BOUND_DEFAULT = 12;
1902
1932
  function reconcile(options) {
1903
1933
  const env = options.env ?? process.env;
@@ -1985,7 +2015,7 @@ function reconcile(options) {
1985
2015
  continue;
1986
2016
  }
1987
2017
  say(`${probe.id}: converging to v${summary.target}`);
1988
- const arm = probe.id === "claude" ? claudeArm({ target: summary.target, dryRun: options.dryRun, env }) : probe.id === "codex" ? codexArm({ target: summary.target, dryRun: options.dryRun, env }) : probe.id === "kilo" ? kiloArm({ target: summary.target, dryRun: options.dryRun, env }) : probe.id === "kimi" ? kimiArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : probe.id === "cursor" ? cursorArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : probe.id === "jervcode" ? jervcodeArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : probe.id === "hermes" ? hermesArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : null;
2018
+ const arm = probe.id === "claude" ? claudeArm({ target: summary.target, dryRun: options.dryRun, env, repoUrl }) : probe.id === "codex" ? codexArm({ target: summary.target, dryRun: options.dryRun, env, repoUrl }) : probe.id === "kilo" ? kiloArm({ target: summary.target, dryRun: options.dryRun, env }) : probe.id === "kimi" ? kimiArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : probe.id === "cursor" ? cursorArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : probe.id === "jervcode" ? jervcodeArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : probe.id === "hermes" ? hermesArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : null;
1989
2019
  if (arm) {
1990
2020
  summary.plugins.push(arm);
1991
2021
  summary.surfaces.push({ id: probe.id, present: true, action: `arm-${arm.verdict}` });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/hub",
3
- "version": "4.0.13",
3
+ "version": "4.0.15",
4
4
  "description": "Install and maintain the MMI CLI and every present host surface from one release-gated Hub command.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",