@mutmutco/cli 2.9.0 → 2.10.1
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/dist/index.cjs +80 -21
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5140,8 +5140,9 @@ function buildAwsCrossAccountCheck(input) {
|
|
|
5140
5140
|
}
|
|
5141
5141
|
var MMI_PLUGIN_ID = "mmi@mmi";
|
|
5142
5142
|
var PLUGIN_LABEL = "plugin install record (mmi@mmi for this project)";
|
|
5143
|
-
function pluginInstallManualFix(projectPath) {
|
|
5144
|
-
|
|
5143
|
+
function pluginInstallManualFix(projectPath, surface = "claude-cli") {
|
|
5144
|
+
const register = surface === "codex" ? `\`codex plugin add ${MMI_PLUGIN_ID}\`` : surface === "shell" ? `enable the MMI plugin in your client` : surface === "claude-vscode" ? `\`claude plugin enable ${MMI_PLUGIN_ID}\`` : `\`/plugin install ${MMI_PLUGIN_ID}\``;
|
|
5145
|
+
return `run ${register} then ${reloadAction(surface)} to register the install record for ${projectPath}`;
|
|
5145
5146
|
}
|
|
5146
5147
|
function isMmiPluginEnabled(settings) {
|
|
5147
5148
|
return Boolean(settings?.enabledPlugins?.[MMI_PLUGIN_ID]);
|
|
@@ -5161,7 +5162,7 @@ function buildPluginInstallRecordCheck(input) {
|
|
|
5161
5162
|
const base = {
|
|
5162
5163
|
ok: true,
|
|
5163
5164
|
label: PLUGIN_LABEL,
|
|
5164
|
-
fix: pluginInstallManualFix(input.projectPath),
|
|
5165
|
+
fix: pluginInstallManualFix(input.projectPath, input.surface),
|
|
5165
5166
|
pluginId
|
|
5166
5167
|
};
|
|
5167
5168
|
if (!input.isOrgRepo || !isMmiPluginEnabled(input.settings)) return base;
|
|
@@ -5177,7 +5178,7 @@ function buildPluginInstallRecordCheck(input) {
|
|
|
5177
5178
|
return {
|
|
5178
5179
|
ok: false,
|
|
5179
5180
|
label: PLUGIN_LABEL,
|
|
5180
|
-
fix: pluginInstallManualFix(input.projectPath),
|
|
5181
|
+
fix: pluginInstallManualFix(input.projectPath, input.surface),
|
|
5181
5182
|
pluginId,
|
|
5182
5183
|
recordToInsert
|
|
5183
5184
|
};
|
|
@@ -5196,8 +5197,7 @@ function bestRecord(records) {
|
|
|
5196
5197
|
}
|
|
5197
5198
|
function pluginConfigDriftFix(pluginId, surface = "claude-cli") {
|
|
5198
5199
|
const file = surface === "codex" ? "~/.codex/plugins/installed_plugins.json" : "~/.claude/plugins/installed_plugins.json";
|
|
5199
|
-
|
|
5200
|
-
return `\`${pluginId}\` has duplicate install rows or stale gitCommitSha in ${file} \u2014 run \`mmi-cli doctor\` interactively to collapse them to one user-scope row (a .bak backup is written first), then ${reload}`;
|
|
5200
|
+
return `\`${pluginId}\` has duplicate install rows or stale gitCommitSha in ${file} \u2014 run \`mmi-cli doctor\` interactively to collapse them to one user-scope row (a .bak backup is written first), then ${reloadAction(surface)}`;
|
|
5201
5201
|
}
|
|
5202
5202
|
function buildPluginConfigDriftCheck(input) {
|
|
5203
5203
|
const pluginId = input.pluginId ?? MMI_PLUGIN_ID;
|
|
@@ -5293,13 +5293,24 @@ function detectSurface(env) {
|
|
|
5293
5293
|
if (isClaude) return "claude-cli";
|
|
5294
5294
|
return "shell";
|
|
5295
5295
|
}
|
|
5296
|
+
function reloadAction(surface) {
|
|
5297
|
+
switch (surface) {
|
|
5298
|
+
case "claude-vscode":
|
|
5299
|
+
return "restart VS Code";
|
|
5300
|
+
case "codex":
|
|
5301
|
+
return "restart Codex";
|
|
5302
|
+
case "claude-cli":
|
|
5303
|
+
case "shell":
|
|
5304
|
+
default:
|
|
5305
|
+
return "restart Claude Code (or run /reload-plugins)";
|
|
5306
|
+
}
|
|
5307
|
+
}
|
|
5296
5308
|
function pluginRecoveryFix(surface) {
|
|
5297
5309
|
const claude = "claude plugin marketplace update mmi && claude plugin update mmi@mmi && claude plugin enable mmi@mmi";
|
|
5298
5310
|
switch (surface) {
|
|
5299
5311
|
case "claude-vscode":
|
|
5300
|
-
return `${claude} # then reopen the VS Code workspace to reload MMI commands`;
|
|
5301
5312
|
case "claude-cli":
|
|
5302
|
-
return `${claude} # then
|
|
5313
|
+
return `${claude} # then ${reloadAction(surface)} to reload MMI commands`;
|
|
5303
5314
|
case "codex":
|
|
5304
5315
|
return "codex plugin marketplace upgrade mmi && codex plugin add mmi@mmi # then restart Codex";
|
|
5305
5316
|
case "shell":
|
|
@@ -5566,9 +5577,10 @@ async function buildTrainApplyContext(deps) {
|
|
|
5566
5577
|
const [owner, name] = repo.split("/");
|
|
5567
5578
|
if (!owner || !name) throw new Error(`repo must be owner/name, got ${repo}`);
|
|
5568
5579
|
const login = requireValue(clean(await deps.run("gh", ["api", "user", "--jq", ".login"])), "GitHub login");
|
|
5569
|
-
const
|
|
5570
|
-
if (
|
|
5571
|
-
|
|
5580
|
+
const verdict = await deps.trainAuthority(repo);
|
|
5581
|
+
if (!verdict.ok) throw new Error(`${commandAuthorityLabel(owner)}: train authority could not be verified (${verdict.error})`);
|
|
5582
|
+
if (!verdict.train) {
|
|
5583
|
+
throw new Error(`${commandAuthorityLabel(owner)}: @${login} is ${verdict.role} \u2014 no train authority on ${repo}`);
|
|
5572
5584
|
}
|
|
5573
5585
|
return {
|
|
5574
5586
|
repo,
|
|
@@ -6391,6 +6403,25 @@ var PROJECTS_ENVELOPE_KEY = "projects";
|
|
|
6391
6403
|
|
|
6392
6404
|
// src/registry-client.ts
|
|
6393
6405
|
var DEFAULT_TIMEOUT_MS = 8e3;
|
|
6406
|
+
async function fetchTrainAuthority(repo, deps) {
|
|
6407
|
+
if (!deps.baseUrl) return { ok: false, error: "Hub API URL not configured" };
|
|
6408
|
+
const token = await deps.token();
|
|
6409
|
+
if (!token) return { ok: false, error: "no GitHub token (gh auth login)" };
|
|
6410
|
+
const doFetch = deps.fetch ?? fetch;
|
|
6411
|
+
try {
|
|
6412
|
+
const res = await doFetch(`${deps.baseUrl.replace(/\/$/, "")}/train-authority?repo=${encodeURIComponent(repo)}`, {
|
|
6413
|
+
method: "GET",
|
|
6414
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
6415
|
+
signal: AbortSignal.timeout(deps.timeoutMs ?? DEFAULT_TIMEOUT_MS)
|
|
6416
|
+
});
|
|
6417
|
+
if (!res.ok) return { ok: false, error: `train-authority HTTP ${res.status}` };
|
|
6418
|
+
const body = await res.json();
|
|
6419
|
+
if (typeof body?.train !== "boolean" || !body.role) return { ok: false, error: "malformed train-authority response" };
|
|
6420
|
+
return { ok: true, authority: body };
|
|
6421
|
+
} catch (e) {
|
|
6422
|
+
return { ok: false, error: e.message };
|
|
6423
|
+
}
|
|
6424
|
+
}
|
|
6394
6425
|
async function fetchProjectsList(deps) {
|
|
6395
6426
|
if (!deps.baseUrl) return null;
|
|
6396
6427
|
const token = await deps.token();
|
|
@@ -8845,7 +8876,11 @@ for (const commandName of ["rcand", "release", "hotfix"]) {
|
|
|
8845
8876
|
try {
|
|
8846
8877
|
const result = await runTrainApply(commandName, {
|
|
8847
8878
|
run: async (file, args) => (await execFileP3(file, args, { timeout: file === "gh" ? 3e4 : GIT_TIMEOUT_MS })).stdout,
|
|
8848
|
-
runSelf: async (args) => (await execFileP3(process.execPath, [process.argv[1], ...args], { timeout: 3e4 })).stdout
|
|
8879
|
+
runSelf: async (args) => (await execFileP3(process.execPath, [process.argv[1], ...args], { timeout: 3e4 })).stdout,
|
|
8880
|
+
trainAuthority: async (repo) => {
|
|
8881
|
+
const verdict = await fetchTrainAuthority(repo, registryClientDeps(await loadConfig()));
|
|
8882
|
+
return verdict.ok ? { ok: true, role: verdict.authority.role, train: verdict.authority.train } : verdict;
|
|
8883
|
+
}
|
|
8849
8884
|
});
|
|
8850
8885
|
const message = `mmi-cli ${commandName}: applied ${result.repo} ${result.stage} train at ${result.tag} [${result.deployModel}]; ${result.dispatch}`;
|
|
8851
8886
|
return printLine(o.json ? JSON.stringify(result, null, 2) : message);
|
|
@@ -9011,6 +9046,29 @@ LIVE apply to ${repo}:
|
|
|
9011
9046
|
}
|
|
9012
9047
|
});
|
|
9013
9048
|
var access = program2.command("access").description("org access audit (read-only)");
|
|
9049
|
+
access.command("role [repo]").description("D14 train authority for a repo (server-side Hub check): master | project-admin | developer").option("--json", "machine-readable output").action(async (repoArg) => {
|
|
9050
|
+
const o = { json: rawFlag("--json") };
|
|
9051
|
+
const repo = repoArg ?? await resolveRepo();
|
|
9052
|
+
if (!repo) return fail("access role: pass <owner/repo> or run inside a repo checkout");
|
|
9053
|
+
const cfg = await loadConfig();
|
|
9054
|
+
const verdict = await fetchTrainAuthority(repo, registryClientDeps(cfg));
|
|
9055
|
+
if (!verdict.ok) {
|
|
9056
|
+
if (o.json) {
|
|
9057
|
+
console.log(JSON.stringify({ repo, train: false, error: verdict.error }));
|
|
9058
|
+
process.exitCode = 1;
|
|
9059
|
+
return;
|
|
9060
|
+
}
|
|
9061
|
+
return fail(`access role: ${verdict.error}`);
|
|
9062
|
+
}
|
|
9063
|
+
const a = verdict.authority;
|
|
9064
|
+
if (o.json) {
|
|
9065
|
+
console.log(JSON.stringify(a));
|
|
9066
|
+
if (!a.train) process.exitCode = 1;
|
|
9067
|
+
return;
|
|
9068
|
+
}
|
|
9069
|
+
console.log(`${a.repo}: @${a.login} is ${a.role} \u2014 train ${a.train ? "AUTHORIZED" : "not authorized"}${a.hubTrainMasterOnly ? " (Hub train is master-only)" : ""}`);
|
|
9070
|
+
if (!a.train) process.exitCode = 1;
|
|
9071
|
+
});
|
|
9014
9072
|
access.command("audit").description("audit collaborator roles + train-branch push allowlists vs the locked state; read-only, emits gh remediation, never applies").option("--json", "machine-readable output").option("--repo <owner/repo>", "audit a single repo instead of the whole org").option("--class <class>", "repo class for --repo (deployable | content)", "deployable").action(async () => {
|
|
9015
9073
|
const o = { json: rawFlag("--json"), repo: rawValue("--repo", ""), class: rawValue("--class", "deployable") };
|
|
9016
9074
|
const deps = { gh: async (args) => execFileP3("gh", args, { timeout: 2e4 }) };
|
|
@@ -9184,6 +9242,7 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
9184
9242
|
}
|
|
9185
9243
|
checks.push({ ok: onPath, label: "mmi-cli on PATH", fix: "auto-provisioned at session start \u2014 reopen the session, or install the MMI plugin" });
|
|
9186
9244
|
const surface = detectSurface(process.env);
|
|
9245
|
+
const reloadHint = reloadAction(surface);
|
|
9187
9246
|
let versionReport = buildVersionLagReport({
|
|
9188
9247
|
currentVersion: resolveVersion(),
|
|
9189
9248
|
repoVersion: readRepoVersion(),
|
|
@@ -9210,12 +9269,13 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
9210
9269
|
settings: readClaudeSettings(),
|
|
9211
9270
|
installed,
|
|
9212
9271
|
projectPath: process.cwd(),
|
|
9213
|
-
mirrorFrom: existingMirrorRecord(installed)
|
|
9272
|
+
mirrorFrom: existingMirrorRecord(installed),
|
|
9273
|
+
surface
|
|
9214
9274
|
});
|
|
9215
|
-
if (!pluginCheck.ok && pluginCheck.recordToInsert && !opts.json
|
|
9275
|
+
if (!pluginCheck.ok && pluginCheck.recordToInsert && !opts.json) {
|
|
9216
9276
|
if (writeProjectInstallRecord(pluginCheck.recordToInsert)) {
|
|
9217
9277
|
pluginCheck = { ...pluginCheck, ok: true };
|
|
9218
|
-
io.err(
|
|
9278
|
+
io.err(` \u21BB repaired: registered mmi@mmi project install record \u2014 ${reloadHint} to load MMI commands`);
|
|
9219
9279
|
}
|
|
9220
9280
|
}
|
|
9221
9281
|
checks.push(pluginCheck);
|
|
@@ -9228,10 +9288,10 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
9228
9288
|
}
|
|
9229
9289
|
checks.push(gitignoreCheck);
|
|
9230
9290
|
let driftCheck = buildPluginConfigDriftCheck({ isOrgRepo: Boolean(cfg.sagaApiUrl), installed, surface });
|
|
9231
|
-
if (!driftCheck.ok && driftCheck.recordsToWrite && !opts.json
|
|
9291
|
+
if (!driftCheck.ok && driftCheck.recordsToWrite && !opts.json) {
|
|
9232
9292
|
if (backupAndWriteInstalledPlugins(driftCheck.recordsToWrite, driftCheck.pluginId)) {
|
|
9233
9293
|
driftCheck = { ...driftCheck, ok: true };
|
|
9234
|
-
io.err(
|
|
9294
|
+
io.err(` \u21BB repaired: collapsed mmi@mmi to one user-scope entry (backup at installed_plugins.json.bak) \u2014 ${reloadHint} to load MMI commands`);
|
|
9235
9295
|
}
|
|
9236
9296
|
}
|
|
9237
9297
|
checks.push(driftCheck);
|
|
@@ -9251,8 +9311,7 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
9251
9311
|
});
|
|
9252
9312
|
installedVersionCheck = healed;
|
|
9253
9313
|
if (healed.ok) {
|
|
9254
|
-
|
|
9255
|
-
io.err(` \u21BB updated MMI plugin \u2192 ${releasedVersion ?? "latest"} via claude plugin \u2014 ${reload} to load the new commands`);
|
|
9314
|
+
io.err(` \u21BB updated MMI plugin \u2192 ${releasedVersion ?? "latest"} via claude plugin \u2014 ${reloadAction(surface)} to load the new commands`);
|
|
9256
9315
|
}
|
|
9257
9316
|
}
|
|
9258
9317
|
}
|
|
@@ -9264,12 +9323,12 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
9264
9323
|
releasedVersion,
|
|
9265
9324
|
installedVersions: installedPluginVersions(installed)
|
|
9266
9325
|
});
|
|
9267
|
-
if (!cacheCleanupCheck.ok && cacheCleanupCheck.quarantinePlan && !opts.json
|
|
9326
|
+
if (!cacheCleanupCheck.ok && cacheCleanupCheck.quarantinePlan && !opts.json) {
|
|
9268
9327
|
const moved = quarantinePluginCacheDirs(cacheCleanupCheck.quarantinePlan);
|
|
9269
9328
|
if (moved > 0) {
|
|
9270
9329
|
const surfaces = [...new Set(cacheCleanupCheck.leftovers?.map((entry) => entry.surface) ?? [])].join("/");
|
|
9271
9330
|
const names = cacheCleanupCheck.leftovers?.map((entry) => entry.name).join(", ");
|
|
9272
|
-
io.err(` \u21BB quarantined ${moved} stale MMI plugin cache dir(s) for ${surfaces || "agent surfaces"}: ${names} \u2014
|
|
9331
|
+
io.err(` \u21BB quarantined ${moved} stale MMI plugin cache dir(s) for ${surfaces || "agent surfaces"}: ${names} \u2014 ${reloadHint} to load MMI commands`);
|
|
9273
9332
|
}
|
|
9274
9333
|
cacheCleanupCheck = buildMmiPluginCacheCleanupCheck({
|
|
9275
9334
|
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.1",
|
|
4
4
|
"description": "MMI Future CLI — delivers the org rules (whole-file), plus saga and KB access. The cross-IDE engine the plugin's SessionStart hook drives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|