@mutmutco/cli 2.32.2 → 2.32.3

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/main.cjs +270 -120
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -3392,7 +3392,7 @@ var program = new Command();
3392
3392
 
3393
3393
  // src/index.ts
3394
3394
  var import_promises5 = require("node:fs/promises");
3395
- var import_node_fs14 = require("node:fs");
3395
+ var import_node_fs15 = require("node:fs");
3396
3396
 
3397
3397
  // src/rules-sync.ts
3398
3398
  function normalizeEol(s) {
@@ -6857,8 +6857,8 @@ async function runNorthstarContext(io, deps) {
6857
6857
  }
6858
6858
 
6859
6859
  // src/index.ts
6860
- var import_node_path13 = require("node:path");
6861
- var import_node_os4 = require("node:os");
6860
+ var import_node_path14 = require("node:path");
6861
+ var import_node_os5 = require("node:os");
6862
6862
 
6863
6863
  // src/gh-create.ts
6864
6864
  var import_promises4 = require("node:fs/promises");
@@ -8247,6 +8247,112 @@ function decideStage(inputs) {
8247
8247
  return { source: "none", gap, staleIgnored: stale || void 0, staleFields: stale ? staleFields : void 0, registryError: registry2.error };
8248
8248
  }
8249
8249
 
8250
+ // src/cursor-plugin-seed.ts
8251
+ var import_node_fs11 = require("node:fs");
8252
+ var import_node_os4 = require("node:os");
8253
+ var import_node_path11 = require("node:path");
8254
+ function isSemverVersion(v) {
8255
+ return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
8256
+ }
8257
+ var MMI_HUB_REPO = "mutmutco/MMI-Hub";
8258
+ var CURSOR_THIRD_PARTY_STATE_KEY = "cursor/thirdPartyExtensibilityEnabled";
8259
+ var PLUGIN_JSON_REL = ".cursor-plugin/plugin.json";
8260
+ function cursorUserGlobalStatePath() {
8261
+ if (process.platform === "win32") {
8262
+ const base2 = process.env.APPDATA || (0, import_node_path11.join)((0, import_node_os4.homedir)(), "AppData", "Roaming");
8263
+ return (0, import_node_path11.join)(base2, "Cursor", "User", "globalStorage", "state.vscdb");
8264
+ }
8265
+ if (process.platform === "darwin") {
8266
+ return (0, import_node_path11.join)((0, import_node_os4.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
8267
+ }
8268
+ return (0, import_node_path11.join)((0, import_node_os4.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
8269
+ }
8270
+ async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
8271
+ const dbPath = cursorUserGlobalStatePath();
8272
+ if (!(0, import_node_fs11.existsSync)(dbPath)) return void 0;
8273
+ try {
8274
+ const { stdout } = await execFileP5("sqlite3", [dbPath, `SELECT value FROM ItemTable WHERE key = '${CURSOR_THIRD_PARTY_STATE_KEY}';`], {
8275
+ timeout: 5e3
8276
+ });
8277
+ const value = stdout.trim().toLowerCase();
8278
+ if (value === "true") return true;
8279
+ if (value === "false") return false;
8280
+ return void 0;
8281
+ } catch {
8282
+ return void 0;
8283
+ }
8284
+ }
8285
+ function syncDirContents(src, dest) {
8286
+ (0, import_node_fs11.mkdirSync)(dest, { recursive: true });
8287
+ for (const name of (0, import_node_fs11.readdirSync)(dest)) {
8288
+ (0, import_node_fs11.rmSync)((0, import_node_path11.join)(dest, name), { recursive: true, force: true });
8289
+ }
8290
+ (0, import_node_fs11.cpSync)(src, dest, { recursive: true });
8291
+ }
8292
+ function releaseTag(releasedVersion) {
8293
+ return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
8294
+ }
8295
+ async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
8296
+ const tarFile = (0, import_node_path11.join)(tmpRoot, "archive.tar");
8297
+ try {
8298
+ await execFileP5("git", ["-C", hubCheckout, "fetch", "origin", `tag ${tag}`, "--quiet"], { timeout: 6e4 });
8299
+ await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
8300
+ timeout: 6e4
8301
+ });
8302
+ await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
8303
+ const pluginMmi = (0, import_node_path11.join)(tmpRoot, "plugins", "mmi");
8304
+ return (0, import_node_fs11.existsSync)((0, import_node_path11.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
8305
+ } catch {
8306
+ return void 0;
8307
+ }
8308
+ }
8309
+ async function downloadPluginMmiViaGh(tag, tmpRoot, execFileP5) {
8310
+ const tarPath = (0, import_node_path11.join)(tmpRoot, "repo.tgz");
8311
+ try {
8312
+ await execFileP5("gh", ["api", `repos/${MMI_HUB_REPO}/tarball/refs/tags/${tag}`, "--output", tarPath], {
8313
+ timeout: 12e4
8314
+ });
8315
+ await execFileP5("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4 });
8316
+ const top = (0, import_node_fs11.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
8317
+ if (!top) return void 0;
8318
+ const pluginMmi = (0, import_node_path11.join)(tmpRoot, top, "plugins", "mmi");
8319
+ return (0, import_node_fs11.existsSync)((0, import_node_path11.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
8320
+ } catch {
8321
+ return void 0;
8322
+ }
8323
+ }
8324
+ async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, execFileP5) {
8325
+ (0, import_node_fs11.mkdirSync)(tmpRoot, { recursive: true });
8326
+ const tag = releaseTag(releasedVersion);
8327
+ if (hubCheckout) {
8328
+ const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
8329
+ if (fromHub) return fromHub;
8330
+ }
8331
+ return downloadPluginMmiViaGh(tag, (0, import_node_path11.join)(tmpRoot, "gh"), execFileP5);
8332
+ }
8333
+ function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
8334
+ if (!isSemverVersion(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
8335
+ return pins.filter((pin) => {
8336
+ if (!pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty) return true;
8337
+ if (isSemverVersion(pin.version) && compareVersions(pin.version, releasedVersion) < 0) return true;
8338
+ return false;
8339
+ });
8340
+ }
8341
+ async function applyCursorPluginCacheSeed(input) {
8342
+ if (!isSemverVersion(input.releasedVersion)) return false;
8343
+ const pinsToSeed = cursorPluginPinsNeedingSeed(input.pins, input.releasedVersion);
8344
+ if (pinsToSeed.length === 0) return false;
8345
+ const tmpRoot = await input.mkdtemp("mmi-cursor-seed-");
8346
+ const source = await resolvePluginMmiSource(input.releasedVersion, input.hubCheckout, tmpRoot, input.execFileP);
8347
+ if (!source) return false;
8348
+ input.log(` \u21BB seeding Cursor MMI plugin cache \u2192 ${input.releasedVersion}\u2026`);
8349
+ for (const pin of pinsToSeed) {
8350
+ syncDirContents(source, pin.path);
8351
+ }
8352
+ (0, import_node_fs11.rmSync)(tmpRoot, { recursive: true, force: true });
8353
+ return true;
8354
+ }
8355
+
8250
8356
  // src/bootstrap-seeds.ts
8251
8357
  var PLACEHOLDER_RE = /\{\{([A-Z0-9_]+)\}\}/g;
8252
8358
  function loadBootstrapSeeds(manifestJson) {
@@ -8622,7 +8728,7 @@ var PLUGIN_UPDATE_RECIPES = {
8622
8728
  };
8623
8729
  function highestSemver(versions) {
8624
8730
  return versions.reduce((best, v) => {
8625
- if (!isSemverVersion(v)) return best;
8731
+ if (!isSemverVersion2(v)) return best;
8626
8732
  if (best === void 0) return v;
8627
8733
  return compareVersions(v, best) > 0 ? v : best;
8628
8734
  }, void 0);
@@ -8633,11 +8739,11 @@ function buildPluginUpdateReport(input) {
8633
8739
  const codexActiveCache = highestSemver(input.codexCacheVersions ?? []);
8634
8740
  return {
8635
8741
  versions: {
8636
- ...isSemverVersion(input.cliVersion) ? { cli: input.cliVersion } : {},
8742
+ ...isSemverVersion2(input.cliVersion) ? { cli: input.cliVersion } : {},
8637
8743
  ...claudePlugin ? { claudePlugin } : {},
8638
8744
  ...codexMarketplace ? { codexMarketplace } : {},
8639
8745
  ...codexActiveCache ? { codexActiveCache } : {},
8640
- ...isSemverVersion(input.releasedVersion) ? { released: input.releasedVersion } : {}
8746
+ ...isSemverVersion2(input.releasedVersion) ? { released: input.releasedVersion } : {}
8641
8747
  },
8642
8748
  recipes: PLUGIN_UPDATE_RECIPES
8643
8749
  };
@@ -8667,7 +8773,7 @@ function buildDoctorJsonPayload(input) {
8667
8773
  };
8668
8774
  }
8669
8775
  var INSTALLED_PLUGIN_VERSION_LABEL = "installed MMI plugin version (vs latest release)";
8670
- function isSemverVersion(v) {
8776
+ function isSemverVersion2(v) {
8671
8777
  return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
8672
8778
  }
8673
8779
  function staleRecordCommand(surface) {
@@ -8688,7 +8794,7 @@ function buildInstalledPluginVersionCheck(input) {
8688
8794
  fix: pluginRecoveryFix(input.surface),
8689
8795
  pluginId
8690
8796
  };
8691
- if (!input.isOrgRepo || !isSemverVersion(input.releasedVersion)) return base2;
8797
+ if (!input.isOrgRepo || !isSemverVersion2(input.releasedVersion)) return base2;
8692
8798
  if (input.surface === "cursor") return base2;
8693
8799
  const stale = [];
8694
8800
  let sawRecord = false;
@@ -8698,7 +8804,7 @@ function buildInstalledPluginVersionCheck(input) {
8698
8804
  if (!Array.isArray(records) || records.length === 0) continue;
8699
8805
  sawRecord = true;
8700
8806
  const installedVersion = bestRecord(records).version;
8701
- if (!isSemverVersion(installedVersion)) continue;
8807
+ if (!isSemverVersion2(installedVersion)) continue;
8702
8808
  if (compareVersions(installedVersion, input.releasedVersion) >= 0) {
8703
8809
  currentVersion = currentVersion ?? installedVersion;
8704
8810
  } else {
@@ -8730,15 +8836,16 @@ function joinCachePath(root, ...parts) {
8730
8836
  function cursorPluginInstallFix(input) {
8731
8837
  const logHint = "check %APPDATA%\\Cursor\\logs\\<session>\\window*\\exthost\\anysphere.cursor-agent-exec\\Cursor Plugins.*.log for `unable to get password from user`";
8732
8838
  const authSteps = "ensure GitHub SSH auth works for Cursor's background git (start ssh-agent, add your GitHub key, verify `ssh -T git@github.com`) \u2014 Cursor disables credential.helper during marketplace clone";
8733
- const marketplaceRefresh = "in Cursor Dashboard \u2192 Settings \u2192 Plugins, click Update next to the MMI Team Marketplace, enable MMI, then restart Cursor";
8839
+ const marketplaceRefresh = "when Dashboard \u2192 Settings \u2192 Plugins shows Update next to the MMI Team Marketplace, refresh it there; otherwise run `mmi-cli doctor --apply` to seed the cache from the latest release";
8734
8840
  const guide = `full recovery: ${CURSOR_MARKETPLACE_INSTALL_GUIDE}`;
8735
8841
  if (input.reason === "missing-cache") {
8736
8842
  return `${marketplaceRefresh}; ${authSteps}; ${guide}`;
8737
8843
  }
8738
8844
  const pin = input.pinName ?? "<commit-pin>";
8739
8845
  const cacheDir = joinCachePath(input.cacheRoot, pin);
8846
+ const autoSeed = "run `mmi-cli doctor --apply` to seed plugins/mmi from the latest release into the active pin";
8740
8847
  const localSeed = input.hubCheckout ? `temporary fallback: copy ${joinCachePath(input.hubCheckout, "plugins", "mmi")} to ${cacheDir}, then restart Cursor` : `temporary fallback: copy plugins/mmi from a local MMI-Hub checkout to ${cacheDir}, then restart Cursor`;
8741
- return `Cursor plugin cache at ${cacheDir} is empty or missing ${CURSOR_PLUGIN_JSON_REL} or ${CURSOR_HOOKS_JSON_REL} \u2014 ${marketplaceRefresh}; ${authSteps}; ${localSeed}; ${logHint}; ${guide}`;
8848
+ return `Cursor plugin cache at ${cacheDir} is empty or missing ${CURSOR_PLUGIN_JSON_REL} or ${CURSOR_HOOKS_JSON_REL} \u2014 ${autoSeed}; ${marketplaceRefresh}; ${authSteps}; ${localSeed}; ${logHint}; ${guide}`;
8742
8849
  }
8743
8850
  function buildCursorPluginInstallCheck(input) {
8744
8851
  const base2 = {
@@ -8776,18 +8883,29 @@ function buildCursorPluginInstallCheck(input) {
8776
8883
  };
8777
8884
  }
8778
8885
  }
8779
- if (input.surface === "cursor" && isSemverVersion(input.releasedVersion) && input.pins.some((pin) => isSemverVersion(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0)) {
8780
- const stale = input.pins.filter((pin) => isSemverVersion(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0).map((pin) => `${pin.version} at ${pin.name}`).join(", ");
8886
+ if (input.surface === "cursor" && isSemverVersion2(input.releasedVersion) && input.pins.some((pin) => isSemverVersion2(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0)) {
8887
+ const stale = input.pins.filter((pin) => isSemverVersion2(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0).map((pin) => `${pin.version} at ${pin.name}`).join(", ");
8781
8888
  return {
8782
8889
  ...base2,
8783
8890
  ok: false,
8784
8891
  cacheRoot: input.cacheRoot,
8785
8892
  pins: input.pins,
8786
- fix: `Cursor MMI plugin cache is behind ${input.releasedVersion} (${stale}) \u2014 in Cursor Dashboard \u2192 Settings \u2192 Plugins, click Update next to the MMI Team Marketplace, enable MMI, then restart Cursor; ${CURSOR_MARKETPLACE_INSTALL_GUIDE}`
8893
+ fix: `Cursor MMI plugin cache is behind ${input.releasedVersion} (${stale}) \u2014 run \`mmi-cli doctor --apply\` to seed the cache from the latest release (effective after restart Cursor); when Dashboard \u2192 Settings \u2192 Plugins shows Update next to the MMI Team Marketplace, a master-admin can refresh the pin there instead; ${CURSOR_MARKETPLACE_INSTALL_GUIDE}`
8787
8894
  };
8788
8895
  }
8789
8896
  return { ...base2, cacheRoot: input.cacheRoot, pins: input.pins };
8790
8897
  }
8898
+ var CURSOR_THIRD_PARTY_EXTENSIBILITY_LABEL = "Cursor third-party plugin import";
8899
+ var CURSOR_THIRD_PARTY_EXTENSIBILITY_FIX = "disable Cursor Settings \u2192 Rules, Skills, Subagents \u2192 Include third-party Plugins, Skills, and other configs (cursor/thirdPartyExtensibilityEnabled) \u2014 it imports Claude/Codex hooks that break the MMI Team Marketplace plugin; use MMI from the Team Marketplace only, then restart Cursor";
8900
+ function buildCursorThirdPartyExtensibilityCheck(input) {
8901
+ const base2 = {
8902
+ ok: true,
8903
+ label: CURSOR_THIRD_PARTY_EXTENSIBILITY_LABEL,
8904
+ fix: CURSOR_THIRD_PARTY_EXTENSIBILITY_FIX
8905
+ };
8906
+ if (!input.isOrgRepo || input.surface !== "cursor" || input.enabled === void 0) return base2;
8907
+ return { ...base2, ok: !input.enabled };
8908
+ }
8791
8909
  function buildCursorHookCliCheck(input) {
8792
8910
  const fix = "update the MMI Team Marketplace plugin (releases ship cli/dist under plugins/mmi/cli/dist) or install mmi-cli on PATH \u2014 Cursor hooks fall back to PATH when the bundled CLI is missing";
8793
8911
  const base2 = { ok: true, label: CURSOR_HOOK_CLI_LABEL, fix };
@@ -8937,8 +9055,8 @@ async function runStageLiveDown(deps, t) {
8937
9055
 
8938
9056
  // src/stage-runner.ts
8939
9057
  var import_node_child_process7 = require("node:child_process");
8940
- var import_node_fs11 = require("node:fs");
8941
- var import_node_path11 = require("node:path");
9058
+ var import_node_fs12 = require("node:fs");
9059
+ var import_node_path12 = require("node:path");
8942
9060
  var import_node_net2 = require("node:net");
8943
9061
  var import_node_util6 = require("node:util");
8944
9062
  var execFileP4 = (0, import_node_util6.promisify)(import_node_child_process7.execFile);
@@ -8983,7 +9101,7 @@ function detectStaleEnvFile(exampleContent, targetContent, mtimes) {
8983
9101
  return void 0;
8984
9102
  }
8985
9103
  function stageStatePath(cwd = process.cwd()) {
8986
- return (0, import_node_path11.join)(cwd, "tmp", "stage", "state.json");
9104
+ return (0, import_node_path12.join)(cwd, "tmp", "stage", "state.json");
8987
9105
  }
8988
9106
  var POSIX_ONLY_VERBS = ["cp", "mv", "rm", "ln", "cat", "touch", "chmod", "export"];
8989
9107
  function posixOnlyShellProblems(command, field, platform = process.platform) {
@@ -9045,9 +9163,9 @@ async function shell(command, cwd, timeoutMs) {
9045
9163
  });
9046
9164
  }
9047
9165
  function readState(path2) {
9048
- if (!(0, import_node_fs11.existsSync)(path2)) return null;
9166
+ if (!(0, import_node_fs12.existsSync)(path2)) return null;
9049
9167
  try {
9050
- return JSON.parse((0, import_node_fs11.readFileSync)(path2, "utf8"));
9168
+ return JSON.parse((0, import_node_fs12.readFileSync)(path2, "utf8"));
9051
9169
  } catch {
9052
9170
  return null;
9053
9171
  }
@@ -9099,7 +9217,7 @@ async function stopStage(opts = {}) {
9099
9217
  return { ok: true, action: "stop", statePath, message: "no previous stage state found" };
9100
9218
  }
9101
9219
  await killTree(state.pid);
9102
- (0, import_node_fs11.rmSync)(statePath, { force: true });
9220
+ (0, import_node_fs12.rmSync)(statePath, { force: true });
9103
9221
  return { ok: true, action: "stop", statePath, pid: state.pid, message: `stopped previous stage pid ${state.pid}` };
9104
9222
  }
9105
9223
  async function startStage(config = {}, opts = {}) {
@@ -9108,7 +9226,7 @@ async function startStage(config = {}, opts = {}) {
9108
9226
  const cwd = opts.cwd ?? process.cwd();
9109
9227
  const statePath = opts.statePath ?? stageStatePath(cwd);
9110
9228
  const dir = statePath.slice(0, Math.max(statePath.lastIndexOf("/"), statePath.lastIndexOf("\\")));
9111
- (0, import_node_fs11.mkdirSync)(dir, { recursive: true });
9229
+ (0, import_node_fs12.mkdirSync)(dir, { recursive: true });
9112
9230
  let stagePort;
9113
9231
  if (config.portRange) {
9114
9232
  const [s, e] = config.portRange;
@@ -9118,14 +9236,14 @@ async function startStage(config = {}, opts = {}) {
9118
9236
  }
9119
9237
  const sub = (s) => s != null && stagePort != null ? s.replace(/\$\{?STAGE_PORT\}?/g, String(stagePort)) : s;
9120
9238
  if (config.ensureEnv) {
9121
- const target = (0, import_node_path11.join)(cwd, config.ensureEnv.target);
9122
- const example = (0, import_node_path11.join)(cwd, config.ensureEnv.example);
9123
- if (!(0, import_node_fs11.existsSync)(target) && (0, import_node_fs11.existsSync)(example)) {
9124
- (0, import_node_fs11.copyFileSync)(example, target);
9125
- } else if ((0, import_node_fs11.existsSync)(target) && (0, import_node_fs11.existsSync)(example)) {
9126
- const stale = detectStaleEnvFile((0, import_node_fs11.readFileSync)(example, "utf8"), (0, import_node_fs11.readFileSync)(target, "utf8"), {
9127
- exampleMtimeMs: (0, import_node_fs11.statSync)(example).mtimeMs,
9128
- targetMtimeMs: (0, import_node_fs11.statSync)(target).mtimeMs
9239
+ const target = (0, import_node_path12.join)(cwd, config.ensureEnv.target);
9240
+ const example = (0, import_node_path12.join)(cwd, config.ensureEnv.example);
9241
+ if (!(0, import_node_fs12.existsSync)(target) && (0, import_node_fs12.existsSync)(example)) {
9242
+ (0, import_node_fs12.copyFileSync)(example, target);
9243
+ } else if ((0, import_node_fs12.existsSync)(target) && (0, import_node_fs12.existsSync)(example)) {
9244
+ const stale = detectStaleEnvFile((0, import_node_fs12.readFileSync)(example, "utf8"), (0, import_node_fs12.readFileSync)(target, "utf8"), {
9245
+ exampleMtimeMs: (0, import_node_fs12.statSync)(example).mtimeMs,
9246
+ targetMtimeMs: (0, import_node_fs12.statSync)(target).mtimeMs
9129
9247
  });
9130
9248
  if (stale) {
9131
9249
  const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running /stage`;
@@ -9156,13 +9274,13 @@ async function startStage(config = {}, opts = {}) {
9156
9274
  healthUrl: sub(config.healthUrl?.trim()) || void 0,
9157
9275
  port: stagePort
9158
9276
  };
9159
- (0, import_node_fs11.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf8");
9277
+ (0, import_node_fs12.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf8");
9160
9278
  try {
9161
9279
  if (state.healthUrl) await waitForHealth(state.healthUrl, opts.timeoutMs ?? 6e4, config.healthAnyStatus);
9162
9280
  else await waitForProcessStability(child);
9163
9281
  } catch (e) {
9164
9282
  await killTree(state.pid);
9165
- (0, import_node_fs11.rmSync)(statePath, { force: true });
9283
+ (0, import_node_fs12.rmSync)(statePath, { force: true });
9166
9284
  throw e;
9167
9285
  }
9168
9286
  const result = { ok: true, action: "start", statePath, pid: state.pid, port: stagePort, message: `started stage pid ${state.pid}${stagePort != null ? ` on port ${stagePort}` : ""}` };
@@ -10902,7 +11020,7 @@ async function announceRelease(deps, args) {
10902
11020
  }
10903
11021
 
10904
11022
  // src/port-registry.ts
10905
- var import_node_fs12 = require("node:fs");
11023
+ var import_node_fs13 = require("node:fs");
10906
11024
 
10907
11025
  // ../infra/port-geometry.mjs
10908
11026
  var PORT_BLOCK = 100;
@@ -10916,8 +11034,8 @@ function nextPortBlock(registry2) {
10916
11034
  return [base2, base2 + PORT_SPAN];
10917
11035
  }
10918
11036
  function loadPortRegistry(path2) {
10919
- if (!(0, import_node_fs12.existsSync)(path2)) return {};
10920
- const raw = JSON.parse((0, import_node_fs12.readFileSync)(path2, "utf8"));
11037
+ if (!(0, import_node_fs13.existsSync)(path2)) return {};
11038
+ const raw = JSON.parse((0, import_node_fs13.readFileSync)(path2, "utf8"));
10921
11039
  const out = {};
10922
11040
  for (const [key, value] of Object.entries(raw)) {
10923
11041
  if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
@@ -10931,9 +11049,9 @@ function ensurePortRange(repo, path2) {
10931
11049
  const existing = registry2[repo];
10932
11050
  if (existing) return existing;
10933
11051
  const range = nextPortBlock(registry2);
10934
- const raw = (0, import_node_fs12.existsSync)(path2) ? JSON.parse((0, import_node_fs12.readFileSync)(path2, "utf8")) : {};
11052
+ const raw = (0, import_node_fs13.existsSync)(path2) ? JSON.parse((0, import_node_fs13.readFileSync)(path2, "utf8")) : {};
10935
11053
  raw[repo] = range;
10936
- (0, import_node_fs12.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
11054
+ (0, import_node_fs13.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
10937
11055
  return range;
10938
11056
  }
10939
11057
  function portCursorSeed(registry2) {
@@ -12719,10 +12837,10 @@ function parseKbTree(stdout, prefix) {
12719
12837
  }
12720
12838
 
12721
12839
  // src/plan.ts
12722
- var import_node_path12 = require("node:path");
12840
+ var import_node_path13 = require("node:path");
12723
12841
  var PLANS_DIR = "plans";
12724
- var META_FILE = (0, import_node_path12.join)(PLANS_DIR, ".plan-meta.json");
12725
- var planPath = (slug) => (0, import_node_path12.join)(PLANS_DIR, `${slug}.md`);
12842
+ var META_FILE = (0, import_node_path13.join)(PLANS_DIR, ".plan-meta.json");
12843
+ var planPath = (slug) => (0, import_node_path13.join)(PLANS_DIR, `${slug}.md`);
12726
12844
  var metaKey = (project2, slug) => `${project2}/${slug}`;
12727
12845
  function parseMeta(raw) {
12728
12846
  if (!raw) return {};
@@ -12747,7 +12865,7 @@ function hashContent(s) {
12747
12865
  function staleHint(slug) {
12748
12866
  return `remote "${slug}" is newer \u2014 run \`mmi-cli northstar pull ${slug}\` first (your local is based on an older version), or re-push with \`--force\` to overwrite`;
12749
12867
  }
12750
- var INDEX_FILE = (0, import_node_path12.join)(PLANS_DIR, ".index.json");
12868
+ var INDEX_FILE = (0, import_node_path13.join)(PLANS_DIR, ".index.json");
12751
12869
  var INDEX_TTL_MS = 6e4;
12752
12870
  function parseIndex(raw) {
12753
12871
  if (!raw) return null;
@@ -12776,7 +12894,7 @@ function mergeIndex(idx, scope, plans, now) {
12776
12894
  const mergedScope = idx.scope === null ? null : [.../* @__PURE__ */ new Set([...idx.scope, ...scope])];
12777
12895
  return { fetchedAt: now, scope: mergedScope, plans: [...kept, ...plans] };
12778
12896
  }
12779
- var QUEUE_FILE = (0, import_node_path12.join)(PLANS_DIR, ".sync-queue.json");
12897
+ var QUEUE_FILE = (0, import_node_path13.join)(PLANS_DIR, ".sync-queue.json");
12780
12898
  var QUEUE_MAX_ATTEMPTS = 10;
12781
12899
  function isValidQueueEntry(e) {
12782
12900
  if (!e || typeof e !== "object") return false;
@@ -13225,11 +13343,11 @@ async function planGraduate(deps, slug, opts = {}) {
13225
13343
  }
13226
13344
 
13227
13345
  // src/atomic-write.ts
13228
- var import_node_fs13 = require("node:fs");
13346
+ var import_node_fs14 = require("node:fs");
13229
13347
  function atomicWriteFileSync(path2, content) {
13230
13348
  const tmp = `${path2}.${process.pid}.tmp`;
13231
- (0, import_node_fs13.writeFileSync)(tmp, content, "utf8");
13232
- (0, import_node_fs13.renameSync)(tmp, path2);
13349
+ (0, import_node_fs14.writeFileSync)(tmp, content, "utf8");
13350
+ (0, import_node_fs14.renameSync)(tmp, path2);
13233
13351
  }
13234
13352
 
13235
13353
  // src/oauth.ts
@@ -13460,7 +13578,7 @@ async function fetchHubVersionInfo(baseUrl) {
13460
13578
  }
13461
13579
  function readRepoVersion() {
13462
13580
  try {
13463
- return JSON.parse((0, import_node_fs14.readFileSync)((0, import_node_path13.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
13581
+ return JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path14.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
13464
13582
  } catch {
13465
13583
  return void 0;
13466
13584
  }
@@ -13570,10 +13688,10 @@ async function runRulesSync(opts, io = consoleIo) {
13570
13688
  for (const entry of fetched) {
13571
13689
  if ("error" in entry) continue;
13572
13690
  const { file, source } = entry;
13573
- const current = (0, import_node_fs14.existsSync)(file) ? await (0, import_promises5.readFile)(file, "utf8") : null;
13691
+ const current = (0, import_node_fs15.existsSync)(file) ? await (0, import_promises5.readFile)(file, "utf8") : null;
13574
13692
  if (needsUpdate(source, current)) {
13575
13693
  const slash = file.lastIndexOf("/");
13576
- if (slash > 0) (0, import_node_fs14.mkdirSync)(file.slice(0, slash), { recursive: true });
13694
+ if (slash > 0) (0, import_node_fs15.mkdirSync)(file.slice(0, slash), { recursive: true });
13577
13695
  await (0, import_promises5.writeFile)(file, normalizeEol(source), "utf8");
13578
13696
  changed++;
13579
13697
  if (!opts.quiet) io.log(`mmi-cli rules: updated ${file}`);
@@ -13599,7 +13717,7 @@ async function runDocsSync(opts, io = consoleIo) {
13599
13717
  return null;
13600
13718
  }
13601
13719
  },
13602
- localContent: async (f) => (0, import_node_fs14.existsSync)(f) ? await (0, import_promises5.readFile)(f, "utf8") : null,
13720
+ localContent: async (f) => (0, import_node_fs15.existsSync)(f) ? await (0, import_promises5.readFile)(f, "utf8") : null,
13603
13721
  writeDoc: async (f, c) => {
13604
13722
  await (0, import_promises5.writeFile)(f, c, "utf8");
13605
13723
  }
@@ -13772,7 +13890,7 @@ function detachPlanSync() {
13772
13890
  }
13773
13891
  }
13774
13892
  function makePlanDeps(cfg, io = consoleIo) {
13775
- const ensureDir = () => (0, import_node_fs14.mkdirSync)(PLANS_DIR, { recursive: true });
13893
+ const ensureDir = () => (0, import_node_fs15.mkdirSync)(PLANS_DIR, { recursive: true });
13776
13894
  return {
13777
13895
  apiUrl: cfg.sagaApiUrl,
13778
13896
  fetch: (url, init = {}) => fetch(url, { ...init, signal: init.signal ?? AbortSignal.timeout(1e4) }),
@@ -13780,24 +13898,24 @@ function makePlanDeps(cfg, io = consoleIo) {
13780
13898
  project: async () => (await sagaKey(cfg)).project,
13781
13899
  readLocal: (slug) => {
13782
13900
  try {
13783
- return (0, import_node_fs14.readFileSync)(planPath(slug), "utf8");
13901
+ return (0, import_node_fs15.readFileSync)(planPath(slug), "utf8");
13784
13902
  } catch {
13785
13903
  return null;
13786
13904
  }
13787
13905
  },
13788
13906
  writeLocal: (slug, content) => {
13789
13907
  ensureDir();
13790
- (0, import_node_fs14.writeFileSync)(planPath(slug), content, "utf8");
13908
+ (0, import_node_fs15.writeFileSync)(planPath(slug), content, "utf8");
13791
13909
  },
13792
13910
  removeLocal: (slug) => {
13793
13911
  try {
13794
- (0, import_node_fs14.rmSync)(planPath(slug));
13912
+ (0, import_node_fs15.rmSync)(planPath(slug));
13795
13913
  } catch {
13796
13914
  }
13797
13915
  },
13798
13916
  readMetaRaw: () => {
13799
13917
  try {
13800
- return (0, import_node_fs14.readFileSync)(META_FILE, "utf8");
13918
+ return (0, import_node_fs15.readFileSync)(META_FILE, "utf8");
13801
13919
  } catch {
13802
13920
  return null;
13803
13921
  }
@@ -13808,7 +13926,7 @@ function makePlanDeps(cfg, io = consoleIo) {
13808
13926
  },
13809
13927
  readIndexRaw: () => {
13810
13928
  try {
13811
- return (0, import_node_fs14.readFileSync)(INDEX_FILE, "utf8");
13929
+ return (0, import_node_fs15.readFileSync)(INDEX_FILE, "utf8");
13812
13930
  } catch {
13813
13931
  return null;
13814
13932
  }
@@ -13819,7 +13937,7 @@ function makePlanDeps(cfg, io = consoleIo) {
13819
13937
  },
13820
13938
  readQueueRaw: () => {
13821
13939
  try {
13822
- return (0, import_node_fs14.readFileSync)(QUEUE_FILE, "utf8");
13940
+ return (0, import_node_fs15.readFileSync)(QUEUE_FILE, "utf8");
13823
13941
  } catch {
13824
13942
  return null;
13825
13943
  }
@@ -14675,7 +14793,7 @@ async function createDeferredWorktreeStore() {
14675
14793
  },
14676
14794
  write: async (entries) => {
14677
14795
  try {
14678
- await (0, import_promises5.mkdir)((0, import_node_path13.dirname)(registryPath), { recursive: true });
14796
+ await (0, import_promises5.mkdir)((0, import_node_path14.dirname)(registryPath), { recursive: true });
14679
14797
  await (0, import_promises5.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
14680
14798
  } catch {
14681
14799
  }
@@ -14694,7 +14812,7 @@ function worktreeRemoveDeps(execGit) {
14694
14812
  }
14695
14813
  function teardownWorktreeStage(worktreePath) {
14696
14814
  return runWorktreeStageTeardown(worktreePath, {
14697
- hasStageState: (wt) => (0, import_node_fs14.existsSync)(stageStatePath(wt)),
14815
+ hasStageState: (wt) => (0, import_node_fs15.existsSync)(stageStatePath(wt)),
14698
14816
  stopRecordedStage: async (wt) => (await stopStage({ cwd: wt })).pid,
14699
14817
  listComposeProjects: async () => {
14700
14818
  const { stdout } = await execFileP2("docker", ["compose", "ls", "--all", "--format", "json"], { timeout: GC_GH_TIMEOUT_MS });
@@ -14757,7 +14875,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
14757
14875
  } : await cleanupPrMergeLocalBranch(headRef, {
14758
14876
  beforeWorktrees,
14759
14877
  startingPath,
14760
- pathExists: (p) => (0, import_node_fs14.existsSync)(p),
14878
+ pathExists: (p) => (0, import_node_fs15.existsSync)(p),
14761
14879
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
14762
14880
  teardownWorktreeStage,
14763
14881
  deferredStore,
@@ -14910,7 +15028,7 @@ function rawValues(flag) {
14910
15028
  return out;
14911
15029
  }
14912
15030
  function printLine(value) {
14913
- (0, import_node_fs14.writeSync)(1, `${value}
15031
+ (0, import_node_fs15.writeSync)(1, `${value}
14914
15032
  `);
14915
15033
  }
14916
15034
  function stageKeepAlive() {
@@ -14927,8 +15045,8 @@ async function resolveStage() {
14927
15045
  local,
14928
15046
  shell: shellFor(),
14929
15047
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
14930
- hasCompose: (0, import_node_fs14.existsSync)((0, import_node_path13.join)(process.cwd(), "docker-compose.yml")),
14931
- hasEnvExample: (0, import_node_fs14.existsSync)((0, import_node_path13.join)(process.cwd(), ".env.example"))
15048
+ hasCompose: (0, import_node_fs15.existsSync)((0, import_node_path14.join)(process.cwd(), "docker-compose.yml")),
15049
+ hasEnvExample: (0, import_node_fs15.existsSync)((0, import_node_path14.join)(process.cwd(), ".env.example"))
14932
15050
  });
14933
15051
  }
14934
15052
  function stageStepsFor(res, stops = true) {
@@ -14964,9 +15082,9 @@ program2.command("port-range <repo>").description("assign (idempotently) + print
14964
15082
  printLine(o.json ? JSON.stringify({ repo, portRange: [start2, end2], source: "meta" }) : `${repo}: stage.portRange [${start2}, ${end2}]`);
14965
15083
  return;
14966
15084
  }
14967
- const path2 = (0, import_node_path13.join)(process.cwd(), "infra", "port-ranges.json");
15085
+ const path2 = (0, import_node_path14.join)(process.cwd(), "infra", "port-ranges.json");
14968
15086
  const allocate = async (seed) => {
14969
- const { stdout } = await execFileP2("node", [(0, import_node_path13.join)(process.cwd(), "infra", "port-ddb.mjs"), String(seed)], { timeout: 15e3 });
15087
+ const { stdout } = await execFileP2("node", [(0, import_node_path14.join)(process.cwd(), "infra", "port-ddb.mjs"), String(seed)], { timeout: 15e3 });
14970
15088
  const parsed = JSON.parse(stdout);
14971
15089
  if (!Array.isArray(parsed.range) || parsed.range.length !== 2) throw new Error("port-ddb: no range in output");
14972
15090
  return parsed.range;
@@ -15300,7 +15418,7 @@ bootstrap.command("verify <repo>").description("audit whether an existing repo i
15300
15418
  const report = await verifyBootstrap(repo, o.class, {
15301
15419
  client: defaultGitHubClient(),
15302
15420
  projectMeta: meta,
15303
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs14.existsSync)(path2) ? (0, import_node_fs14.readFileSync)(path2, "utf8") : null,
15421
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs15.existsSync)(path2) ? (0, import_node_fs15.readFileSync)(path2, "utf8") : null,
15304
15422
  // requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
15305
15423
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
15306
15424
  requiredGcpApis: (() => {
@@ -15343,12 +15461,12 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
15343
15461
  return fail(`bootstrap apply: ${e.message}`);
15344
15462
  }
15345
15463
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
15346
- if (!(0, import_node_fs14.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
15347
- const manifest = loadBootstrapSeeds((0, import_node_fs14.readFileSync)(manifestPath, "utf8"));
15464
+ if (!(0, import_node_fs15.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
15465
+ const manifest = loadBootstrapSeeds((0, import_node_fs15.readFileSync)(manifestPath, "utf8"));
15348
15466
  const baseBranch = o.class === "content" ? "main" : "development";
15349
15467
  const slug = parsedRepo.slug;
15350
15468
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
15351
- const readFile5 = (p) => (0, import_node_fs14.existsSync)(p) ? (0, import_node_fs14.readFileSync)(p, "utf8") : null;
15469
+ const readFile5 = (p) => (0, import_node_fs15.existsSync)(p) ? (0, import_node_fs15.readFileSync)(p, "utf8") : null;
15352
15470
  const enc2 = (p) => p.split("/").map(encodeURIComponent).join("/");
15353
15471
  const rawVars = {};
15354
15472
  for (const value of rawValues("--var")) {
@@ -15557,16 +15675,16 @@ access.command("audit").description("audit collaborator roles + train-branch pus
15557
15675
  if (o.class !== "deployable" && o.class !== "content") return failGraceful("access audit: --class must be deployable or content");
15558
15676
  targets = [{ repo: o.repo, class: o.class }];
15559
15677
  } else {
15560
- const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs14.existsSync)("projects.json") ? (0, import_node_fs14.readFileSync)("projects.json", "utf8") : null;
15678
+ const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs15.existsSync)("projects.json") ? (0, import_node_fs15.readFileSync)("projects.json", "utf8") : null;
15561
15679
  if (!projectsJson) return failGraceful("access audit: no project registry \u2014 Hub API unreachable and projects.json not found; run from the MMI-Hub repo root or pass --repo <owner/repo>");
15562
- const fanoutJson = (0, import_node_fs14.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs14.readFileSync)(".github/fanout-targets.json", "utf8") : null;
15680
+ const fanoutJson = (0, import_node_fs15.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs15.readFileSync)(".github/fanout-targets.json", "utf8") : null;
15563
15681
  targets = loadAccessTargets(projectsJson, fanoutJson);
15564
15682
  }
15565
15683
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
15566
- const fileMatrix = (0, import_node_fs14.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs14.readFileSync)("access-matrix.json", "utf8")) : {};
15684
+ const fileMatrix = (0, import_node_fs15.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs15.readFileSync)("access-matrix.json", "utf8")) : {};
15567
15685
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
15568
15686
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
15569
- const fileContracts = (0, import_node_fs14.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs14.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
15687
+ const fileContracts = (0, import_node_fs15.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs15.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
15570
15688
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
15571
15689
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
15572
15690
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
@@ -15575,20 +15693,20 @@ access.command("audit").description("audit collaborator roles + train-branch pus
15575
15693
  var isWin = process.platform === "win32";
15576
15694
  var installedPluginsPath = (surface = detectSurface(process.env)) => {
15577
15695
  const homeDir = surface === "codex" ? ".codex" : ".claude";
15578
- return (0, import_node_path13.join)((0, import_node_os4.homedir)(), homeDir, "plugins", "installed_plugins.json");
15696
+ return (0, import_node_path14.join)((0, import_node_os5.homedir)(), homeDir, "plugins", "installed_plugins.json");
15579
15697
  };
15580
15698
  function readInstalledPlugins() {
15581
15699
  try {
15582
- return JSON.parse((0, import_node_fs14.readFileSync)(installedPluginsPath(), "utf8"));
15700
+ return JSON.parse((0, import_node_fs15.readFileSync)(installedPluginsPath(), "utf8"));
15583
15701
  } catch {
15584
15702
  return null;
15585
15703
  }
15586
15704
  }
15587
15705
  function installedPluginSources() {
15588
15706
  return ["claude", "codex"].map((surface) => {
15589
- const recordPath = (0, import_node_path13.join)((0, import_node_os4.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
15707
+ const recordPath = (0, import_node_path14.join)((0, import_node_os5.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
15590
15708
  try {
15591
- return { surface, installed: JSON.parse((0, import_node_fs14.readFileSync)(recordPath, "utf8")), recordPath };
15709
+ return { surface, installed: JSON.parse((0, import_node_fs15.readFileSync)(recordPath, "utf8")), recordPath };
15592
15710
  } catch {
15593
15711
  return { surface, installed: null, recordPath };
15594
15712
  }
@@ -15596,7 +15714,7 @@ function installedPluginSources() {
15596
15714
  }
15597
15715
  function readClaudeSettings() {
15598
15716
  try {
15599
- return JSON.parse((0, import_node_fs14.readFileSync)((0, import_node_path13.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
15717
+ return JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path14.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
15600
15718
  } catch {
15601
15719
  return null;
15602
15720
  }
@@ -15618,7 +15736,7 @@ function writeProjectInstallRecord(record) {
15618
15736
  const list = file.plugins[MMI_PLUGIN_ID] ?? [];
15619
15737
  list.push(record);
15620
15738
  file.plugins[MMI_PLUGIN_ID] = list;
15621
- (0, import_node_fs14.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
15739
+ (0, import_node_fs15.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
15622
15740
  `, "utf8");
15623
15741
  return true;
15624
15742
  } catch {
@@ -15631,9 +15749,9 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
15631
15749
  if (!file) return false;
15632
15750
  if (!file.plugins) file.plugins = {};
15633
15751
  const path2 = installedPluginsPath();
15634
- (0, import_node_fs14.copyFileSync)(path2, `${path2}.bak`);
15752
+ (0, import_node_fs15.copyFileSync)(path2, `${path2}.bak`);
15635
15753
  file.plugins[pluginId] = records;
15636
- (0, import_node_fs14.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
15754
+ (0, import_node_fs15.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
15637
15755
  `, "utf8");
15638
15756
  return true;
15639
15757
  } catch {
@@ -15641,35 +15759,35 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
15641
15759
  }
15642
15760
  }
15643
15761
  function cursorPluginCacheRoot() {
15644
- return (0, import_node_path13.join)((0, import_node_os4.homedir)(), ".cursor", "plugins", "cache", "mmi", "mmi");
15762
+ return (0, import_node_path14.join)((0, import_node_os5.homedir)(), ".cursor", "plugins", "cache", "mmi", "mmi");
15645
15763
  }
15646
15764
  function cursorPluginCachePinSnapshots() {
15647
15765
  const root = cursorPluginCacheRoot();
15648
15766
  try {
15649
- return (0, import_node_fs14.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
15650
- const path2 = (0, import_node_path13.join)(root, entry.name);
15651
- const pluginJson = (0, import_node_path13.join)(path2, ".cursor-plugin", "plugin.json");
15652
- const hooksJson = (0, import_node_path13.join)(path2, "hooks", "hooks.json");
15653
- const cliBundle = (0, import_node_path13.join)(path2, "cli", "dist", "index.cjs");
15767
+ return (0, import_node_fs15.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
15768
+ const path2 = (0, import_node_path14.join)(root, entry.name);
15769
+ const pluginJson = (0, import_node_path14.join)(path2, ".cursor-plugin", "plugin.json");
15770
+ const hooksJson = (0, import_node_path14.join)(path2, "hooks", "hooks.json");
15771
+ const cliBundle = (0, import_node_path14.join)(path2, "cli", "dist", "index.cjs");
15654
15772
  let version;
15655
15773
  try {
15656
- const raw = JSON.parse((0, import_node_fs14.readFileSync)(pluginJson, "utf8"));
15774
+ const raw = JSON.parse((0, import_node_fs15.readFileSync)(pluginJson, "utf8"));
15657
15775
  version = typeof raw.version === "string" ? raw.version : void 0;
15658
15776
  } catch {
15659
15777
  version = void 0;
15660
15778
  }
15661
15779
  let isEmpty = true;
15662
15780
  try {
15663
- isEmpty = (0, import_node_fs14.readdirSync)(path2).length === 0;
15781
+ isEmpty = (0, import_node_fs15.readdirSync)(path2).length === 0;
15664
15782
  } catch {
15665
15783
  isEmpty = true;
15666
15784
  }
15667
15785
  return {
15668
15786
  name: entry.name,
15669
15787
  path: path2,
15670
- hasPluginJson: (0, import_node_fs14.existsSync)(pluginJson),
15671
- hasHooksJson: (0, import_node_fs14.existsSync)(hooksJson),
15672
- hasCliBundle: (0, import_node_fs14.existsSync)(cliBundle),
15788
+ hasPluginJson: (0, import_node_fs15.existsSync)(pluginJson),
15789
+ hasHooksJson: (0, import_node_fs15.existsSync)(hooksJson),
15790
+ hasCliBundle: (0, import_node_fs15.existsSync)(cliBundle),
15673
15791
  isEmpty,
15674
15792
  version
15675
15793
  };
@@ -15679,19 +15797,19 @@ function cursorPluginCachePinSnapshots() {
15679
15797
  }
15680
15798
  }
15681
15799
  function hubCheckoutForCursorSeed() {
15682
- const manifest = (0, import_node_path13.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
15683
- return (0, import_node_fs14.existsSync)(manifest) ? process.cwd() : void 0;
15800
+ const manifest = (0, import_node_path14.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
15801
+ return (0, import_node_fs15.existsSync)(manifest) ? process.cwd() : void 0;
15684
15802
  }
15685
15803
  function mmiPluginCacheRootSnapshots() {
15686
15804
  const roots = [
15687
- { surface: "claude", root: (0, import_node_path13.join)((0, import_node_os4.homedir)(), ".claude", "plugins", "cache", "mmi", "mmi") },
15688
- { surface: "codex", root: (0, import_node_path13.join)((0, import_node_os4.homedir)(), ".codex", "plugins", "cache", "mmi", "mmi") }
15805
+ { surface: "claude", root: (0, import_node_path14.join)((0, import_node_os5.homedir)(), ".claude", "plugins", "cache", "mmi", "mmi") },
15806
+ { surface: "codex", root: (0, import_node_path14.join)((0, import_node_os5.homedir)(), ".codex", "plugins", "cache", "mmi", "mmi") }
15689
15807
  ];
15690
15808
  return roots.flatMap(({ surface, root }) => {
15691
15809
  try {
15692
- const entries = (0, import_node_fs14.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
15810
+ const entries = (0, import_node_fs15.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
15693
15811
  name: entry.name,
15694
- path: (0, import_node_path13.join)(root, entry.name),
15812
+ path: (0, import_node_path14.join)(root, entry.name),
15695
15813
  isDirectory: entry.isDirectory()
15696
15814
  }));
15697
15815
  return [{ surface, root, entries }];
@@ -15702,7 +15820,7 @@ function mmiPluginCacheRootSnapshots() {
15702
15820
  }
15703
15821
  function hasNestedMmiChild(versionDir) {
15704
15822
  try {
15705
- return (0, import_node_fs14.statSync)((0, import_node_path13.join)(versionDir, "mmi")).isDirectory();
15823
+ return (0, import_node_fs15.statSync)((0, import_node_path14.join)(versionDir, "mmi")).isDirectory();
15706
15824
  } catch {
15707
15825
  return false;
15708
15826
  }
@@ -15713,10 +15831,10 @@ function nestedPluginTreeSnapshot() {
15713
15831
  );
15714
15832
  }
15715
15833
  function uniqueQuarantineTarget(path2) {
15716
- if (!(0, import_node_fs14.existsSync)(path2)) return path2;
15834
+ if (!(0, import_node_fs15.existsSync)(path2)) return path2;
15717
15835
  for (let i = 1; i < 100; i += 1) {
15718
15836
  const candidate = `${path2}-${i}`;
15719
- if (!(0, import_node_fs14.existsSync)(candidate)) return candidate;
15837
+ if (!(0, import_node_fs15.existsSync)(candidate)) return candidate;
15720
15838
  }
15721
15839
  return `${path2}-${Date.now()}`;
15722
15840
  }
@@ -15724,32 +15842,32 @@ function quarantinePluginCacheDirs(plan2) {
15724
15842
  let moved = 0;
15725
15843
  for (const move of plan2) {
15726
15844
  try {
15727
- if (!(0, import_node_fs14.existsSync)(move.from)) continue;
15845
+ if (!(0, import_node_fs15.existsSync)(move.from)) continue;
15728
15846
  const target = uniqueQuarantineTarget(move.to);
15729
- (0, import_node_fs14.mkdirSync)((0, import_node_path13.dirname)(target), { recursive: true });
15730
- (0, import_node_fs14.renameSync)(move.from, target);
15847
+ (0, import_node_fs15.mkdirSync)((0, import_node_path14.dirname)(target), { recursive: true });
15848
+ (0, import_node_fs15.renameSync)(move.from, target);
15731
15849
  moved += 1;
15732
15850
  } catch {
15733
15851
  }
15734
15852
  }
15735
15853
  return moved;
15736
15854
  }
15737
- var gitignorePath = () => (0, import_node_path13.join)(process.cwd(), ".gitignore");
15855
+ var gitignorePath = () => (0, import_node_path14.join)(process.cwd(), ".gitignore");
15738
15856
  function readTextFile(path2) {
15739
15857
  try {
15740
- if (!(0, import_node_fs14.existsSync)(path2)) return null;
15741
- return (0, import_node_fs14.readFileSync)(path2, "utf8");
15858
+ if (!(0, import_node_fs15.existsSync)(path2)) return null;
15859
+ return (0, import_node_fs15.readFileSync)(path2, "utf8");
15742
15860
  } catch {
15743
15861
  return null;
15744
15862
  }
15745
15863
  }
15746
15864
  function playwrightMcpConfigSnapshots() {
15747
15865
  const cwd = process.cwd();
15748
- const home = (0, import_node_os4.homedir)();
15866
+ const home = (0, import_node_os5.homedir)();
15749
15867
  const candidates = [
15750
- (0, import_node_path13.join)(cwd, ".cursor", "mcp.json"),
15751
- (0, import_node_path13.join)(home, ".cursor", "mcp.json"),
15752
- (0, import_node_path13.join)(home, ".codex", "config.toml")
15868
+ (0, import_node_path14.join)(cwd, ".cursor", "mcp.json"),
15869
+ (0, import_node_path14.join)(home, ".cursor", "mcp.json"),
15870
+ (0, import_node_path14.join)(home, ".codex", "config.toml")
15753
15871
  ];
15754
15872
  const out = [];
15755
15873
  for (const path2 of candidates) {
@@ -15762,7 +15880,7 @@ function strayBrowserArtifactPaths() {
15762
15880
  const cwd = process.cwd();
15763
15881
  return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
15764
15882
  try {
15765
- return (0, import_node_fs14.existsSync)((0, import_node_path13.join)(cwd, rel));
15883
+ return (0, import_node_fs15.existsSync)((0, import_node_path14.join)(cwd, rel));
15766
15884
  } catch {
15767
15885
  return false;
15768
15886
  }
@@ -15770,14 +15888,14 @@ function strayBrowserArtifactPaths() {
15770
15888
  }
15771
15889
  function readGitignore() {
15772
15890
  try {
15773
- return (0, import_node_fs14.readFileSync)(gitignorePath(), "utf8");
15891
+ return (0, import_node_fs15.readFileSync)(gitignorePath(), "utf8");
15774
15892
  } catch {
15775
15893
  return null;
15776
15894
  }
15777
15895
  }
15778
15896
  function writeGitignore(content) {
15779
15897
  try {
15780
- (0, import_node_fs14.writeFileSync)(gitignorePath(), content, "utf8");
15898
+ (0, import_node_fs15.writeFileSync)(gitignorePath(), content, "utf8");
15781
15899
  return true;
15782
15900
  } catch {
15783
15901
  return false;
@@ -15816,7 +15934,7 @@ async function runDoctor(opts, io = consoleIo) {
15816
15934
  let onPath = pathProbe;
15817
15935
  if (!onPath) {
15818
15936
  const root = process.env.CLAUDE_PLUGIN_ROOT;
15819
- if (root && (0, import_node_fs14.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
15937
+ if (root && (0, import_node_fs15.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
15820
15938
  }
15821
15939
  checks.push({ ok: onPath, label: "mmi-cli on PATH", fix: "auto-provisioned at session start \u2014 reopen the session, or install the MMI plugin" });
15822
15940
  const surface = detectSurface(process.env);
@@ -15945,18 +16063,50 @@ async function runDoctor(opts, io = consoleIo) {
15945
16063
  })
15946
16064
  );
15947
16065
  const cursorCacheRoot = cursorPluginCacheRoot();
16066
+ let cursorPins = cursorPluginCachePinSnapshots() ?? [];
16067
+ let cursorPluginCheck = buildCursorPluginInstallCheck({
16068
+ isOrgRepo: Boolean(cfg.sagaApiUrl),
16069
+ surface,
16070
+ cacheRoot: cursorCacheRoot,
16071
+ cacheRootExists: (0, import_node_fs15.existsSync)(cursorCacheRoot),
16072
+ pins: cursorPins,
16073
+ hubCheckout: hubCheckoutForCursorSeed(),
16074
+ releasedVersion
16075
+ });
16076
+ if (!cursorPluginCheck.ok && repairLocal) {
16077
+ const seeded = await applyCursorPluginCacheSeed({
16078
+ pins: cursorPins,
16079
+ releasedVersion,
16080
+ hubCheckout: hubCheckoutForCursorSeed(),
16081
+ execFileP: execFileP2,
16082
+ mkdtemp: (prefix) => (0, import_promises5.mkdtemp)((0, import_node_path14.join)((0, import_node_os5.tmpdir)(), prefix)),
16083
+ log: (m) => io.err(m)
16084
+ });
16085
+ if (seeded) {
16086
+ cursorPins = cursorPluginCachePinSnapshots() ?? [];
16087
+ cursorPluginCheck = buildCursorPluginInstallCheck({
16088
+ isOrgRepo: Boolean(cfg.sagaApiUrl),
16089
+ surface,
16090
+ cacheRoot: cursorCacheRoot,
16091
+ cacheRootExists: (0, import_node_fs15.existsSync)(cursorCacheRoot),
16092
+ pins: cursorPins,
16093
+ hubCheckout: hubCheckoutForCursorSeed(),
16094
+ releasedVersion
16095
+ });
16096
+ if (cursorPluginCheck.ok) {
16097
+ io.err(` \u21BB seeded Cursor MMI plugin cache \u2192 ${releasedVersion ?? "latest"} \u2014 ${reloadAction(surface)}`);
16098
+ }
16099
+ }
16100
+ }
16101
+ checks.push(cursorPluginCheck);
16102
+ const cursorThirdPartyEnabled = await readCursorThirdPartyExtensibilityEnabled(execFileP2);
15948
16103
  checks.push(
15949
- buildCursorPluginInstallCheck({
16104
+ buildCursorThirdPartyExtensibilityCheck({
15950
16105
  isOrgRepo: Boolean(cfg.sagaApiUrl),
15951
16106
  surface,
15952
- cacheRoot: cursorCacheRoot,
15953
- cacheRootExists: (0, import_node_fs14.existsSync)(cursorCacheRoot),
15954
- pins: cursorPluginCachePinSnapshots() ?? [],
15955
- hubCheckout: hubCheckoutForCursorSeed(),
15956
- releasedVersion
16107
+ enabled: cursorThirdPartyEnabled
15957
16108
  })
15958
16109
  );
15959
- const cursorPins = cursorPluginCachePinSnapshots() ?? [];
15960
16110
  checks.push(
15961
16111
  buildCursorHookCliCheck({
15962
16112
  isOrgRepo: Boolean(cfg.sagaApiUrl),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "2.32.2",
3
+ "version": "2.32.3",
4
4
  "description": "MMI Future CLI — delivers the org rules (whole-file), plus saga and KB access. The cross-IDE engine the plugin's SessionStart hook drives.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",