@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.
Files changed (2) hide show
  1. package/dist/index.cjs +80 -21
  2. 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
- return `run \`/plugin install ${MMI_PLUGIN_ID}\` then \`/reload-plugins\` (VS Code extension: reopen the workspace) to register the project install record for ${projectPath}`;
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
- const reload = surface === "codex" ? "restart Codex" : surface === "claude-vscode" ? "reopen the VS Code workspace" : "restart Claude Code, or run /reload-plugins";
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 restart Claude Code, or run /reload-plugins`;
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 role = clean(await deps.run("gh", ["api", `orgs/${owner}/memberships/${login}`, "--jq", ".role"]));
5570
- if (role !== "admin") {
5571
- throw new Error(`${commandAuthorityLabel(owner)} is master-admin only; @${login} is ${role || "not an org admin"}`);
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 && !opts.banner) {
9275
+ if (!pluginCheck.ok && pluginCheck.recordToInsert && !opts.json) {
9216
9276
  if (writeProjectInstallRecord(pluginCheck.recordToInsert)) {
9217
9277
  pluginCheck = { ...pluginCheck, ok: true };
9218
- io.err(" \u21BB repaired: registered mmi@mmi project install record \u2014 run /reload-plugins to load it this session");
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 && !opts.banner) {
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(" \u21BB repaired: collapsed mmi@mmi to one user-scope entry (backup at installed_plugins.json.bak) \u2014 run /reload-plugins");
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
- const reload = surface === "claude-vscode" ? "reopen the VS Code workspace" : "restart Claude Code (or run /reload-plugins)";
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 && !opts.banner) {
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 reload affected sessions`);
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.9.0",
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",