@mutmutco/cli 2.32.1 → 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 +287 -117
  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) {
@@ -8567,7 +8673,7 @@ function detectSurface(env) {
8567
8673
  if (env.MMI_AGENT_SURFACE === "codex" || has("CODEX_HOME") || (env.CLAUDE_PLUGIN_ROOT ?? "").includes(".codex")) {
8568
8674
  return "codex";
8569
8675
  }
8570
- if (env.MMI_AGENT_SURFACE === "cursor" || has("CURSOR_TRACE_ID") || has("CURSOR_USER") || has("CURSOR_SESSION_ID")) {
8676
+ if (env.MMI_AGENT_SURFACE === "cursor" || has("CURSOR_TRACE_ID") || has("CURSOR_USER") || has("CURSOR_SESSION_ID") || env.CURSOR_AGENT === "1" || has("CURSOR_EXTENSION_HOST_ROLE")) {
8571
8677
  return "cursor";
8572
8678
  }
8573
8679
  const isClaude = has("CLAUDECODE") || has("CLAUDE_CODE_ENTRYPOINT") || has("CLAUDE_PLUGIN_ROOT") || env.MMI_AGENT_SURFACE === "claude";
@@ -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,8 @@ 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;
8798
+ if (input.surface === "cursor") return base2;
8692
8799
  const stale = [];
8693
8800
  let sawRecord = false;
8694
8801
  let currentVersion;
@@ -8697,7 +8804,7 @@ function buildInstalledPluginVersionCheck(input) {
8697
8804
  if (!Array.isArray(records) || records.length === 0) continue;
8698
8805
  sawRecord = true;
8699
8806
  const installedVersion = bestRecord(records).version;
8700
- if (!isSemverVersion(installedVersion)) continue;
8807
+ if (!isSemverVersion2(installedVersion)) continue;
8701
8808
  if (compareVersions(installedVersion, input.releasedVersion) >= 0) {
8702
8809
  currentVersion = currentVersion ?? installedVersion;
8703
8810
  } else {
@@ -8729,15 +8836,16 @@ function joinCachePath(root, ...parts) {
8729
8836
  function cursorPluginInstallFix(input) {
8730
8837
  const logHint = "check %APPDATA%\\Cursor\\logs\\<session>\\window*\\exthost\\anysphere.cursor-agent-exec\\Cursor Plugins.*.log for `unable to get password from user`";
8731
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";
8732
- 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";
8733
8840
  const guide = `full recovery: ${CURSOR_MARKETPLACE_INSTALL_GUIDE}`;
8734
8841
  if (input.reason === "missing-cache") {
8735
8842
  return `${marketplaceRefresh}; ${authSteps}; ${guide}`;
8736
8843
  }
8737
8844
  const pin = input.pinName ?? "<commit-pin>";
8738
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";
8739
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`;
8740
- 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}`;
8741
8849
  }
8742
8850
  function buildCursorPluginInstallCheck(input) {
8743
8851
  const base2 = {
@@ -8775,8 +8883,29 @@ function buildCursorPluginInstallCheck(input) {
8775
8883
  };
8776
8884
  }
8777
8885
  }
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(", ");
8888
+ return {
8889
+ ...base2,
8890
+ ok: false,
8891
+ cacheRoot: input.cacheRoot,
8892
+ pins: input.pins,
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}`
8894
+ };
8895
+ }
8778
8896
  return { ...base2, cacheRoot: input.cacheRoot, pins: input.pins };
8779
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
+ }
8780
8909
  function buildCursorHookCliCheck(input) {
8781
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";
8782
8911
  const base2 = { ok: true, label: CURSOR_HOOK_CLI_LABEL, fix };
@@ -8926,8 +9055,8 @@ async function runStageLiveDown(deps, t) {
8926
9055
 
8927
9056
  // src/stage-runner.ts
8928
9057
  var import_node_child_process7 = require("node:child_process");
8929
- var import_node_fs11 = require("node:fs");
8930
- var import_node_path11 = require("node:path");
9058
+ var import_node_fs12 = require("node:fs");
9059
+ var import_node_path12 = require("node:path");
8931
9060
  var import_node_net2 = require("node:net");
8932
9061
  var import_node_util6 = require("node:util");
8933
9062
  var execFileP4 = (0, import_node_util6.promisify)(import_node_child_process7.execFile);
@@ -8972,7 +9101,7 @@ function detectStaleEnvFile(exampleContent, targetContent, mtimes) {
8972
9101
  return void 0;
8973
9102
  }
8974
9103
  function stageStatePath(cwd = process.cwd()) {
8975
- return (0, import_node_path11.join)(cwd, "tmp", "stage", "state.json");
9104
+ return (0, import_node_path12.join)(cwd, "tmp", "stage", "state.json");
8976
9105
  }
8977
9106
  var POSIX_ONLY_VERBS = ["cp", "mv", "rm", "ln", "cat", "touch", "chmod", "export"];
8978
9107
  function posixOnlyShellProblems(command, field, platform = process.platform) {
@@ -9034,9 +9163,9 @@ async function shell(command, cwd, timeoutMs) {
9034
9163
  });
9035
9164
  }
9036
9165
  function readState(path2) {
9037
- if (!(0, import_node_fs11.existsSync)(path2)) return null;
9166
+ if (!(0, import_node_fs12.existsSync)(path2)) return null;
9038
9167
  try {
9039
- return JSON.parse((0, import_node_fs11.readFileSync)(path2, "utf8"));
9168
+ return JSON.parse((0, import_node_fs12.readFileSync)(path2, "utf8"));
9040
9169
  } catch {
9041
9170
  return null;
9042
9171
  }
@@ -9088,7 +9217,7 @@ async function stopStage(opts = {}) {
9088
9217
  return { ok: true, action: "stop", statePath, message: "no previous stage state found" };
9089
9218
  }
9090
9219
  await killTree(state.pid);
9091
- (0, import_node_fs11.rmSync)(statePath, { force: true });
9220
+ (0, import_node_fs12.rmSync)(statePath, { force: true });
9092
9221
  return { ok: true, action: "stop", statePath, pid: state.pid, message: `stopped previous stage pid ${state.pid}` };
9093
9222
  }
9094
9223
  async function startStage(config = {}, opts = {}) {
@@ -9097,7 +9226,7 @@ async function startStage(config = {}, opts = {}) {
9097
9226
  const cwd = opts.cwd ?? process.cwd();
9098
9227
  const statePath = opts.statePath ?? stageStatePath(cwd);
9099
9228
  const dir = statePath.slice(0, Math.max(statePath.lastIndexOf("/"), statePath.lastIndexOf("\\")));
9100
- (0, import_node_fs11.mkdirSync)(dir, { recursive: true });
9229
+ (0, import_node_fs12.mkdirSync)(dir, { recursive: true });
9101
9230
  let stagePort;
9102
9231
  if (config.portRange) {
9103
9232
  const [s, e] = config.portRange;
@@ -9107,14 +9236,14 @@ async function startStage(config = {}, opts = {}) {
9107
9236
  }
9108
9237
  const sub = (s) => s != null && stagePort != null ? s.replace(/\$\{?STAGE_PORT\}?/g, String(stagePort)) : s;
9109
9238
  if (config.ensureEnv) {
9110
- const target = (0, import_node_path11.join)(cwd, config.ensureEnv.target);
9111
- const example = (0, import_node_path11.join)(cwd, config.ensureEnv.example);
9112
- if (!(0, import_node_fs11.existsSync)(target) && (0, import_node_fs11.existsSync)(example)) {
9113
- (0, import_node_fs11.copyFileSync)(example, target);
9114
- } else if ((0, import_node_fs11.existsSync)(target) && (0, import_node_fs11.existsSync)(example)) {
9115
- const stale = detectStaleEnvFile((0, import_node_fs11.readFileSync)(example, "utf8"), (0, import_node_fs11.readFileSync)(target, "utf8"), {
9116
- exampleMtimeMs: (0, import_node_fs11.statSync)(example).mtimeMs,
9117
- 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
9118
9247
  });
9119
9248
  if (stale) {
9120
9249
  const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running /stage`;
@@ -9145,13 +9274,13 @@ async function startStage(config = {}, opts = {}) {
9145
9274
  healthUrl: sub(config.healthUrl?.trim()) || void 0,
9146
9275
  port: stagePort
9147
9276
  };
9148
- (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");
9149
9278
  try {
9150
9279
  if (state.healthUrl) await waitForHealth(state.healthUrl, opts.timeoutMs ?? 6e4, config.healthAnyStatus);
9151
9280
  else await waitForProcessStability(child);
9152
9281
  } catch (e) {
9153
9282
  await killTree(state.pid);
9154
- (0, import_node_fs11.rmSync)(statePath, { force: true });
9283
+ (0, import_node_fs12.rmSync)(statePath, { force: true });
9155
9284
  throw e;
9156
9285
  }
9157
9286
  const result = { ok: true, action: "start", statePath, pid: state.pid, port: stagePort, message: `started stage pid ${state.pid}${stagePort != null ? ` on port ${stagePort}` : ""}` };
@@ -10891,7 +11020,7 @@ async function announceRelease(deps, args) {
10891
11020
  }
10892
11021
 
10893
11022
  // src/port-registry.ts
10894
- var import_node_fs12 = require("node:fs");
11023
+ var import_node_fs13 = require("node:fs");
10895
11024
 
10896
11025
  // ../infra/port-geometry.mjs
10897
11026
  var PORT_BLOCK = 100;
@@ -10905,8 +11034,8 @@ function nextPortBlock(registry2) {
10905
11034
  return [base2, base2 + PORT_SPAN];
10906
11035
  }
10907
11036
  function loadPortRegistry(path2) {
10908
- if (!(0, import_node_fs12.existsSync)(path2)) return {};
10909
- 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"));
10910
11039
  const out = {};
10911
11040
  for (const [key, value] of Object.entries(raw)) {
10912
11041
  if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
@@ -10920,9 +11049,9 @@ function ensurePortRange(repo, path2) {
10920
11049
  const existing = registry2[repo];
10921
11050
  if (existing) return existing;
10922
11051
  const range = nextPortBlock(registry2);
10923
- 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")) : {};
10924
11053
  raw[repo] = range;
10925
- (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");
10926
11055
  return range;
10927
11056
  }
10928
11057
  function portCursorSeed(registry2) {
@@ -12708,10 +12837,10 @@ function parseKbTree(stdout, prefix) {
12708
12837
  }
12709
12838
 
12710
12839
  // src/plan.ts
12711
- var import_node_path12 = require("node:path");
12840
+ var import_node_path13 = require("node:path");
12712
12841
  var PLANS_DIR = "plans";
12713
- var META_FILE = (0, import_node_path12.join)(PLANS_DIR, ".plan-meta.json");
12714
- 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`);
12715
12844
  var metaKey = (project2, slug) => `${project2}/${slug}`;
12716
12845
  function parseMeta(raw) {
12717
12846
  if (!raw) return {};
@@ -12736,7 +12865,7 @@ function hashContent(s) {
12736
12865
  function staleHint(slug) {
12737
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`;
12738
12867
  }
12739
- 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");
12740
12869
  var INDEX_TTL_MS = 6e4;
12741
12870
  function parseIndex(raw) {
12742
12871
  if (!raw) return null;
@@ -12765,7 +12894,7 @@ function mergeIndex(idx, scope, plans, now) {
12765
12894
  const mergedScope = idx.scope === null ? null : [.../* @__PURE__ */ new Set([...idx.scope, ...scope])];
12766
12895
  return { fetchedAt: now, scope: mergedScope, plans: [...kept, ...plans] };
12767
12896
  }
12768
- 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");
12769
12898
  var QUEUE_MAX_ATTEMPTS = 10;
12770
12899
  function isValidQueueEntry(e) {
12771
12900
  if (!e || typeof e !== "object") return false;
@@ -13214,11 +13343,11 @@ async function planGraduate(deps, slug, opts = {}) {
13214
13343
  }
13215
13344
 
13216
13345
  // src/atomic-write.ts
13217
- var import_node_fs13 = require("node:fs");
13346
+ var import_node_fs14 = require("node:fs");
13218
13347
  function atomicWriteFileSync(path2, content) {
13219
13348
  const tmp = `${path2}.${process.pid}.tmp`;
13220
- (0, import_node_fs13.writeFileSync)(tmp, content, "utf8");
13221
- (0, import_node_fs13.renameSync)(tmp, path2);
13349
+ (0, import_node_fs14.writeFileSync)(tmp, content, "utf8");
13350
+ (0, import_node_fs14.renameSync)(tmp, path2);
13222
13351
  }
13223
13352
 
13224
13353
  // src/oauth.ts
@@ -13449,7 +13578,7 @@ async function fetchHubVersionInfo(baseUrl) {
13449
13578
  }
13450
13579
  function readRepoVersion() {
13451
13580
  try {
13452
- 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;
13453
13582
  } catch {
13454
13583
  return void 0;
13455
13584
  }
@@ -13559,10 +13688,10 @@ async function runRulesSync(opts, io = consoleIo) {
13559
13688
  for (const entry of fetched) {
13560
13689
  if ("error" in entry) continue;
13561
13690
  const { file, source } = entry;
13562
- 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;
13563
13692
  if (needsUpdate(source, current)) {
13564
13693
  const slash = file.lastIndexOf("/");
13565
- 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 });
13566
13695
  await (0, import_promises5.writeFile)(file, normalizeEol(source), "utf8");
13567
13696
  changed++;
13568
13697
  if (!opts.quiet) io.log(`mmi-cli rules: updated ${file}`);
@@ -13588,7 +13717,7 @@ async function runDocsSync(opts, io = consoleIo) {
13588
13717
  return null;
13589
13718
  }
13590
13719
  },
13591
- 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,
13592
13721
  writeDoc: async (f, c) => {
13593
13722
  await (0, import_promises5.writeFile)(f, c, "utf8");
13594
13723
  }
@@ -13761,7 +13890,7 @@ function detachPlanSync() {
13761
13890
  }
13762
13891
  }
13763
13892
  function makePlanDeps(cfg, io = consoleIo) {
13764
- const ensureDir = () => (0, import_node_fs14.mkdirSync)(PLANS_DIR, { recursive: true });
13893
+ const ensureDir = () => (0, import_node_fs15.mkdirSync)(PLANS_DIR, { recursive: true });
13765
13894
  return {
13766
13895
  apiUrl: cfg.sagaApiUrl,
13767
13896
  fetch: (url, init = {}) => fetch(url, { ...init, signal: init.signal ?? AbortSignal.timeout(1e4) }),
@@ -13769,24 +13898,24 @@ function makePlanDeps(cfg, io = consoleIo) {
13769
13898
  project: async () => (await sagaKey(cfg)).project,
13770
13899
  readLocal: (slug) => {
13771
13900
  try {
13772
- return (0, import_node_fs14.readFileSync)(planPath(slug), "utf8");
13901
+ return (0, import_node_fs15.readFileSync)(planPath(slug), "utf8");
13773
13902
  } catch {
13774
13903
  return null;
13775
13904
  }
13776
13905
  },
13777
13906
  writeLocal: (slug, content) => {
13778
13907
  ensureDir();
13779
- (0, import_node_fs14.writeFileSync)(planPath(slug), content, "utf8");
13908
+ (0, import_node_fs15.writeFileSync)(planPath(slug), content, "utf8");
13780
13909
  },
13781
13910
  removeLocal: (slug) => {
13782
13911
  try {
13783
- (0, import_node_fs14.rmSync)(planPath(slug));
13912
+ (0, import_node_fs15.rmSync)(planPath(slug));
13784
13913
  } catch {
13785
13914
  }
13786
13915
  },
13787
13916
  readMetaRaw: () => {
13788
13917
  try {
13789
- return (0, import_node_fs14.readFileSync)(META_FILE, "utf8");
13918
+ return (0, import_node_fs15.readFileSync)(META_FILE, "utf8");
13790
13919
  } catch {
13791
13920
  return null;
13792
13921
  }
@@ -13797,7 +13926,7 @@ function makePlanDeps(cfg, io = consoleIo) {
13797
13926
  },
13798
13927
  readIndexRaw: () => {
13799
13928
  try {
13800
- return (0, import_node_fs14.readFileSync)(INDEX_FILE, "utf8");
13929
+ return (0, import_node_fs15.readFileSync)(INDEX_FILE, "utf8");
13801
13930
  } catch {
13802
13931
  return null;
13803
13932
  }
@@ -13808,7 +13937,7 @@ function makePlanDeps(cfg, io = consoleIo) {
13808
13937
  },
13809
13938
  readQueueRaw: () => {
13810
13939
  try {
13811
- return (0, import_node_fs14.readFileSync)(QUEUE_FILE, "utf8");
13940
+ return (0, import_node_fs15.readFileSync)(QUEUE_FILE, "utf8");
13812
13941
  } catch {
13813
13942
  return null;
13814
13943
  }
@@ -14664,7 +14793,7 @@ async function createDeferredWorktreeStore() {
14664
14793
  },
14665
14794
  write: async (entries) => {
14666
14795
  try {
14667
- 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 });
14668
14797
  await (0, import_promises5.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
14669
14798
  } catch {
14670
14799
  }
@@ -14683,7 +14812,7 @@ function worktreeRemoveDeps(execGit) {
14683
14812
  }
14684
14813
  function teardownWorktreeStage(worktreePath) {
14685
14814
  return runWorktreeStageTeardown(worktreePath, {
14686
- hasStageState: (wt) => (0, import_node_fs14.existsSync)(stageStatePath(wt)),
14815
+ hasStageState: (wt) => (0, import_node_fs15.existsSync)(stageStatePath(wt)),
14687
14816
  stopRecordedStage: async (wt) => (await stopStage({ cwd: wt })).pid,
14688
14817
  listComposeProjects: async () => {
14689
14818
  const { stdout } = await execFileP2("docker", ["compose", "ls", "--all", "--format", "json"], { timeout: GC_GH_TIMEOUT_MS });
@@ -14746,7 +14875,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
14746
14875
  } : await cleanupPrMergeLocalBranch(headRef, {
14747
14876
  beforeWorktrees,
14748
14877
  startingPath,
14749
- pathExists: (p) => (0, import_node_fs14.existsSync)(p),
14878
+ pathExists: (p) => (0, import_node_fs15.existsSync)(p),
14750
14879
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
14751
14880
  teardownWorktreeStage,
14752
14881
  deferredStore,
@@ -14899,7 +15028,7 @@ function rawValues(flag) {
14899
15028
  return out;
14900
15029
  }
14901
15030
  function printLine(value) {
14902
- (0, import_node_fs14.writeSync)(1, `${value}
15031
+ (0, import_node_fs15.writeSync)(1, `${value}
14903
15032
  `);
14904
15033
  }
14905
15034
  function stageKeepAlive() {
@@ -14916,8 +15045,8 @@ async function resolveStage() {
14916
15045
  local,
14917
15046
  shell: shellFor(),
14918
15047
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
14919
- hasCompose: (0, import_node_fs14.existsSync)((0, import_node_path13.join)(process.cwd(), "docker-compose.yml")),
14920
- 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"))
14921
15050
  });
14922
15051
  }
14923
15052
  function stageStepsFor(res, stops = true) {
@@ -14953,9 +15082,9 @@ program2.command("port-range <repo>").description("assign (idempotently) + print
14953
15082
  printLine(o.json ? JSON.stringify({ repo, portRange: [start2, end2], source: "meta" }) : `${repo}: stage.portRange [${start2}, ${end2}]`);
14954
15083
  return;
14955
15084
  }
14956
- 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");
14957
15086
  const allocate = async (seed) => {
14958
- 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 });
14959
15088
  const parsed = JSON.parse(stdout);
14960
15089
  if (!Array.isArray(parsed.range) || parsed.range.length !== 2) throw new Error("port-ddb: no range in output");
14961
15090
  return parsed.range;
@@ -15289,7 +15418,7 @@ bootstrap.command("verify <repo>").description("audit whether an existing repo i
15289
15418
  const report = await verifyBootstrap(repo, o.class, {
15290
15419
  client: defaultGitHubClient(),
15291
15420
  projectMeta: meta,
15292
- 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,
15293
15422
  // requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
15294
15423
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
15295
15424
  requiredGcpApis: (() => {
@@ -15332,12 +15461,12 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
15332
15461
  return fail(`bootstrap apply: ${e.message}`);
15333
15462
  }
15334
15463
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
15335
- if (!(0, import_node_fs14.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
15336
- 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"));
15337
15466
  const baseBranch = o.class === "content" ? "main" : "development";
15338
15467
  const slug = parsedRepo.slug;
15339
15468
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
15340
- 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;
15341
15470
  const enc2 = (p) => p.split("/").map(encodeURIComponent).join("/");
15342
15471
  const rawVars = {};
15343
15472
  for (const value of rawValues("--var")) {
@@ -15546,16 +15675,16 @@ access.command("audit").description("audit collaborator roles + train-branch pus
15546
15675
  if (o.class !== "deployable" && o.class !== "content") return failGraceful("access audit: --class must be deployable or content");
15547
15676
  targets = [{ repo: o.repo, class: o.class }];
15548
15677
  } else {
15549
- 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;
15550
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>");
15551
- 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;
15552
15681
  targets = loadAccessTargets(projectsJson, fanoutJson);
15553
15682
  }
15554
15683
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
15555
- 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")) : {};
15556
15685
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
15557
15686
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
15558
- 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: {} };
15559
15688
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
15560
15689
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
15561
15690
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
@@ -15564,20 +15693,20 @@ access.command("audit").description("audit collaborator roles + train-branch pus
15564
15693
  var isWin = process.platform === "win32";
15565
15694
  var installedPluginsPath = (surface = detectSurface(process.env)) => {
15566
15695
  const homeDir = surface === "codex" ? ".codex" : ".claude";
15567
- 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");
15568
15697
  };
15569
15698
  function readInstalledPlugins() {
15570
15699
  try {
15571
- return JSON.parse((0, import_node_fs14.readFileSync)(installedPluginsPath(), "utf8"));
15700
+ return JSON.parse((0, import_node_fs15.readFileSync)(installedPluginsPath(), "utf8"));
15572
15701
  } catch {
15573
15702
  return null;
15574
15703
  }
15575
15704
  }
15576
15705
  function installedPluginSources() {
15577
15706
  return ["claude", "codex"].map((surface) => {
15578
- 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");
15579
15708
  try {
15580
- 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 };
15581
15710
  } catch {
15582
15711
  return { surface, installed: null, recordPath };
15583
15712
  }
@@ -15585,7 +15714,7 @@ function installedPluginSources() {
15585
15714
  }
15586
15715
  function readClaudeSettings() {
15587
15716
  try {
15588
- 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"));
15589
15718
  } catch {
15590
15719
  return null;
15591
15720
  }
@@ -15607,7 +15736,7 @@ function writeProjectInstallRecord(record) {
15607
15736
  const list = file.plugins[MMI_PLUGIN_ID] ?? [];
15608
15737
  list.push(record);
15609
15738
  file.plugins[MMI_PLUGIN_ID] = list;
15610
- (0, import_node_fs14.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
15739
+ (0, import_node_fs15.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
15611
15740
  `, "utf8");
15612
15741
  return true;
15613
15742
  } catch {
@@ -15620,9 +15749,9 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
15620
15749
  if (!file) return false;
15621
15750
  if (!file.plugins) file.plugins = {};
15622
15751
  const path2 = installedPluginsPath();
15623
- (0, import_node_fs14.copyFileSync)(path2, `${path2}.bak`);
15752
+ (0, import_node_fs15.copyFileSync)(path2, `${path2}.bak`);
15624
15753
  file.plugins[pluginId] = records;
15625
- (0, import_node_fs14.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
15754
+ (0, import_node_fs15.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
15626
15755
  `, "utf8");
15627
15756
  return true;
15628
15757
  } catch {
@@ -15630,29 +15759,37 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
15630
15759
  }
15631
15760
  }
15632
15761
  function cursorPluginCacheRoot() {
15633
- 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");
15634
15763
  }
15635
15764
  function cursorPluginCachePinSnapshots() {
15636
15765
  const root = cursorPluginCacheRoot();
15637
15766
  try {
15638
- return (0, import_node_fs14.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
15639
- const path2 = (0, import_node_path13.join)(root, entry.name);
15640
- const pluginJson = (0, import_node_path13.join)(path2, ".cursor-plugin", "plugin.json");
15641
- const hooksJson = (0, import_node_path13.join)(path2, "hooks", "hooks.json");
15642
- 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");
15772
+ let version;
15773
+ try {
15774
+ const raw = JSON.parse((0, import_node_fs15.readFileSync)(pluginJson, "utf8"));
15775
+ version = typeof raw.version === "string" ? raw.version : void 0;
15776
+ } catch {
15777
+ version = void 0;
15778
+ }
15643
15779
  let isEmpty = true;
15644
15780
  try {
15645
- isEmpty = (0, import_node_fs14.readdirSync)(path2).length === 0;
15781
+ isEmpty = (0, import_node_fs15.readdirSync)(path2).length === 0;
15646
15782
  } catch {
15647
15783
  isEmpty = true;
15648
15784
  }
15649
15785
  return {
15650
15786
  name: entry.name,
15651
15787
  path: path2,
15652
- hasPluginJson: (0, import_node_fs14.existsSync)(pluginJson),
15653
- hasHooksJson: (0, import_node_fs14.existsSync)(hooksJson),
15654
- hasCliBundle: (0, import_node_fs14.existsSync)(cliBundle),
15655
- isEmpty
15788
+ hasPluginJson: (0, import_node_fs15.existsSync)(pluginJson),
15789
+ hasHooksJson: (0, import_node_fs15.existsSync)(hooksJson),
15790
+ hasCliBundle: (0, import_node_fs15.existsSync)(cliBundle),
15791
+ isEmpty,
15792
+ version
15656
15793
  };
15657
15794
  });
15658
15795
  } catch {
@@ -15660,19 +15797,19 @@ function cursorPluginCachePinSnapshots() {
15660
15797
  }
15661
15798
  }
15662
15799
  function hubCheckoutForCursorSeed() {
15663
- const manifest = (0, import_node_path13.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
15664
- 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;
15665
15802
  }
15666
15803
  function mmiPluginCacheRootSnapshots() {
15667
15804
  const roots = [
15668
- { surface: "claude", root: (0, import_node_path13.join)((0, import_node_os4.homedir)(), ".claude", "plugins", "cache", "mmi", "mmi") },
15669
- { 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") }
15670
15807
  ];
15671
15808
  return roots.flatMap(({ surface, root }) => {
15672
15809
  try {
15673
- 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) => ({
15674
15811
  name: entry.name,
15675
- path: (0, import_node_path13.join)(root, entry.name),
15812
+ path: (0, import_node_path14.join)(root, entry.name),
15676
15813
  isDirectory: entry.isDirectory()
15677
15814
  }));
15678
15815
  return [{ surface, root, entries }];
@@ -15683,7 +15820,7 @@ function mmiPluginCacheRootSnapshots() {
15683
15820
  }
15684
15821
  function hasNestedMmiChild(versionDir) {
15685
15822
  try {
15686
- 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();
15687
15824
  } catch {
15688
15825
  return false;
15689
15826
  }
@@ -15694,10 +15831,10 @@ function nestedPluginTreeSnapshot() {
15694
15831
  );
15695
15832
  }
15696
15833
  function uniqueQuarantineTarget(path2) {
15697
- if (!(0, import_node_fs14.existsSync)(path2)) return path2;
15834
+ if (!(0, import_node_fs15.existsSync)(path2)) return path2;
15698
15835
  for (let i = 1; i < 100; i += 1) {
15699
15836
  const candidate = `${path2}-${i}`;
15700
- if (!(0, import_node_fs14.existsSync)(candidate)) return candidate;
15837
+ if (!(0, import_node_fs15.existsSync)(candidate)) return candidate;
15701
15838
  }
15702
15839
  return `${path2}-${Date.now()}`;
15703
15840
  }
@@ -15705,32 +15842,32 @@ function quarantinePluginCacheDirs(plan2) {
15705
15842
  let moved = 0;
15706
15843
  for (const move of plan2) {
15707
15844
  try {
15708
- if (!(0, import_node_fs14.existsSync)(move.from)) continue;
15845
+ if (!(0, import_node_fs15.existsSync)(move.from)) continue;
15709
15846
  const target = uniqueQuarantineTarget(move.to);
15710
- (0, import_node_fs14.mkdirSync)((0, import_node_path13.dirname)(target), { recursive: true });
15711
- (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);
15712
15849
  moved += 1;
15713
15850
  } catch {
15714
15851
  }
15715
15852
  }
15716
15853
  return moved;
15717
15854
  }
15718
- var gitignorePath = () => (0, import_node_path13.join)(process.cwd(), ".gitignore");
15855
+ var gitignorePath = () => (0, import_node_path14.join)(process.cwd(), ".gitignore");
15719
15856
  function readTextFile(path2) {
15720
15857
  try {
15721
- if (!(0, import_node_fs14.existsSync)(path2)) return null;
15722
- 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");
15723
15860
  } catch {
15724
15861
  return null;
15725
15862
  }
15726
15863
  }
15727
15864
  function playwrightMcpConfigSnapshots() {
15728
15865
  const cwd = process.cwd();
15729
- const home = (0, import_node_os4.homedir)();
15866
+ const home = (0, import_node_os5.homedir)();
15730
15867
  const candidates = [
15731
- (0, import_node_path13.join)(cwd, ".cursor", "mcp.json"),
15732
- (0, import_node_path13.join)(home, ".cursor", "mcp.json"),
15733
- (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")
15734
15871
  ];
15735
15872
  const out = [];
15736
15873
  for (const path2 of candidates) {
@@ -15743,7 +15880,7 @@ function strayBrowserArtifactPaths() {
15743
15880
  const cwd = process.cwd();
15744
15881
  return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
15745
15882
  try {
15746
- 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));
15747
15884
  } catch {
15748
15885
  return false;
15749
15886
  }
@@ -15751,14 +15888,14 @@ function strayBrowserArtifactPaths() {
15751
15888
  }
15752
15889
  function readGitignore() {
15753
15890
  try {
15754
- return (0, import_node_fs14.readFileSync)(gitignorePath(), "utf8");
15891
+ return (0, import_node_fs15.readFileSync)(gitignorePath(), "utf8");
15755
15892
  } catch {
15756
15893
  return null;
15757
15894
  }
15758
15895
  }
15759
15896
  function writeGitignore(content) {
15760
15897
  try {
15761
- (0, import_node_fs14.writeFileSync)(gitignorePath(), content, "utf8");
15898
+ (0, import_node_fs15.writeFileSync)(gitignorePath(), content, "utf8");
15762
15899
  return true;
15763
15900
  } catch {
15764
15901
  return false;
@@ -15797,7 +15934,7 @@ async function runDoctor(opts, io = consoleIo) {
15797
15934
  let onPath = pathProbe;
15798
15935
  if (!onPath) {
15799
15936
  const root = process.env.CLAUDE_PLUGIN_ROOT;
15800
- 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;
15801
15938
  }
15802
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" });
15803
15940
  const surface = detectSurface(process.env);
@@ -15926,17 +16063,50 @@ async function runDoctor(opts, io = consoleIo) {
15926
16063
  })
15927
16064
  );
15928
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);
15929
16103
  checks.push(
15930
- buildCursorPluginInstallCheck({
16104
+ buildCursorThirdPartyExtensibilityCheck({
15931
16105
  isOrgRepo: Boolean(cfg.sagaApiUrl),
15932
16106
  surface,
15933
- cacheRoot: cursorCacheRoot,
15934
- cacheRootExists: (0, import_node_fs14.existsSync)(cursorCacheRoot),
15935
- pins: cursorPluginCachePinSnapshots() ?? [],
15936
- hubCheckout: hubCheckoutForCursorSeed()
16107
+ enabled: cursorThirdPartyEnabled
15937
16108
  })
15938
16109
  );
15939
- const cursorPins = cursorPluginCachePinSnapshots() ?? [];
15940
16110
  checks.push(
15941
16111
  buildCursorHookCliCheck({
15942
16112
  isOrgRepo: Boolean(cfg.sagaApiUrl),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "2.32.1",
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",