@mutmutco/cli 3.72.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.
- package/README.md +1 -1
- package/dist/main.cjs +327 -89
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The command-line engine for MMI Future org tooling. It delivers the org-managed `.gitignore` block, reads and claims GitHub Project work, and exposes the model-agnostic commands used by the MMI plugin and non-Claude agents. (Personal agent guides — `AGENTS.md` / `CLAUDE.md` — are developer-owned and gitignored; the CLI does not deliver, overwrite, or fan them out — the whole-spine fanout is retired.)
|
|
4
4
|
|
|
5
|
-
This package is published from [mutmutco/MMI-Hub](https://github.com/mutmutco/MMI-Hub) and its version matches the MMI Hub
|
|
5
|
+
This package is published from [mutmutco/MMI-Hub](https://github.com/mutmutco/MMI-Hub) and its version matches the MMI Hub plugin distribution version — the release train bumps the CLI and the Claude, Codex, and Kimi plugin manifests in lockstep.
|
|
6
6
|
|
|
7
7
|
The CLI carries the org **Hub endpoint** intrinsically (override with the `MMI_HUB_URL` env var), so a product repo needs **no committed control-plane config** to reach the Hub — board coords, deploy coordinates, OAuth, and the secrets layout are all discovered from the Hub registry at runtime.
|
|
8
8
|
|
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
|
|
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
|
|
13082
|
-
return `${
|
|
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
|
|
13091
|
-
return `${
|
|
13090
|
+
function pluginCacheStagingRootForConfig(configRoot) {
|
|
13091
|
+
return `${configRoot}/plugins/cache`;
|
|
13092
13092
|
}
|
|
13093
|
-
function
|
|
13094
|
-
return `${
|
|
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
|
|
13167
|
-
const
|
|
13166
|
+
const configRoot = opts.configRoot ?? `${home}/.claude`;
|
|
13167
|
+
const cacheRoot = pluginCacheRootForConfig(configRoot);
|
|
13168
|
+
const stagingRoot = pluginCacheStagingRootForConfig(configRoot);
|
|
13168
13169
|
let stagingEntries;
|
|
13169
|
-
|
|
13170
|
-
|
|
13171
|
-
|
|
13172
|
-
|
|
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
|
-
|
|
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
|
|
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,11 +21804,15 @@ 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("
|
|
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
|
}
|
|
21813
|
+
if (env.MMI_AGENT_SURFACE === "kimi" || has("KIMI_PLUGIN_ROOT") || has("KIMI_CODE_HOME")) {
|
|
21814
|
+
return "kimi";
|
|
21815
|
+
}
|
|
21805
21816
|
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")) {
|
|
21806
21817
|
return "cursor";
|
|
21807
21818
|
}
|
|
@@ -21819,6 +21830,8 @@ function surfaceToken(surface) {
|
|
|
21819
21830
|
return "claude";
|
|
21820
21831
|
case "codex":
|
|
21821
21832
|
return "codex";
|
|
21833
|
+
case "kimi":
|
|
21834
|
+
return "kimi";
|
|
21822
21835
|
case "cursor":
|
|
21823
21836
|
return "cursor";
|
|
21824
21837
|
case "opencode":
|
|
@@ -21834,6 +21847,8 @@ function reloadAction(surface) {
|
|
|
21834
21847
|
return "restart VS Code";
|
|
21835
21848
|
case "codex":
|
|
21836
21849
|
return "restart Codex";
|
|
21850
|
+
case "kimi":
|
|
21851
|
+
return "run /reload (or start a new session) in Kimi Code";
|
|
21837
21852
|
case "opencode":
|
|
21838
21853
|
return "restart OpenCode";
|
|
21839
21854
|
case "cursor":
|
|
@@ -21845,6 +21860,7 @@ function reloadAction(surface) {
|
|
|
21845
21860
|
}
|
|
21846
21861
|
}
|
|
21847
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";
|
|
21848
21864
|
var PLUGIN_SURFACE_HEAL = {
|
|
21849
21865
|
claude: {
|
|
21850
21866
|
delivery: "plugin-cli",
|
|
@@ -21858,13 +21874,28 @@ var PLUGIN_SURFACE_HEAL = {
|
|
|
21858
21874
|
],
|
|
21859
21875
|
fix: (surface) => `${CLAUDE_RECOVERY} # then ${reloadAction(surface)} to reload MMI commands`,
|
|
21860
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]
|
|
21861
21889
|
}
|
|
21862
21890
|
};
|
|
21863
21891
|
function nonClaudeSurfaceHealMessage(surface) {
|
|
21864
21892
|
if (surface === "codex") {
|
|
21865
21893
|
return "Codex ships an MMI plugin (#3563). Install or repair it with:\n codex plugin marketplace add mutmutco/MMI-Hub && codex plugin add mmi@mutmutco\n Then run /hooks and TRUST the MMI hooks \u2014 bundled hooks are skipped until reviewed.\n Update the CLI too: npm i -g @mutmutco/cli";
|
|
21866
21894
|
}
|
|
21867
|
-
|
|
21895
|
+
if (surface === "kimi") {
|
|
21896
|
+
return "Kimi Code CLI ships an MMI plugin (kimi-k3). Install or repair it from the Kimi TUI with:\n /plugins install https://github.com/mutmutco/MMI-Hub\n Then run /reload (plugin hooks start only after a reload), and TRUST the install when prompted.\n Update the CLI too: npm i -g @mutmutco/cli";
|
|
21897
|
+
}
|
|
21898
|
+
return "No Hub-shipped MMI plugin on this host \u2014 the Hub ships plugins for Claude, Codex, and Kimi only.\n Update the CLI instead: npm i -g @mutmutco/cli (org rules ride an AGENTS.md authored outside the Hub)";
|
|
21868
21899
|
}
|
|
21869
21900
|
function healStepAborts(step, ok) {
|
|
21870
21901
|
return !ok && step.gated;
|
|
@@ -21903,28 +21934,97 @@ var NPM_VIEW_TIMEOUT_MS = 15e3;
|
|
|
21903
21934
|
function runHostBin(bin, args, opts) {
|
|
21904
21935
|
return isWin ? execFileP2("cmd.exe", ["/c", bin, ...args], opts) : execFileP2(bin, args, opts);
|
|
21905
21936
|
}
|
|
21906
|
-
|
|
21907
|
-
|
|
21908
|
-
|
|
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");
|
|
21941
|
+
}
|
|
21942
|
+
var installedPluginsPath = (surface = detectSurface(process.env)) => {
|
|
21943
|
+
return (0, import_node_path24.join)(surfaceConfigRoot(surface), "plugins", "installed_plugins.json");
|
|
21909
21944
|
};
|
|
21910
21945
|
function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
21911
21946
|
try {
|
|
21912
|
-
return JSON.parse((0, import_node_fs25.readFileSync)(
|
|
21947
|
+
return JSON.parse((0, import_node_fs25.readFileSync)(installedPluginsPath(surface), "utf8"));
|
|
21913
21948
|
} catch {
|
|
21914
21949
|
return null;
|
|
21915
21950
|
}
|
|
21916
21951
|
}
|
|
21917
|
-
function marketplaceCloneCandidates(surface, home) {
|
|
21952
|
+
function marketplaceCloneCandidates(surface, home, env = process.env) {
|
|
21918
21953
|
if (surface === "codex") {
|
|
21954
|
+
const root = surfaceConfigRoot(surface, env, home);
|
|
21919
21955
|
return [
|
|
21920
|
-
(0, import_node_path24.join)(
|
|
21921
|
-
(0, import_node_path24.join)(
|
|
21956
|
+
(0, import_node_path24.join)(root, ".tmp", "marketplaces", CODEX_MARKETPLACE),
|
|
21957
|
+
(0, import_node_path24.join)(root, "plugins", "marketplaces", CODEX_MARKETPLACE)
|
|
21922
21958
|
];
|
|
21923
21959
|
}
|
|
21960
|
+
if (surface === "kimi") return [];
|
|
21924
21961
|
return [(0, import_node_path24.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
|
|
21925
21962
|
}
|
|
21926
|
-
function marketplaceClonePresent(surface, home, exists = import_node_fs25.existsSync) {
|
|
21927
|
-
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
|
+
};
|
|
21928
22028
|
}
|
|
21929
22029
|
async function fetchNpmReleasedVersion() {
|
|
21930
22030
|
try {
|
|
@@ -21945,16 +22045,20 @@ async function npmSelfUpdateCli(target) {
|
|
|
21945
22045
|
}
|
|
21946
22046
|
}
|
|
21947
22047
|
function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRepo = false) {
|
|
21948
|
-
const
|
|
22048
|
+
const root = surfaceConfigRoot(surface);
|
|
21949
22049
|
const installed = readInstalledPlugins(surface);
|
|
22050
|
+
const codexStatus = surface === "codex" ? codexPluginStatus() : void 0;
|
|
21950
22051
|
return {
|
|
21951
22052
|
isOrgRepo,
|
|
21952
22053
|
installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
|
|
21953
22054
|
marketplaceClonePresent: marketplaceClonePresent(surface, (0, import_node_os6.homedir)()),
|
|
21954
|
-
|
|
22055
|
+
// Kimi keeps no plugin cache dir — installs are copied to plugins/managed/<id> and run from there.
|
|
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"))
|
|
21955
22059
|
};
|
|
21956
22060
|
}
|
|
21957
|
-
function
|
|
22061
|
+
function activePluginGuardState(isOrgRepo) {
|
|
21958
22062
|
return buildPluginGuardDecision(snapshotPluginGuardInput(detectSurface(process.env), isOrgRepo)).state;
|
|
21959
22063
|
}
|
|
21960
22064
|
async function runClaudePlugin(args) {
|
|
@@ -21965,6 +22069,14 @@ async function runClaudePlugin(args) {
|
|
|
21965
22069
|
return false;
|
|
21966
22070
|
}
|
|
21967
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
|
+
}
|
|
21968
22080
|
async function marketplaceAddRefSupported(bin) {
|
|
21969
22081
|
try {
|
|
21970
22082
|
const { stdout, stderr } = await runHostBin(bin, ["plugin", "marketplace", "add", "--help"], {
|
|
@@ -21991,18 +22103,23 @@ function pluginReadGrantNote(login = "<your-github-login>") {
|
|
|
21991
22103
|
gh api -X PUT repos/${PLUGIN_READ_REPO2}/collaborators/${login} -f permission=pull`;
|
|
21992
22104
|
}
|
|
21993
22105
|
async function applyPluginHeal(surface, log, opts) {
|
|
21994
|
-
|
|
21995
|
-
|
|
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;
|
|
21996
22111
|
if (!tableSteps) return false;
|
|
21997
|
-
const
|
|
22112
|
+
const bin = token;
|
|
22113
|
+
const refSupported = await marketplaceAddRefSupported(bin);
|
|
21998
22114
|
const { steps } = adaptHealStepsForRefSupport(tableSteps, refSupported);
|
|
21999
|
-
log(healBannerLine(
|
|
22115
|
+
log(healBannerLine(bin, token, refSupported));
|
|
22000
22116
|
const pinsPath = (0, import_node_path24.join)((0, import_node_os6.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE);
|
|
22001
|
-
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();
|
|
22002
22118
|
for (const step of steps) {
|
|
22003
|
-
|
|
22119
|
+
const ok = token === "claude" ? await runClaudePlugin([...step.args]) : await runCodexPlugin([...step.args]);
|
|
22120
|
+
if (healStepAborts(step, ok)) return false;
|
|
22004
22121
|
}
|
|
22005
|
-
const restored = restoreMarketplacePinsOnDisk(pinsPath, pins);
|
|
22122
|
+
const restored = token === "claude" ? restoreMarketplacePinsOnDisk(pinsPath, pins) : void 0;
|
|
22006
22123
|
if (restored) log(` ${restored}`);
|
|
22007
22124
|
return true;
|
|
22008
22125
|
}
|
|
@@ -22018,6 +22135,23 @@ async function healClaudePluginForDoctor(surface = detectSurface(process.env)) {
|
|
|
22018
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]})` : ""}`
|
|
22019
22136
|
};
|
|
22020
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
|
+
}
|
|
22021
22155
|
function readKnownMarketplacesFile(path2) {
|
|
22022
22156
|
try {
|
|
22023
22157
|
return (0, import_node_fs25.existsSync)(path2) ? (0, import_node_fs25.readFileSync)(path2, "utf8") : void 0;
|
|
@@ -22043,9 +22177,10 @@ function restoreMarketplacePinsOnDisk(path2, pins) {
|
|
|
22043
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)`;
|
|
22044
22178
|
}
|
|
22045
22179
|
async function runGuard(readOrigin) {
|
|
22180
|
+
const surface = detectSurface(process.env);
|
|
22046
22181
|
try {
|
|
22047
|
-
const
|
|
22048
|
-
if (
|
|
22182
|
+
const token = surfaceToken(surface);
|
|
22183
|
+
if (token !== "claude" && token !== "codex") {
|
|
22049
22184
|
process.exitCode = 0;
|
|
22050
22185
|
return;
|
|
22051
22186
|
}
|
|
@@ -22054,24 +22189,39 @@ async function runGuard(readOrigin) {
|
|
|
22054
22189
|
const { state } = buildPluginGuardDecision(input);
|
|
22055
22190
|
const { line, exitCode } = buildPluginGuardLine(state);
|
|
22056
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
|
+
}
|
|
22057
22198
|
process.exitCode = exitCode;
|
|
22058
22199
|
} catch {
|
|
22059
|
-
|
|
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
|
+
}
|
|
22060
22206
|
}
|
|
22061
22207
|
}
|
|
22062
22208
|
async function runPluginHeal(surface = detectSurface(process.env)) {
|
|
22063
|
-
|
|
22064
|
-
|
|
22209
|
+
const token = surfaceToken(surface);
|
|
22210
|
+
if (token !== "claude" && token !== "codex") {
|
|
22211
|
+
console.log(nonClaudeSurfaceHealMessage(token ?? void 0));
|
|
22065
22212
|
return;
|
|
22066
22213
|
}
|
|
22067
|
-
const descriptor = PLUGIN_SURFACE_HEAL
|
|
22068
|
-
const
|
|
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;
|
|
22069
22217
|
if (healed) {
|
|
22070
|
-
|
|
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}`);
|
|
22071
22220
|
} else {
|
|
22072
|
-
|
|
22221
|
+
process.exitCode = 1;
|
|
22222
|
+
const refSupported = await marketplaceAddRefSupported(token);
|
|
22073
22223
|
const recovery = refSupported ? descriptor.recovery : recoveryWithoutRef(descriptor.recovery);
|
|
22074
|
-
const note = refAbsenceNote(
|
|
22224
|
+
const note = refAbsenceNote(token, refSupported);
|
|
22075
22225
|
console.log(` \u2717 Auto-heal failed or was skipped. Run manually:
|
|
22076
22226
|
${recovery}${note}${pluginReadGrantNote()}`);
|
|
22077
22227
|
}
|
|
@@ -22633,7 +22783,7 @@ async function remoteBranchExists2(branch, options = {}) {
|
|
|
22633
22783
|
}
|
|
22634
22784
|
var COMPOSE_TIMEOUT_MS = 12e4;
|
|
22635
22785
|
function spawnDeferredGcSweep() {
|
|
22636
|
-
spawnDetachedSelf(["worktree", "gc", "sweep-deferred", "--quiet"], { spawn:
|
|
22786
|
+
spawnDetachedSelf(["worktree", "gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process13.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
|
|
22637
22787
|
}
|
|
22638
22788
|
async function createDeferredWorktreeStore() {
|
|
22639
22789
|
try {
|
|
@@ -23590,7 +23740,7 @@ ${spec.body ?? ""}`;
|
|
|
23590
23740
|
const hash = (0, import_node_crypto5.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
|
|
23591
23741
|
return `${batchKey}:${hash}`;
|
|
23592
23742
|
}
|
|
23593
|
-
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"]);
|
|
23594
23744
|
function validateBatchSpecs(specs) {
|
|
23595
23745
|
const errors = [];
|
|
23596
23746
|
const validated = [];
|
|
@@ -23607,6 +23757,11 @@ function validateBatchSpecs(specs) {
|
|
|
23607
23757
|
errors.push({ row, error: `unknown key(s): ${unknown.join(", ")} \u2014 expected only: ${[...BATCH_SPEC_KEYS].join(", ")}` });
|
|
23608
23758
|
continue;
|
|
23609
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
|
+
}
|
|
23610
23765
|
if (spec.repo !== void 0 && !/^[\w.-]+\/[\w.-]+$/.test(spec.repo)) {
|
|
23611
23766
|
errors.push({ row, error: `bad repo "${spec.repo}" \u2014 expected owner/repo` });
|
|
23612
23767
|
continue;
|
|
@@ -25181,6 +25336,10 @@ function pluginHealTrigger(probe) {
|
|
|
25181
25336
|
}
|
|
25182
25337
|
function checkClaudePlugin(probe) {
|
|
25183
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";
|
|
25184
25343
|
const evidence = [
|
|
25185
25344
|
`installed: ${installed ?? "(none)"}`,
|
|
25186
25345
|
// #3485 item 7: when the value was reused from the banner's once-a-day cache, say so and say how old.
|
|
@@ -25190,38 +25349,57 @@ function checkClaudePlugin(probe) {
|
|
|
25190
25349
|
const trigger = pluginHealTrigger(probe);
|
|
25191
25350
|
if (trigger === "unresolved") {
|
|
25192
25351
|
return {
|
|
25193
|
-
id
|
|
25352
|
+
id,
|
|
25194
25353
|
ok: false,
|
|
25195
|
-
label
|
|
25354
|
+
label,
|
|
25196
25355
|
detail: guardState === "no-install" ? "not installed" : "unresolved (marketplace/cache missing)",
|
|
25197
|
-
fix:
|
|
25356
|
+
fix: `run \`mmi-cli doctor --apply\` (or \`mmi-cli plugin heal\`) to reinstall the MMI marketplace + plugin, then ${restart}`,
|
|
25198
25357
|
verbose: evidence
|
|
25199
25358
|
};
|
|
25200
25359
|
}
|
|
25201
25360
|
if (trigger === "behind") {
|
|
25202
25361
|
return {
|
|
25203
|
-
id
|
|
25362
|
+
id,
|
|
25204
25363
|
ok: false,
|
|
25205
|
-
label
|
|
25364
|
+
label,
|
|
25206
25365
|
detail: `${installed} \u2192 ${released}`,
|
|
25207
25366
|
// Name the CLI verb that actually fixes it, like every other red row — `/plugin` is the manual
|
|
25208
25367
|
// fallback, not the first resort (#3282).
|
|
25209
|
-
fix:
|
|
25368
|
+
fix: `run \`mmi-cli doctor --apply\` (or \`mmi-cli plugin heal\`) to reinstall it, then ${restart}`,
|
|
25210
25369
|
verbose: evidence
|
|
25211
25370
|
};
|
|
25212
25371
|
}
|
|
25213
25372
|
if (!released) {
|
|
25214
25373
|
return {
|
|
25215
|
-
id
|
|
25374
|
+
id,
|
|
25216
25375
|
ok: false,
|
|
25217
25376
|
reportOnly: true,
|
|
25218
|
-
label
|
|
25377
|
+
label,
|
|
25219
25378
|
detail: `${installed ?? "installed"} \u2014 freshness UNKNOWN, the published version could not be read`,
|
|
25220
25379
|
fix: "check it directly: `npm view @mutmutco/cli version`, then `mmi-cli plugin heal` if behind",
|
|
25221
25380
|
verbose: evidence
|
|
25222
25381
|
};
|
|
25223
25382
|
}
|
|
25224
|
-
return { id
|
|
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
|
+
};
|
|
25225
25403
|
}
|
|
25226
25404
|
function checkCliVersion(input, releasedNote) {
|
|
25227
25405
|
const report = buildVersionLagReport(input);
|
|
@@ -25459,6 +25637,8 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
25459
25637
|
]);
|
|
25460
25638
|
const ghInstalled2 = login ? true : await deps.ghInstalled();
|
|
25461
25639
|
const installed = deps.installedPluginVersion();
|
|
25640
|
+
const pluginSurface = deps.pluginSurface?.() ?? "claude-cli";
|
|
25641
|
+
const codexSurface = pluginSurface === "codex";
|
|
25462
25642
|
const checks = [];
|
|
25463
25643
|
let restartPending = false;
|
|
25464
25644
|
checks.push(checkGithubAuth({ login, ghInstalled: ghInstalled2, reach }));
|
|
@@ -25485,10 +25665,18 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
25485
25665
|
}
|
|
25486
25666
|
}
|
|
25487
25667
|
const releasedNote = deps.releasedVersionNote?.();
|
|
25488
|
-
const pluginProbe = {
|
|
25668
|
+
const pluginProbe = {
|
|
25669
|
+
installed,
|
|
25670
|
+
released,
|
|
25671
|
+
guardState: deps.pluginGuardState(isOrgRepo),
|
|
25672
|
+
releasedNote,
|
|
25673
|
+
surface: pluginSurface
|
|
25674
|
+
};
|
|
25489
25675
|
const healTrigger = pluginHealTrigger(pluginProbe);
|
|
25676
|
+
let pluginHealed = false;
|
|
25490
25677
|
if (applyEnv && deps.healPlugin && healTrigger) {
|
|
25491
25678
|
const heal = await deps.healPlugin();
|
|
25679
|
+
pluginHealed = heal.ok;
|
|
25492
25680
|
const measured = healTrigger === "behind" ? `${installed} \u2192 ${released}` : "unresolved install";
|
|
25493
25681
|
const healEvidence = [
|
|
25494
25682
|
`installed: ${installed ?? "(none)"}`,
|
|
@@ -25497,22 +25685,22 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
25497
25685
|
`heal: ${heal.detail}`
|
|
25498
25686
|
];
|
|
25499
25687
|
checks.push(heal.ok ? {
|
|
25500
|
-
id: "claude-plugin",
|
|
25688
|
+
id: codexSurface ? "codex-plugin" : "claude-plugin",
|
|
25501
25689
|
ok: true,
|
|
25502
|
-
label: "Claude plugin",
|
|
25503
|
-
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" : ""}`,
|
|
25504
25692
|
verbose: healEvidence
|
|
25505
25693
|
} : {
|
|
25506
|
-
id: "claude-plugin",
|
|
25694
|
+
id: codexSurface ? "codex-plugin" : "claude-plugin",
|
|
25507
25695
|
ok: false,
|
|
25508
|
-
label: "Claude plugin",
|
|
25696
|
+
label: codexSurface ? "Codex plugin" : "Claude plugin",
|
|
25509
25697
|
detail: measured,
|
|
25510
25698
|
// #3489: a heal skipped because another doctor holds the env-heal lock is a real ✗ — this run did
|
|
25511
25699
|
// not fix what it found — but it is not this invocation's to clear. The other process is doing it;
|
|
25512
25700
|
// waiting is the correct response and a re-run finds it healed. A heal that RAN and failed is a
|
|
25513
25701
|
// genuine gap and still gates.
|
|
25514
25702
|
...heal.skipped ? { reportOnly: true } : {},
|
|
25515
|
-
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"}`,
|
|
25516
25704
|
verbose: healEvidence
|
|
25517
25705
|
});
|
|
25518
25706
|
if (!heal.skipped) restartPending = true;
|
|
@@ -25523,6 +25711,10 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
25523
25711
|
if (!plugin.ok) restartPending = true;
|
|
25524
25712
|
}
|
|
25525
25713
|
}
|
|
25714
|
+
if (codexSurface && (pluginProbe.guardState === "healthy" || pluginHealed)) {
|
|
25715
|
+
const trust = checkCodexHookTrust(deps.pluginTrustState?.());
|
|
25716
|
+
if (trust) checks.push(trust);
|
|
25717
|
+
}
|
|
25526
25718
|
const cliInput = { currentVersion: deps.currentCliVersion(), releasedVersion: released };
|
|
25527
25719
|
const cliReport = buildVersionLagReport(cliInput);
|
|
25528
25720
|
if (applyEnv && deps.updateCli && versionAutoUpdateAction(cliReport) === "npm") {
|
|
@@ -25656,7 +25848,12 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
25656
25848
|
const exitCode = doctorReportExitCode(checks);
|
|
25657
25849
|
if (opts.json) {
|
|
25658
25850
|
const payload = opts.verbose ? checks : checks.map(({ verbose: _evidence, ...check }) => check);
|
|
25659
|
-
io.log(JSON.stringify({
|
|
25851
|
+
io.log(JSON.stringify({
|
|
25852
|
+
checks: payload,
|
|
25853
|
+
restartPending,
|
|
25854
|
+
...restartPending ? { restartAction: codexSurface ? "restart Codex" : "restart Claude" } : {},
|
|
25855
|
+
exitCode
|
|
25856
|
+
}, null, 2));
|
|
25660
25857
|
return exitCode;
|
|
25661
25858
|
}
|
|
25662
25859
|
if (opts.banner) {
|
|
@@ -25665,10 +25862,12 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
25665
25862
|
io.log(renderReport([c], { restartPending: false }));
|
|
25666
25863
|
if (opts.verbose) for (const evidence of c.verbose ?? []) io.log(`${VERBOSE_INDENT}${evidence}`);
|
|
25667
25864
|
}
|
|
25668
|
-
if (restartPending) io.log(RESTART_LINE);
|
|
25865
|
+
if (restartPending) io.log(codexSurface ? "\u21BB Restart Codex to finish." : RESTART_LINE);
|
|
25669
25866
|
return 0;
|
|
25670
25867
|
}
|
|
25671
|
-
|
|
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);
|
|
25672
25871
|
return exitCode;
|
|
25673
25872
|
}
|
|
25674
25873
|
|
|
@@ -25754,9 +25953,9 @@ function ghAccountCaveat(announcedLogin, accounts) {
|
|
|
25754
25953
|
var import_node_fs32 = require("node:fs");
|
|
25755
25954
|
var import_node_os10 = require("node:os");
|
|
25756
25955
|
var import_node_path30 = require("node:path");
|
|
25757
|
-
var
|
|
25956
|
+
var import_node_child_process14 = require("node:child_process");
|
|
25758
25957
|
var import_node_util8 = require("node:util");
|
|
25759
|
-
var execFileP6 = (0, import_node_util8.promisify)(
|
|
25958
|
+
var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process14.execFile);
|
|
25760
25959
|
var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
25761
25960
|
function installedClaudePluginVersion() {
|
|
25762
25961
|
try {
|
|
@@ -25770,9 +25969,28 @@ function installedClaudePluginVersion() {
|
|
|
25770
25969
|
return void 0;
|
|
25771
25970
|
}
|
|
25772
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
|
+
}
|
|
25773
25991
|
function worktreeRootSync() {
|
|
25774
25992
|
try {
|
|
25775
|
-
const out = (0,
|
|
25993
|
+
const out = (0, import_node_child_process14.execFileSync)("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
25776
25994
|
let root = out.endsWith("\n") ? out.slice(0, -1) : out;
|
|
25777
25995
|
if (process.platform === "win32" && root.endsWith("\r")) root = root.slice(0, -1);
|
|
25778
25996
|
return root || null;
|
|
@@ -25917,8 +26135,10 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
25917
26135
|
githubRepoReach: githubRepoReachProbe,
|
|
25918
26136
|
awsCallerArn,
|
|
25919
26137
|
isOrgRepo: () => isOrgRepoRoot(),
|
|
25920
|
-
installedPluginVersion:
|
|
25921
|
-
pluginGuardState:
|
|
26138
|
+
installedPluginVersion: installedActivePluginVersion,
|
|
26139
|
+
pluginGuardState: activePluginGuardState,
|
|
26140
|
+
pluginSurface: () => detectSurface(process.env),
|
|
26141
|
+
pluginTrustState: () => codexHookTrustState(),
|
|
25922
26142
|
releasedVersion: throttled ? throttled.read : fetchNpmReleasedVersion,
|
|
25923
26143
|
releasedVersionNote: throttled ? throttled.note : void 0,
|
|
25924
26144
|
// #3272: the --apply self-heal for a stale running CLI — npm global, shadows any plugin shim (#2879).
|
|
@@ -25929,7 +26149,10 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
25929
26149
|
updateCli: (target) => withEnvHealLock("npm global CLI self-update", () => npmSelfUpdateCli(target)),
|
|
25930
26150
|
// #3282: the --apply self-heal for a stale/unresolved Claude plugin — the same marketplace reinstall
|
|
25931
26151
|
// `mmi-cli plugin heal` drives. Takes effect on the next Claude reload, so the row asks for a restart.
|
|
25932
|
-
healPlugin: () =>
|
|
26152
|
+
healPlugin: () => {
|
|
26153
|
+
const surface = detectSurface(process.env);
|
|
26154
|
+
return withEnvHealLock(`${surface === "codex" ? "Codex" : "Claude"} plugin reinstall`, () => healActivePluginForDoctor(surface));
|
|
26155
|
+
},
|
|
25933
26156
|
currentCliVersion: resolveClientVersion,
|
|
25934
26157
|
readGitignore,
|
|
25935
26158
|
writeGitignore,
|
|
@@ -25964,10 +26187,14 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
25964
26187
|
return { personal, app };
|
|
25965
26188
|
},
|
|
25966
26189
|
pluginCache: () => {
|
|
26190
|
+
const surface = detectSurface(process.env);
|
|
26191
|
+
const configRoot = surfaceConfigRoot(surface);
|
|
26192
|
+
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
25967
26193
|
const plan = buildPluginCachePlan(
|
|
25968
26194
|
(0, import_node_os11.homedir)(),
|
|
25969
|
-
|
|
25970
|
-
pluginCacheFsDeps(
|
|
26195
|
+
running,
|
|
26196
|
+
pluginCacheFsDeps(configRoot, () => 0),
|
|
26197
|
+
{ configRoot, includeStaging: surface !== "codex" }
|
|
25971
26198
|
);
|
|
25972
26199
|
return {
|
|
25973
26200
|
stale: plan.prune,
|
|
@@ -26016,6 +26243,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
26016
26243
|
// which branch it would pick one up from. Two local file reads, no network, fail-soft to no rows.
|
|
26017
26244
|
marketplaceRows: () => {
|
|
26018
26245
|
try {
|
|
26246
|
+
if (detectSurface(process.env) === "codex") return [];
|
|
26019
26247
|
const home = (0, import_node_os11.homedir)();
|
|
26020
26248
|
return marketplaceRows(
|
|
26021
26249
|
MMI_MARKETPLACE_NAME,
|
|
@@ -26407,7 +26635,7 @@ function runWorktreeInstall(command, cwd, quiet) {
|
|
|
26407
26635
|
const file = isWin2 ? "cmd.exe" : bin;
|
|
26408
26636
|
const spawnArgs = isWin2 ? ["/c", bin, ...args] : args;
|
|
26409
26637
|
return new Promise((resolve5, reject) => {
|
|
26410
|
-
const child2 = (0,
|
|
26638
|
+
const child2 = (0, import_node_child_process15.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
|
|
26411
26639
|
const timer = setTimeout(() => {
|
|
26412
26640
|
try {
|
|
26413
26641
|
child2.kill();
|
|
@@ -26678,7 +26906,7 @@ function scheduleRelatedDiscovery(o) {
|
|
|
26678
26906
|
try {
|
|
26679
26907
|
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
|
|
26680
26908
|
if (o.repo) args.push("--repo", o.repo);
|
|
26681
|
-
spawnDetachedSelf(args, { spawn:
|
|
26909
|
+
spawnDetachedSelf(args, { spawn: import_node_child_process15.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
|
|
26682
26910
|
} catch {
|
|
26683
26911
|
}
|
|
26684
26912
|
}
|
|
@@ -28373,8 +28601,8 @@ function directoryBytes(path2) {
|
|
|
28373
28601
|
function listDirEntries(dir) {
|
|
28374
28602
|
return (0, import_node_fs33.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
28375
28603
|
}
|
|
28376
|
-
function readInstalledPluginRefs(
|
|
28377
|
-
const p =
|
|
28604
|
+
function readInstalledPluginRefs(configRoot) {
|
|
28605
|
+
const p = installedPluginsPathForConfig(configRoot);
|
|
28378
28606
|
if (!(0, import_node_fs33.existsSync)(p)) return [];
|
|
28379
28607
|
try {
|
|
28380
28608
|
return installedPluginPaths((0, import_node_fs33.readFileSync)(p, "utf8"));
|
|
@@ -28382,7 +28610,7 @@ function readInstalledPluginRefs(home) {
|
|
|
28382
28610
|
return null;
|
|
28383
28611
|
}
|
|
28384
28612
|
}
|
|
28385
|
-
function pluginCacheFsDeps(
|
|
28613
|
+
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
28386
28614
|
return {
|
|
28387
28615
|
exists: (p) => (0, import_node_fs33.existsSync)(p),
|
|
28388
28616
|
listVersionDirs: (root) => (0, import_node_fs33.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
@@ -28394,14 +28622,14 @@ function pluginCacheFsDeps(home, dirBytes) {
|
|
|
28394
28622
|
return { name: d.name, mtimeMs: Date.now() };
|
|
28395
28623
|
}
|
|
28396
28624
|
}),
|
|
28397
|
-
readInstalledPluginPaths: () => readInstalledPluginRefs(
|
|
28625
|
+
readInstalledPluginPaths: () => readInstalledPluginRefs(configRoot),
|
|
28398
28626
|
now: () => Date.now()
|
|
28399
28627
|
};
|
|
28400
28628
|
}
|
|
28401
|
-
function stagingApplyFsGuard(
|
|
28402
|
-
const stagingRoot =
|
|
28629
|
+
function stagingApplyFsGuard(configRoot) {
|
|
28630
|
+
const stagingRoot = pluginCacheStagingRootForConfig(configRoot);
|
|
28403
28631
|
return {
|
|
28404
|
-
referencedPaths: () => readInstalledPluginRefs(
|
|
28632
|
+
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
28405
28633
|
mtimeMs: (name) => {
|
|
28406
28634
|
const p = (0, import_node_path31.join)(stagingRoot, name);
|
|
28407
28635
|
if (!(0, import_node_fs33.existsSync)(p)) return null;
|
|
@@ -28415,14 +28643,24 @@ function stagingApplyFsGuard(home) {
|
|
|
28415
28643
|
};
|
|
28416
28644
|
}
|
|
28417
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
|
+
}
|
|
28418
28656
|
const plan = buildPluginCachePlan(
|
|
28419
28657
|
(0, import_node_os11.homedir)(),
|
|
28420
|
-
|
|
28421
|
-
pluginCacheFsDeps(
|
|
28422
|
-
{ withBytes: true }
|
|
28658
|
+
running,
|
|
28659
|
+
pluginCacheFsDeps(configRoot, directoryBytes),
|
|
28660
|
+
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
28423
28661
|
);
|
|
28424
28662
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
28425
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs33.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(
|
|
28663
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs33.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
28426
28664
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
28427
28665
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
28428
28666
|
else console.log(renderPluginCachePlan(plan, result));
|
|
@@ -28485,7 +28723,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
28485
28723
|
for (const line of scratchGcLines(process.cwd())) bannerIo.log(line);
|
|
28486
28724
|
const worktreeBanner = worktreeAutoProvisionBanner(process.cwd());
|
|
28487
28725
|
if (worktreeBanner) {
|
|
28488
|
-
spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn:
|
|
28726
|
+
spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process15.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
|
|
28489
28727
|
bannerIo.log(worktreeBanner);
|
|
28490
28728
|
}
|
|
28491
28729
|
if (isLinkedWorktree(process.cwd())) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "MMI Future CLI — the org dev toolbox (board, registry, keyless secrets, release train, bootstrap, doctor) and the cross-IDE engine the plugin's
|
|
3
|
+
"version": "3.74.0",
|
|
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",
|
|
7
7
|
"author": {
|