@mutmutco/cli 3.73.0 → 3.74.0

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 +313 -91
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -3417,7 +3417,7 @@ var program = new Command();
3417
3417
  // src/index.ts
3418
3418
  var import_promises8 = require("node:fs/promises");
3419
3419
  var import_node_fs33 = require("node:fs");
3420
- var import_node_child_process14 = require("node:child_process");
3420
+ var import_node_child_process15 = require("node:child_process");
3421
3421
 
3422
3422
  // src/cli-shared.ts
3423
3423
  var import_node_child_process3 = require("node:child_process");
@@ -13078,8 +13078,8 @@ function selectPrunablePluginVersions(names, currentVersion) {
13078
13078
  }
13079
13079
  return newestFirst.filter((v) => !keep.has(v));
13080
13080
  }
13081
- function pluginCacheRoot(home) {
13082
- return `${home}/.claude/plugins/cache/mutmutco/mmi`;
13081
+ function pluginCacheRootForConfig(configRoot) {
13082
+ return `${configRoot}/plugins/cache/mutmutco/mmi`;
13083
13083
  }
13084
13084
  function runningPluginVersion(env, cliVersion) {
13085
13085
  const root = env.CLAUDE_PLUGIN_ROOT?.trim();
@@ -13087,11 +13087,11 @@ function runningPluginVersion(env, cliVersion) {
13087
13087
  if (leaf && isVersionDirName(leaf)) return leaf;
13088
13088
  return cliVersion && isVersionDirName(cliVersion) ? cliVersion : void 0;
13089
13089
  }
13090
- function pluginCacheStagingRoot(home) {
13091
- return `${home}/.claude/plugins/cache`;
13090
+ function pluginCacheStagingRootForConfig(configRoot) {
13091
+ return `${configRoot}/plugins/cache`;
13092
13092
  }
13093
- function installedPluginsPath(home) {
13094
- return `${home}/.claude/plugins/installed_plugins.json`;
13093
+ function installedPluginsPathForConfig(configRoot) {
13094
+ return `${configRoot}/plugins/installed_plugins.json`;
13095
13095
  }
13096
13096
  var STAGING_DIR = /^temp_git_\d{13}_[a-z0-9]+$/i;
13097
13097
  function isStagingDirName(name) {
@@ -13163,13 +13163,17 @@ function humanStagingAge(ms) {
13163
13163
  return `${Math.floor(hours / 24)}d old`;
13164
13164
  }
13165
13165
  function buildPluginCachePlan(home, running, deps, opts = {}) {
13166
- const cacheRoot = pluginCacheRoot(home);
13167
- const stagingRoot = pluginCacheStagingRoot(home);
13166
+ const configRoot = opts.configRoot ?? `${home}/.claude`;
13167
+ const cacheRoot = pluginCacheRootForConfig(configRoot);
13168
+ const stagingRoot = pluginCacheStagingRootForConfig(configRoot);
13168
13169
  let stagingEntries;
13169
- try {
13170
- stagingEntries = deps.listStagingDirs(stagingRoot);
13171
- } catch {
13172
- stagingEntries = [];
13170
+ if (opts.includeStaging === false) stagingEntries = [];
13171
+ else {
13172
+ try {
13173
+ stagingEntries = deps.listStagingDirs(stagingRoot);
13174
+ } catch {
13175
+ stagingEntries = [];
13176
+ }
13173
13177
  }
13174
13178
  let referenced;
13175
13179
  try {
@@ -16579,8 +16583,10 @@ function runDocRefs(root, deps = {}) {
16579
16583
  const listDocs = deps.listDocs ?? defaultListDocs;
16580
16584
  const isIgnored = deps.isIgnored ?? ((paths) => defaultIsIgnored(root, paths));
16581
16585
  const commandPaths = deps.commandPaths ?? null;
16586
+ const walked = listDocs(root);
16587
+ const ignoredDocs = walked.length ? isIgnored(walked) : /* @__PURE__ */ new Set();
16582
16588
  const docs2 = Object.fromEntries(
16583
- listDocs(root).map((rel) => [rel, readFile7((0, import_node_path16.join)(root, rel))]).filter(([, body]) => body != null)
16589
+ walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile7((0, import_node_path16.join)(root, rel))]).filter(([, body]) => body != null)
16584
16590
  );
16585
16591
  const findings = [
16586
16592
  ...checkPins(root, readFile7, docs2).findings,
@@ -21605,7 +21611,7 @@ var import_node_fs27 = require("node:fs");
21605
21611
  var import_promises6 = require("node:fs/promises");
21606
21612
  var import_node_path26 = require("node:path");
21607
21613
  var import_node_os8 = require("node:os");
21608
- var import_node_child_process12 = require("node:child_process");
21614
+ var import_node_child_process13 = require("node:child_process");
21609
21615
 
21610
21616
  // src/board-advance.ts
21611
21617
  function repoOf2(ref) {
@@ -21772,6 +21778,7 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
21772
21778
 
21773
21779
  // src/plugin-guard-io.ts
21774
21780
  var import_node_fs25 = require("node:fs");
21781
+ var import_node_child_process12 = require("node:child_process");
21775
21782
  var import_node_path24 = require("node:path");
21776
21783
  var import_node_os6 = require("node:os");
21777
21784
 
@@ -21797,9 +21804,10 @@ function buildPluginGuardLine(state, opts = {}) {
21797
21804
  var isWin = process.platform === "win32";
21798
21805
  var MMI_PLUGIN_ID = "mmi@mutmutco";
21799
21806
  var LEGACY_MMI_MARKETPLACE = "mmi";
21807
+ var CODEX_MARKETPLACE = "mutmutco";
21800
21808
  function detectSurface(env) {
21801
21809
  const has = (k) => Boolean(env[k]?.trim());
21802
- if (env.MMI_AGENT_SURFACE === "codex" || has("CODEX_HOME") || (env.CLAUDE_PLUGIN_ROOT ?? "").includes(".codex")) {
21810
+ if (env.MMI_AGENT_SURFACE === "codex" || has("CODEX_THREAD_ID") || has("CODEX_MANAGED_BY_NPM") || has("CODEX_MANAGED_PACKAGE_ROOT") || (env.CLAUDE_PLUGIN_ROOT ?? "").includes(".codex")) {
21803
21811
  return "codex";
21804
21812
  }
21805
21813
  if (env.MMI_AGENT_SURFACE === "kimi" || has("KIMI_PLUGIN_ROOT") || has("KIMI_CODE_HOME")) {
@@ -21852,6 +21860,7 @@ function reloadAction(surface) {
21852
21860
  }
21853
21861
  }
21854
21862
  var CLAUDE_RECOVERY = `claude plugin marketplace remove ${LEGACY_MMI_MARKETPLACE} && claude plugin marketplace remove mutmutco && claude plugin marketplace add mutmutco/MMI-Hub --ref main && claude plugin install mmi@mutmutco`;
21863
+ var CODEX_RECOVERY = "codex plugin remove mmi@mutmutco && codex plugin marketplace remove mutmutco && codex plugin marketplace add mutmutco/MMI-Hub --ref main && codex plugin add mmi@mutmutco";
21855
21864
  var PLUGIN_SURFACE_HEAL = {
21856
21865
  claude: {
21857
21866
  delivery: "plugin-cli",
@@ -21865,6 +21874,18 @@ var PLUGIN_SURFACE_HEAL = {
21865
21874
  ],
21866
21875
  fix: (surface) => `${CLAUDE_RECOVERY} # then ${reloadAction(surface)} to reload MMI commands`,
21867
21876
  updateRecipe: [CLAUDE_RECOVERY]
21877
+ },
21878
+ codex: {
21879
+ delivery: "plugin-cli",
21880
+ recovery: CODEX_RECOVERY,
21881
+ healSteps: [
21882
+ { args: ["plugin", "remove", MMI_PLUGIN_ID], gated: false },
21883
+ { args: ["plugin", "marketplace", "remove", CODEX_MARKETPLACE], gated: false },
21884
+ { args: ["plugin", "marketplace", "add", "mutmutco/MMI-Hub", "--ref", "main"], gated: true },
21885
+ { args: ["plugin", "add", MMI_PLUGIN_ID], gated: true }
21886
+ ],
21887
+ fix: (surface) => `${CODEX_RECOVERY} # then ${reloadAction(surface)} and review /hooks`,
21888
+ updateRecipe: [CODEX_RECOVERY]
21868
21889
  }
21869
21890
  };
21870
21891
  function nonClaudeSurfaceHealMessage(surface) {
@@ -21913,33 +21934,97 @@ var NPM_VIEW_TIMEOUT_MS = 15e3;
21913
21934
  function runHostBin(bin, args, opts) {
21914
21935
  return isWin ? execFileP2("cmd.exe", ["/c", bin, ...args], opts) : execFileP2(bin, args, opts);
21915
21936
  }
21916
- function surfaceHomeDir(surface) {
21917
- if (surface === "codex") return ".codex";
21918
- if (surface === "kimi") return ".kimi-code";
21919
- return ".claude";
21937
+ function surfaceConfigRoot(surface, env = process.env, home = (0, import_node_os6.homedir)()) {
21938
+ if (surface === "codex") return env.CODEX_HOME?.trim() || (0, import_node_path24.join)(home, ".codex");
21939
+ if (surface === "kimi") return env.KIMI_CODE_HOME?.trim() || (0, import_node_path24.join)(home, ".kimi-code");
21940
+ return (0, import_node_path24.join)(home, ".claude");
21920
21941
  }
21921
- var installedPluginsPath2 = (surface = detectSurface(process.env)) => {
21922
- return (0, import_node_path24.join)((0, import_node_os6.homedir)(), surfaceHomeDir(surface), "plugins", "installed_plugins.json");
21942
+ var installedPluginsPath = (surface = detectSurface(process.env)) => {
21943
+ return (0, import_node_path24.join)(surfaceConfigRoot(surface), "plugins", "installed_plugins.json");
21923
21944
  };
21924
21945
  function readInstalledPlugins(surface = detectSurface(process.env)) {
21925
21946
  try {
21926
- return JSON.parse((0, import_node_fs25.readFileSync)(installedPluginsPath2(surface), "utf8"));
21947
+ return JSON.parse((0, import_node_fs25.readFileSync)(installedPluginsPath(surface), "utf8"));
21927
21948
  } catch {
21928
21949
  return null;
21929
21950
  }
21930
21951
  }
21931
- function marketplaceCloneCandidates(surface, home) {
21952
+ function marketplaceCloneCandidates(surface, home, env = process.env) {
21932
21953
  if (surface === "codex") {
21954
+ const root = surfaceConfigRoot(surface, env, home);
21933
21955
  return [
21934
- (0, import_node_path24.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
21935
- (0, import_node_path24.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
21956
+ (0, import_node_path24.join)(root, ".tmp", "marketplaces", CODEX_MARKETPLACE),
21957
+ (0, import_node_path24.join)(root, "plugins", "marketplaces", CODEX_MARKETPLACE)
21936
21958
  ];
21937
21959
  }
21938
21960
  if (surface === "kimi") return [];
21939
21961
  return [(0, import_node_path24.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
21940
21962
  }
21941
- function marketplaceClonePresent(surface, home, exists = import_node_fs25.existsSync) {
21942
- return marketplaceCloneCandidates(surface, home).some(exists);
21963
+ function marketplaceClonePresent(surface, home, exists = import_node_fs25.existsSync, env = process.env) {
21964
+ return marketplaceCloneCandidates(surface, home, env).some(exists);
21965
+ }
21966
+ function runHostBinSync(bin, args) {
21967
+ return isWin ? (0, import_node_child_process12.execFileSync)("cmd.exe", ["/c", bin, ...args], {
21968
+ encoding: "utf8",
21969
+ stdio: ["ignore", "pipe", "ignore"],
21970
+ timeout: 15e3
21971
+ }) : (0, import_node_child_process12.execFileSync)(bin, args, {
21972
+ encoding: "utf8",
21973
+ stdio: ["ignore", "pipe", "ignore"],
21974
+ timeout: 15e3
21975
+ });
21976
+ }
21977
+ function codexPluginStatus() {
21978
+ try {
21979
+ const raw = runHostBinSync("codex", ["plugin", "list", "--json"]);
21980
+ const parsed = JSON.parse(raw);
21981
+ const plugin = parsed.installed?.find((entry) => entry.pluginId === MMI_PLUGIN_ID);
21982
+ return {
21983
+ installed: plugin?.installed === true,
21984
+ enabled: plugin?.enabled === true,
21985
+ ...plugin?.version ? { version: plugin.version } : {}
21986
+ };
21987
+ } catch {
21988
+ return { installed: false, enabled: false };
21989
+ }
21990
+ }
21991
+ function countCodexHookCommands(path2) {
21992
+ try {
21993
+ const parsed = JSON.parse((0, import_node_fs25.readFileSync)(path2, "utf8"));
21994
+ let count = 0;
21995
+ for (const groups of Object.values(parsed.hooks ?? {})) {
21996
+ for (const group of groups) {
21997
+ count += (group.hooks ?? []).filter((hook) => hook.type === "command").length;
21998
+ }
21999
+ }
22000
+ return count;
22001
+ } catch {
22002
+ return 0;
22003
+ }
22004
+ }
22005
+ function codexHookTrustState(status = codexPluginStatus()) {
22006
+ if (!status.installed || !status.version) {
22007
+ return { applicable: false, trusted: false, trustedCount: 0, requiredCount: 0 };
22008
+ }
22009
+ const root = surfaceConfigRoot("codex");
22010
+ const hooksPath = (0, import_node_path24.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", status.version, "hooks", "codex-hooks.json");
22011
+ const requiredCount = countCodexHookCommands(hooksPath);
22012
+ let config = "";
22013
+ try {
22014
+ config = (0, import_node_fs25.readFileSync)((0, import_node_path24.join)(root, "config.toml"), "utf8");
22015
+ } catch {
22016
+ return { applicable: true, trusted: false, trustedCount: 0, requiredCount };
22017
+ }
22018
+ const trustedCount = (config.match(/\[hooks\.state\."mmi@mutmutco:hooks\/codex-hooks\.json:[^"]+"\]/g) ?? []).length;
22019
+ return {
22020
+ applicable: true,
22021
+ // Codex stores a hash beside each approval row and intentionally leaves stale rows behind. The hash
22022
+ // input is not a public host contract, so row count can prove only that approvals EXISTED — never that
22023
+ // the current commands are trusted. `/hooks` is the only honest freshness check.
22024
+ trusted: false,
22025
+ trustedCount,
22026
+ requiredCount
22027
+ };
21943
22028
  }
21944
22029
  async function fetchNpmReleasedVersion() {
21945
22030
  try {
@@ -21960,17 +22045,20 @@ async function npmSelfUpdateCli(target) {
21960
22045
  }
21961
22046
  }
21962
22047
  function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRepo = false) {
21963
- const homeDir = surfaceHomeDir(surface);
22048
+ const root = surfaceConfigRoot(surface);
21964
22049
  const installed = readInstalledPlugins(surface);
22050
+ const codexStatus = surface === "codex" ? codexPluginStatus() : void 0;
21965
22051
  return {
21966
22052
  isOrgRepo,
21967
22053
  installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
21968
22054
  marketplaceClonePresent: marketplaceClonePresent(surface, (0, import_node_os6.homedir)()),
21969
22055
  // Kimi keeps no plugin cache dir — installs are copied to plugins/managed/<id> and run from there.
21970
- pluginCachePresent: surface === "kimi" ? (0, import_node_fs25.existsSync)((0, import_node_path24.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "managed", "mmi")) : (0, import_node_fs25.existsSync)((0, import_node_path24.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
22056
+ pluginCachePresent: surface === "kimi" ? (0, import_node_fs25.existsSync)((0, import_node_path24.join)(root, "plugins", "managed", "mmi")) : surface === "codex" ? Boolean(
22057
+ codexStatus?.installed && codexStatus.enabled && codexStatus.version && (0, import_node_fs25.existsSync)((0, import_node_path24.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", codexStatus.version))
22058
+ ) : (0, import_node_fs25.existsSync)((0, import_node_path24.join)(root, "plugins", "cache", "mutmutco", "mmi"))
21971
22059
  };
21972
22060
  }
21973
- function claudePluginGuardState(isOrgRepo) {
22061
+ function activePluginGuardState(isOrgRepo) {
21974
22062
  return buildPluginGuardDecision(snapshotPluginGuardInput(detectSurface(process.env), isOrgRepo)).state;
21975
22063
  }
21976
22064
  async function runClaudePlugin(args) {
@@ -21981,6 +22069,14 @@ async function runClaudePlugin(args) {
21981
22069
  return false;
21982
22070
  }
21983
22071
  }
22072
+ async function runCodexPlugin(args) {
22073
+ try {
22074
+ await runHostBin("codex", args, { timeout: CLAUDE_PLUGIN_TIMEOUT_MS });
22075
+ return true;
22076
+ } catch {
22077
+ return false;
22078
+ }
22079
+ }
21984
22080
  async function marketplaceAddRefSupported(bin) {
21985
22081
  try {
21986
22082
  const { stdout, stderr } = await runHostBin(bin, ["plugin", "marketplace", "add", "--help"], {
@@ -22007,18 +22103,23 @@ function pluginReadGrantNote(login = "<your-github-login>") {
22007
22103
  gh api -X PUT repos/${PLUGIN_READ_REPO2}/collaborators/${login} -f permission=pull`;
22008
22104
  }
22009
22105
  async function applyPluginHeal(surface, log, opts) {
22010
- if (!opts?.force && surfaceToken(surface) !== "claude") return false;
22011
- const tableSteps = PLUGIN_SURFACE_HEAL.claude.healSteps;
22106
+ const token = surfaceToken(surface);
22107
+ if (token !== "claude" && token !== "codex") return false;
22108
+ if (!opts?.force && !PLUGIN_SURFACE_HEAL[token]) return false;
22109
+ const descriptor = PLUGIN_SURFACE_HEAL[token];
22110
+ const tableSteps = descriptor.healSteps;
22012
22111
  if (!tableSteps) return false;
22013
- const refSupported = await marketplaceAddRefSupported("claude");
22112
+ const bin = token;
22113
+ const refSupported = await marketplaceAddRefSupported(bin);
22014
22114
  const { steps } = adaptHealStepsForRefSupport(tableSteps, refSupported);
22015
- log(healBannerLine("claude", "claude", refSupported));
22115
+ log(healBannerLine(bin, token, refSupported));
22016
22116
  const pinsPath = (0, import_node_path24.join)((0, import_node_os6.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE);
22017
- const pins = captureMarketplacePins(readKnownMarketplacesFile(pinsPath), [MMI_MARKETPLACE_NAME, JERV_MARKETPLACE_NAME]);
22117
+ const pins = token === "claude" ? captureMarketplacePins(readKnownMarketplacesFile(pinsPath), [MMI_MARKETPLACE_NAME, JERV_MARKETPLACE_NAME]) : /* @__PURE__ */ new Map();
22018
22118
  for (const step of steps) {
22019
- if (healStepAborts(step, await runClaudePlugin([...step.args]))) return false;
22119
+ const ok = token === "claude" ? await runClaudePlugin([...step.args]) : await runCodexPlugin([...step.args]);
22120
+ if (healStepAborts(step, ok)) return false;
22020
22121
  }
22021
- const restored = restoreMarketplacePinsOnDisk(pinsPath, pins);
22122
+ const restored = token === "claude" ? restoreMarketplacePinsOnDisk(pinsPath, pins) : void 0;
22022
22123
  if (restored) log(` ${restored}`);
22023
22124
  return true;
22024
22125
  }
@@ -22034,6 +22135,23 @@ async function healClaudePluginForDoctor(surface = detectSurface(process.env)) {
22034
22135
  detail: ok ? `marketplace remove \u2192 add \u2192 install succeeded${pinNote ? `; ${pinNote}` : ""}` : `\`claude plugin\` reinstall failed or was skipped${steps.length ? ` (${steps[steps.length - 1]})` : ""}`
22035
22136
  };
22036
22137
  }
22138
+ async function healActivePluginForDoctor(surface = detectSurface(process.env)) {
22139
+ const token = surfaceToken(surface);
22140
+ if (token !== "claude" && token !== "codex") {
22141
+ return { ok: false, detail: `not a supported plugin surface (${surface})` };
22142
+ }
22143
+ if (token === "claude") return healClaudePluginForDoctor(surface);
22144
+ const steps = [];
22145
+ const applied = await applyPluginHeal(surface, (msg) => steps.push(msg.trim()));
22146
+ const snapshot = applied ? snapshotPluginGuardInput(surface, true) : void 0;
22147
+ const guardState = snapshot ? buildPluginGuardDecision(snapshot).state : "unresolved";
22148
+ const ok = applied && guardState === "healthy";
22149
+ const verification = snapshot ? `record=${snapshot.installRecordPresent ? "yes" : "no"}, marketplace=${snapshot.marketplaceClonePresent ? "yes" : "no"}, enabled-cache=${snapshot.pluginCachePresent ? "yes" : "no"}` : "reinstall steps did not complete";
22150
+ return {
22151
+ ok,
22152
+ detail: ok ? `marketplace remove \u2192 add --ref main \u2192 plugin add succeeded; full guard verified (${verification})` : `\`codex plugin\` reinstall failed full-guard verification (${verification})${steps.length ? `; ${steps[steps.length - 1]}` : ""}`
22153
+ };
22154
+ }
22037
22155
  function readKnownMarketplacesFile(path2) {
22038
22156
  try {
22039
22157
  return (0, import_node_fs25.existsSync)(path2) ? (0, import_node_fs25.readFileSync)(path2, "utf8") : void 0;
@@ -22059,9 +22177,10 @@ function restoreMarketplacePinsOnDisk(path2, pins) {
22059
22177
  return failed.length ? `restore did NOT take for ${failed.map(([n]) => n).join(", ")} \u2014 re-pin by hand` : `re-pinned ${[...pins.keys()].join(", ")} (auto-update + catalog ref survive the reinstall)`;
22060
22178
  }
22061
22179
  async function runGuard(readOrigin) {
22180
+ const surface = detectSurface(process.env);
22062
22181
  try {
22063
- const surface = detectSurface(process.env);
22064
- if (surfaceToken(surface) !== "claude") {
22182
+ const token = surfaceToken(surface);
22183
+ if (token !== "claude" && token !== "codex") {
22065
22184
  process.exitCode = 0;
22066
22185
  return;
22067
22186
  }
@@ -22070,24 +22189,39 @@ async function runGuard(readOrigin) {
22070
22189
  const { state } = buildPluginGuardDecision(input);
22071
22190
  const { line, exitCode } = buildPluginGuardLine(state);
22072
22191
  if (line) console.error(line);
22192
+ if (token === "codex" && exitCode === 0) {
22193
+ const trust = codexHookTrustState();
22194
+ if (trust.applicable && !trust.trusted) {
22195
+ console.error(`[mmi-guard] MMI Codex hook trust cannot be verified non-interactively (${trust.trustedCount}/${trust.requiredCount} approval rows present); run /hooks and review the current hashes.`);
22196
+ }
22197
+ }
22073
22198
  process.exitCode = exitCode;
22074
22199
  } catch {
22075
- process.exitCode = 0;
22200
+ if (surfaceToken(surface) === "codex") {
22201
+ console.error("[mmi-guard] Could not inspect the active Codex plugin; run `mmi-cli plugin heal`.");
22202
+ process.exitCode = 1;
22203
+ } else {
22204
+ process.exitCode = 0;
22205
+ }
22076
22206
  }
22077
22207
  }
22078
22208
  async function runPluginHeal(surface = detectSurface(process.env)) {
22079
- if (surfaceToken(surface) !== "claude") {
22080
- console.log(nonClaudeSurfaceHealMessage(surfaceToken(surface) ?? void 0));
22209
+ const token = surfaceToken(surface);
22210
+ if (token !== "claude" && token !== "codex") {
22211
+ console.log(nonClaudeSurfaceHealMessage(token ?? void 0));
22081
22212
  return;
22082
22213
  }
22083
- const descriptor = PLUGIN_SURFACE_HEAL.claude;
22084
- const healed = await applyPluginHeal(surface, console.log, { force: true });
22214
+ const descriptor = PLUGIN_SURFACE_HEAL[token];
22215
+ const applied = await applyPluginHeal(surface, console.log, { force: true });
22216
+ const healed = token === "codex" ? applied && buildPluginGuardDecision(snapshotPluginGuardInput(surface, true)).state === "healthy" : applied;
22085
22217
  if (healed) {
22086
- console.log(` \u2713 MMI plugin reinstalled \u2014 ${reloadAction(surface)} to load MMI commands`);
22218
+ const trust = token === "codex" ? " Then run /hooks and review + trust the MMI hooks." : "";
22219
+ console.log(` \u2713 MMI plugin reinstalled \u2014 ${reloadAction(surface)} to load MMI commands.${trust}`);
22087
22220
  } else {
22088
- const refSupported = await marketplaceAddRefSupported("claude");
22221
+ process.exitCode = 1;
22222
+ const refSupported = await marketplaceAddRefSupported(token);
22089
22223
  const recovery = refSupported ? descriptor.recovery : recoveryWithoutRef(descriptor.recovery);
22090
- const note = refAbsenceNote("claude", refSupported);
22224
+ const note = refAbsenceNote(token, refSupported);
22091
22225
  console.log(` \u2717 Auto-heal failed or was skipped. Run manually:
22092
22226
  ${recovery}${note}${pluginReadGrantNote()}`);
22093
22227
  }
@@ -22649,7 +22783,7 @@ async function remoteBranchExists2(branch, options = {}) {
22649
22783
  }
22650
22784
  var COMPOSE_TIMEOUT_MS = 12e4;
22651
22785
  function spawnDeferredGcSweep() {
22652
- spawnDetachedSelf(["worktree", "gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
22786
+ spawnDetachedSelf(["worktree", "gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process13.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
22653
22787
  }
22654
22788
  async function createDeferredWorktreeStore() {
22655
22789
  try {
@@ -23606,7 +23740,7 @@ ${spec.body ?? ""}`;
23606
23740
  const hash = (0, import_node_crypto5.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
23607
23741
  return `${batchKey}:${hash}`;
23608
23742
  }
23609
- var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "parent", "repo"]);
23743
+ var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo"]);
23610
23744
  function validateBatchSpecs(specs) {
23611
23745
  const errors = [];
23612
23746
  const validated = [];
@@ -23623,6 +23757,11 @@ function validateBatchSpecs(specs) {
23623
23757
  errors.push({ row, error: `unknown key(s): ${unknown.join(", ")} \u2014 expected only: ${[...BATCH_SPEC_KEYS].join(", ")}` });
23624
23758
  continue;
23625
23759
  }
23760
+ if (spec.label !== void 0) {
23761
+ const alias = Array.isArray(spec.label) ? spec.label : [spec.label];
23762
+ spec.labels = [...spec.labels ?? [], ...alias];
23763
+ delete spec.label;
23764
+ }
23626
23765
  if (spec.repo !== void 0 && !/^[\w.-]+\/[\w.-]+$/.test(spec.repo)) {
23627
23766
  errors.push({ row, error: `bad repo "${spec.repo}" \u2014 expected owner/repo` });
23628
23767
  continue;
@@ -25197,6 +25336,10 @@ function pluginHealTrigger(probe) {
25197
25336
  }
25198
25337
  function checkClaudePlugin(probe) {
25199
25338
  const { installed, released, guardState } = probe;
25339
+ const codex = probe.surface === "codex";
25340
+ const id = codex ? "codex-plugin" : "claude-plugin";
25341
+ const label = codex ? "Codex plugin" : "Claude plugin";
25342
+ const restart = codex ? "restart Codex" : "restart Claude";
25200
25343
  const evidence = [
25201
25344
  `installed: ${installed ?? "(none)"}`,
25202
25345
  // #3485 item 7: when the value was reused from the banner's once-a-day cache, say so and say how old.
@@ -25206,38 +25349,57 @@ function checkClaudePlugin(probe) {
25206
25349
  const trigger = pluginHealTrigger(probe);
25207
25350
  if (trigger === "unresolved") {
25208
25351
  return {
25209
- id: "claude-plugin",
25352
+ id,
25210
25353
  ok: false,
25211
- label: "Claude plugin",
25354
+ label,
25212
25355
  detail: guardState === "no-install" ? "not installed" : "unresolved (marketplace/cache missing)",
25213
- fix: "run `mmi-cli doctor --apply` (or `mmi-cli plugin heal`) to reinstall the MMI marketplace + plugin, then restart Claude",
25356
+ fix: `run \`mmi-cli doctor --apply\` (or \`mmi-cli plugin heal\`) to reinstall the MMI marketplace + plugin, then ${restart}`,
25214
25357
  verbose: evidence
25215
25358
  };
25216
25359
  }
25217
25360
  if (trigger === "behind") {
25218
25361
  return {
25219
- id: "claude-plugin",
25362
+ id,
25220
25363
  ok: false,
25221
- label: "Claude plugin",
25364
+ label,
25222
25365
  detail: `${installed} \u2192 ${released}`,
25223
25366
  // Name the CLI verb that actually fixes it, like every other red row — `/plugin` is the manual
25224
25367
  // fallback, not the first resort (#3282).
25225
- fix: "run `mmi-cli doctor --apply` (or `mmi-cli plugin heal`) to reinstall it, then restart Claude",
25368
+ fix: `run \`mmi-cli doctor --apply\` (or \`mmi-cli plugin heal\`) to reinstall it, then ${restart}`,
25226
25369
  verbose: evidence
25227
25370
  };
25228
25371
  }
25229
25372
  if (!released) {
25230
25373
  return {
25231
- id: "claude-plugin",
25374
+ id,
25232
25375
  ok: false,
25233
25376
  reportOnly: true,
25234
- label: "Claude plugin",
25377
+ label,
25235
25378
  detail: `${installed ?? "installed"} \u2014 freshness UNKNOWN, the published version could not be read`,
25236
25379
  fix: "check it directly: `npm view @mutmutco/cli version`, then `mmi-cli plugin heal` if behind",
25237
25380
  verbose: evidence
25238
25381
  };
25239
25382
  }
25240
- return { id: "claude-plugin", ok: true, label: "Claude plugin", ...installed ? { detail: installed } : {}, verbose: evidence };
25383
+ return { id, ok: true, label, ...installed ? { detail: installed } : {}, verbose: evidence };
25384
+ }
25385
+ function checkCodexHookTrust(probe) {
25386
+ if (!probe?.applicable) return null;
25387
+ const evidence = [
25388
+ `stored approval rows: ${probe.trustedCount}/${probe.requiredCount}`,
25389
+ "current command hashes: verify interactively in /hooks"
25390
+ ];
25391
+ if (probe.trusted) {
25392
+ return { id: "codex-hook-trust", ok: true, label: "Codex hook trust", detail: "trusted", verbose: evidence };
25393
+ }
25394
+ return {
25395
+ id: "codex-hook-trust",
25396
+ ok: true,
25397
+ warn: true,
25398
+ label: "Codex hook trust",
25399
+ detail: probe.requiredCount > 0 ? `${probe.trustedCount}/${probe.requiredCount} approval rows present \u2014 current hashes unverified` : "hook bundle could not be verified",
25400
+ fix: probe.requiredCount > 0 ? "Codex does not allow silent hook approval; run `/hooks`, review the MMI commands, and trust them" : "run `mmi-cli plugin heal`, restart Codex, then review and trust MMI under `/hooks`",
25401
+ verbose: evidence
25402
+ };
25241
25403
  }
25242
25404
  function checkCliVersion(input, releasedNote) {
25243
25405
  const report = buildVersionLagReport(input);
@@ -25475,6 +25637,8 @@ async function runDoctorClean(opts, io, deps) {
25475
25637
  ]);
25476
25638
  const ghInstalled2 = login ? true : await deps.ghInstalled();
25477
25639
  const installed = deps.installedPluginVersion();
25640
+ const pluginSurface = deps.pluginSurface?.() ?? "claude-cli";
25641
+ const codexSurface = pluginSurface === "codex";
25478
25642
  const checks = [];
25479
25643
  let restartPending = false;
25480
25644
  checks.push(checkGithubAuth({ login, ghInstalled: ghInstalled2, reach }));
@@ -25501,10 +25665,18 @@ async function runDoctorClean(opts, io, deps) {
25501
25665
  }
25502
25666
  }
25503
25667
  const releasedNote = deps.releasedVersionNote?.();
25504
- const pluginProbe = { installed, released, guardState: deps.pluginGuardState(isOrgRepo), releasedNote };
25668
+ const pluginProbe = {
25669
+ installed,
25670
+ released,
25671
+ guardState: deps.pluginGuardState(isOrgRepo),
25672
+ releasedNote,
25673
+ surface: pluginSurface
25674
+ };
25505
25675
  const healTrigger = pluginHealTrigger(pluginProbe);
25676
+ let pluginHealed = false;
25506
25677
  if (applyEnv && deps.healPlugin && healTrigger) {
25507
25678
  const heal = await deps.healPlugin();
25679
+ pluginHealed = heal.ok;
25508
25680
  const measured = healTrigger === "behind" ? `${installed} \u2192 ${released}` : "unresolved install";
25509
25681
  const healEvidence = [
25510
25682
  `installed: ${installed ?? "(none)"}`,
@@ -25513,22 +25685,22 @@ async function runDoctorClean(opts, io, deps) {
25513
25685
  `heal: ${heal.detail}`
25514
25686
  ];
25515
25687
  checks.push(heal.ok ? {
25516
- id: "claude-plugin",
25688
+ id: codexSurface ? "codex-plugin" : "claude-plugin",
25517
25689
  ok: true,
25518
- label: "Claude plugin",
25519
- detail: `${measured} \u2014 reinstalled via the MMI marketplace (remove \u2192 add \u2192 install)`,
25690
+ label: codexSurface ? "Codex plugin" : "Claude plugin",
25691
+ detail: `${measured} \u2014 reinstalled via the MMI marketplace (remove \u2192 add \u2192 ${codexSurface ? "add" : "install"})${codexSurface ? "; review trust in /hooks" : ""}`,
25520
25692
  verbose: healEvidence
25521
25693
  } : {
25522
- id: "claude-plugin",
25694
+ id: codexSurface ? "codex-plugin" : "claude-plugin",
25523
25695
  ok: false,
25524
- label: "Claude plugin",
25696
+ label: codexSurface ? "Codex plugin" : "Claude plugin",
25525
25697
  detail: measured,
25526
25698
  // #3489: a heal skipped because another doctor holds the env-heal lock is a real ✗ — this run did
25527
25699
  // not fix what it found — but it is not this invocation's to clear. The other process is doing it;
25528
25700
  // waiting is the correct response and a re-run finds it healed. A heal that RAN and failed is a
25529
25701
  // genuine gap and still gates.
25530
25702
  ...heal.skipped ? { reportOnly: true } : {},
25531
- fix: heal.skipped ? `${heal.detail} \u2014 another doctor on this machine is healing it now; re-run once it finishes` : `auto-heal failed (${heal.detail}) \u2014 run \`mmi-cli plugin heal\`, then restart Claude`,
25703
+ fix: heal.skipped ? `${heal.detail} \u2014 another doctor on this machine is healing it now; re-run once it finishes` : `auto-heal failed (${heal.detail}) \u2014 run \`mmi-cli plugin heal\`, then ${codexSurface ? "restart Codex" : "restart Claude"}`,
25532
25704
  verbose: healEvidence
25533
25705
  });
25534
25706
  if (!heal.skipped) restartPending = true;
@@ -25539,6 +25711,10 @@ async function runDoctorClean(opts, io, deps) {
25539
25711
  if (!plugin.ok) restartPending = true;
25540
25712
  }
25541
25713
  }
25714
+ if (codexSurface && (pluginProbe.guardState === "healthy" || pluginHealed)) {
25715
+ const trust = checkCodexHookTrust(deps.pluginTrustState?.());
25716
+ if (trust) checks.push(trust);
25717
+ }
25542
25718
  const cliInput = { currentVersion: deps.currentCliVersion(), releasedVersion: released };
25543
25719
  const cliReport = buildVersionLagReport(cliInput);
25544
25720
  if (applyEnv && deps.updateCli && versionAutoUpdateAction(cliReport) === "npm") {
@@ -25672,7 +25848,12 @@ async function runDoctorClean(opts, io, deps) {
25672
25848
  const exitCode = doctorReportExitCode(checks);
25673
25849
  if (opts.json) {
25674
25850
  const payload = opts.verbose ? checks : checks.map(({ verbose: _evidence, ...check }) => check);
25675
- io.log(JSON.stringify({ checks: payload, restartPending, exitCode }, null, 2));
25851
+ io.log(JSON.stringify({
25852
+ checks: payload,
25853
+ restartPending,
25854
+ ...restartPending ? { restartAction: codexSurface ? "restart Codex" : "restart Claude" } : {},
25855
+ exitCode
25856
+ }, null, 2));
25676
25857
  return exitCode;
25677
25858
  }
25678
25859
  if (opts.banner) {
@@ -25681,10 +25862,12 @@ async function runDoctorClean(opts, io, deps) {
25681
25862
  io.log(renderReport([c], { restartPending: false }));
25682
25863
  if (opts.verbose) for (const evidence of c.verbose ?? []) io.log(`${VERBOSE_INDENT}${evidence}`);
25683
25864
  }
25684
- if (restartPending) io.log(RESTART_LINE);
25865
+ if (restartPending) io.log(codexSurface ? "\u21BB Restart Codex to finish." : RESTART_LINE);
25685
25866
  return 0;
25686
25867
  }
25687
- io.log(renderDoctorText(checks, { verbose: Boolean(opts.verbose), restartPending }));
25868
+ const rendered = renderDoctorText(checks, { verbose: Boolean(opts.verbose), restartPending: restartPending && !codexSurface });
25869
+ io.log(restartPending && codexSurface ? `${rendered}
25870
+ \u21BB Restart Codex to finish.` : rendered);
25688
25871
  return exitCode;
25689
25872
  }
25690
25873
 
@@ -25770,9 +25953,9 @@ function ghAccountCaveat(announcedLogin, accounts) {
25770
25953
  var import_node_fs32 = require("node:fs");
25771
25954
  var import_node_os10 = require("node:os");
25772
25955
  var import_node_path30 = require("node:path");
25773
- var import_node_child_process13 = require("node:child_process");
25956
+ var import_node_child_process14 = require("node:child_process");
25774
25957
  var import_node_util8 = require("node:util");
25775
- var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process13.execFile);
25958
+ var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process14.execFile);
25776
25959
  var MMI_PLUGIN_ID2 = "mmi@mutmutco";
25777
25960
  function installedClaudePluginVersion() {
25778
25961
  try {
@@ -25786,9 +25969,28 @@ function installedClaudePluginVersion() {
25786
25969
  return void 0;
25787
25970
  }
25788
25971
  }
25972
+ function installedActivePluginVersion(surface = detectSurface(process.env)) {
25973
+ if (surface !== "codex") return installedClaudePluginVersion();
25974
+ try {
25975
+ const raw = process.platform === "win32" ? (0, import_node_child_process14.execFileSync)("cmd.exe", ["/c", "codex", "plugin", "list", "--json"], {
25976
+ encoding: "utf8",
25977
+ stdio: ["ignore", "pipe", "ignore"],
25978
+ timeout: 15e3
25979
+ }) : (0, import_node_child_process14.execFileSync)("codex", ["plugin", "list", "--json"], {
25980
+ encoding: "utf8",
25981
+ stdio: ["ignore", "pipe", "ignore"],
25982
+ timeout: 15e3
25983
+ });
25984
+ const parsed = JSON.parse(raw);
25985
+ const plugin = parsed.installed?.find((entry) => entry.pluginId === MMI_PLUGIN_ID2 && entry.installed === true && entry.enabled === true);
25986
+ return plugin?.version;
25987
+ } catch {
25988
+ return void 0;
25989
+ }
25990
+ }
25789
25991
  function worktreeRootSync() {
25790
25992
  try {
25791
- const out = (0, import_node_child_process13.execFileSync)("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
25993
+ const out = (0, import_node_child_process14.execFileSync)("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
25792
25994
  let root = out.endsWith("\n") ? out.slice(0, -1) : out;
25793
25995
  if (process.platform === "win32" && root.endsWith("\r")) root = root.slice(0, -1);
25794
25996
  return root || null;
@@ -25933,8 +26135,10 @@ function mmiDoctorDeps(opts = {}) {
25933
26135
  githubRepoReach: githubRepoReachProbe,
25934
26136
  awsCallerArn,
25935
26137
  isOrgRepo: () => isOrgRepoRoot(),
25936
- installedPluginVersion: installedClaudePluginVersion,
25937
- pluginGuardState: claudePluginGuardState,
26138
+ installedPluginVersion: installedActivePluginVersion,
26139
+ pluginGuardState: activePluginGuardState,
26140
+ pluginSurface: () => detectSurface(process.env),
26141
+ pluginTrustState: () => codexHookTrustState(),
25938
26142
  releasedVersion: throttled ? throttled.read : fetchNpmReleasedVersion,
25939
26143
  releasedVersionNote: throttled ? throttled.note : void 0,
25940
26144
  // #3272: the --apply self-heal for a stale running CLI — npm global, shadows any plugin shim (#2879).
@@ -25945,7 +26149,10 @@ function mmiDoctorDeps(opts = {}) {
25945
26149
  updateCli: (target) => withEnvHealLock("npm global CLI self-update", () => npmSelfUpdateCli(target)),
25946
26150
  // #3282: the --apply self-heal for a stale/unresolved Claude plugin — the same marketplace reinstall
25947
26151
  // `mmi-cli plugin heal` drives. Takes effect on the next Claude reload, so the row asks for a restart.
25948
- healPlugin: () => withEnvHealLock("Claude plugin reinstall", () => healClaudePluginForDoctor()),
26152
+ healPlugin: () => {
26153
+ const surface = detectSurface(process.env);
26154
+ return withEnvHealLock(`${surface === "codex" ? "Codex" : "Claude"} plugin reinstall`, () => healActivePluginForDoctor(surface));
26155
+ },
25949
26156
  currentCliVersion: resolveClientVersion,
25950
26157
  readGitignore,
25951
26158
  writeGitignore,
@@ -25980,10 +26187,14 @@ function mmiDoctorDeps(opts = {}) {
25980
26187
  return { personal, app };
25981
26188
  },
25982
26189
  pluginCache: () => {
26190
+ const surface = detectSurface(process.env);
26191
+ const configRoot = surfaceConfigRoot(surface);
26192
+ const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
25983
26193
  const plan = buildPluginCachePlan(
25984
26194
  (0, import_node_os11.homedir)(),
25985
- runningPluginVersion(process.env, resolveClientVersion()),
25986
- pluginCacheFsDeps((0, import_node_os11.homedir)(), () => 0)
26195
+ running,
26196
+ pluginCacheFsDeps(configRoot, () => 0),
26197
+ { configRoot, includeStaging: surface !== "codex" }
25987
26198
  );
25988
26199
  return {
25989
26200
  stale: plan.prune,
@@ -26032,6 +26243,7 @@ function mmiDoctorDeps(opts = {}) {
26032
26243
  // which branch it would pick one up from. Two local file reads, no network, fail-soft to no rows.
26033
26244
  marketplaceRows: () => {
26034
26245
  try {
26246
+ if (detectSurface(process.env) === "codex") return [];
26035
26247
  const home = (0, import_node_os11.homedir)();
26036
26248
  return marketplaceRows(
26037
26249
  MMI_MARKETPLACE_NAME,
@@ -26423,7 +26635,7 @@ function runWorktreeInstall(command, cwd, quiet) {
26423
26635
  const file = isWin2 ? "cmd.exe" : bin;
26424
26636
  const spawnArgs = isWin2 ? ["/c", bin, ...args] : args;
26425
26637
  return new Promise((resolve5, reject) => {
26426
- const child2 = (0, import_node_child_process14.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
26638
+ const child2 = (0, import_node_child_process15.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
26427
26639
  const timer = setTimeout(() => {
26428
26640
  try {
26429
26641
  child2.kill();
@@ -26694,7 +26906,7 @@ function scheduleRelatedDiscovery(o) {
26694
26906
  try {
26695
26907
  const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
26696
26908
  if (o.repo) args.push("--repo", o.repo);
26697
- spawnDetachedSelf(args, { spawn: import_node_child_process14.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
26909
+ spawnDetachedSelf(args, { spawn: import_node_child_process15.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
26698
26910
  } catch {
26699
26911
  }
26700
26912
  }
@@ -28389,8 +28601,8 @@ function directoryBytes(path2) {
28389
28601
  function listDirEntries(dir) {
28390
28602
  return (0, import_node_fs33.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
28391
28603
  }
28392
- function readInstalledPluginRefs(home) {
28393
- const p = installedPluginsPath(home);
28604
+ function readInstalledPluginRefs(configRoot) {
28605
+ const p = installedPluginsPathForConfig(configRoot);
28394
28606
  if (!(0, import_node_fs33.existsSync)(p)) return [];
28395
28607
  try {
28396
28608
  return installedPluginPaths((0, import_node_fs33.readFileSync)(p, "utf8"));
@@ -28398,7 +28610,7 @@ function readInstalledPluginRefs(home) {
28398
28610
  return null;
28399
28611
  }
28400
28612
  }
28401
- function pluginCacheFsDeps(home, dirBytes) {
28613
+ function pluginCacheFsDeps(configRoot, dirBytes) {
28402
28614
  return {
28403
28615
  exists: (p) => (0, import_node_fs33.existsSync)(p),
28404
28616
  listVersionDirs: (root) => (0, import_node_fs33.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
@@ -28410,14 +28622,14 @@ function pluginCacheFsDeps(home, dirBytes) {
28410
28622
  return { name: d.name, mtimeMs: Date.now() };
28411
28623
  }
28412
28624
  }),
28413
- readInstalledPluginPaths: () => readInstalledPluginRefs(home),
28625
+ readInstalledPluginPaths: () => readInstalledPluginRefs(configRoot),
28414
28626
  now: () => Date.now()
28415
28627
  };
28416
28628
  }
28417
- function stagingApplyFsGuard(home) {
28418
- const stagingRoot = pluginCacheStagingRoot(home);
28629
+ function stagingApplyFsGuard(configRoot) {
28630
+ const stagingRoot = pluginCacheStagingRootForConfig(configRoot);
28419
28631
  return {
28420
- referencedPaths: () => readInstalledPluginRefs(home),
28632
+ referencedPaths: () => readInstalledPluginRefs(configRoot),
28421
28633
  mtimeMs: (name) => {
28422
28634
  const p = (0, import_node_path31.join)(stagingRoot, name);
28423
28635
  if (!(0, import_node_fs33.existsSync)(p)) return null;
@@ -28431,14 +28643,24 @@ function stagingApplyFsGuard(home) {
28431
28643
  };
28432
28644
  }
28433
28645
  program2.command("plugin-prune").description(`prune stale cached MMI plugin versions (keeps running + newest, ${PLUGIN_CACHE_KEEP} total) and orphaned temp_git_* staging dirs; dry-run unless --apply (#2903, #2990)`).option("--apply", "actually delete the stale version dirs + orphaned staging dirs (default: report only)").option("--json", "machine-readable output").action((o) => {
28646
+ const surface = detectSurface(process.env);
28647
+ const configRoot = surfaceConfigRoot(surface);
28648
+ const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
28649
+ if (o.apply && surface === "codex" && !running) {
28650
+ const detail = "plugin prune refused: Codex could not identify the enabled MMI version; no cache was deleted";
28651
+ if (o.json) console.log(JSON.stringify({ ok: false, error: detail }));
28652
+ else console.error(detail);
28653
+ process.exitCode = 1;
28654
+ return;
28655
+ }
28434
28656
  const plan = buildPluginCachePlan(
28435
28657
  (0, import_node_os11.homedir)(),
28436
- runningPluginVersion(process.env, resolveClientVersion()),
28437
- pluginCacheFsDeps((0, import_node_os11.homedir)(), directoryBytes),
28438
- { withBytes: true }
28658
+ running,
28659
+ pluginCacheFsDeps(configRoot, directoryBytes),
28660
+ { withBytes: true, configRoot, includeStaging: surface !== "codex" }
28439
28661
  );
28440
28662
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
28441
- const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs33.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard((0, import_node_os11.homedir)())) : void 0;
28663
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs33.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
28442
28664
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
28443
28665
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
28444
28666
  else console.log(renderPluginCachePlan(plan, result));
@@ -28501,7 +28723,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
28501
28723
  for (const line of scratchGcLines(process.cwd())) bannerIo.log(line);
28502
28724
  const worktreeBanner = worktreeAutoProvisionBanner(process.cwd());
28503
28725
  if (worktreeBanner) {
28504
- spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process14.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
28726
+ spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process15.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
28505
28727
  bannerIo.log(worktreeBanner);
28506
28728
  }
28507
28729
  if (isLinkedWorktree(process.cwd())) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "3.73.0",
3
+ "version": "3.74.0",
4
4
  "description": "MMI Future CLI — the org dev toolbox (board, registry, keyless secrets, release train, bootstrap, doctor) and the cross-IDE engine the MMI plugin's skills and gates drive on Claude, Codex, and Kimi.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",