@mutmutco/cli 3.2.0 → 3.3.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 +213 -32
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -9219,9 +9219,23 @@ function parseNpmVersion(stdout) {
9219
9219
  function staleTrainCliMessage(report, commandName) {
9220
9220
  return `running mmi-cli ${report.currentVersion} is stale against released ${report.releasedVersion}; this is recoverable \u2014 run \`mmi-cli doctor --apply --no-repo-writes\` to update to ${report.releasedVersion}, then rerun ${commandName} so it uses the current train path`;
9221
9221
  }
9222
- function versionAutoUpdateAction(report, hasPluginRoot) {
9222
+ async function resolveReleasedVersion(runners) {
9223
+ try {
9224
+ const npmVersion = parseNpmVersion(await runners.npm());
9225
+ if (npmVersion) return { version: npmVersion, source: "npm" };
9226
+ } catch {
9227
+ }
9228
+ try {
9229
+ const ghVersion = parseManifestVersion(await runners.gh());
9230
+ if (ghVersion) return { version: ghVersion, source: "gh" };
9231
+ } catch {
9232
+ }
9233
+ return { version: void 0, source: "unavailable" };
9234
+ }
9235
+ function versionAutoUpdateAction(report, hasPluginRoot, releasedSource) {
9223
9236
  if (report.ok || report.staleAgainst !== "released") return "none";
9224
- return hasPluginRoot ? "plugin-pull" : "npm";
9237
+ if (hasPluginRoot) return "plugin-pull";
9238
+ return releasedSource === "gh" ? "npm-unreachable" : "npm";
9225
9239
  }
9226
9240
 
9227
9241
  // src/issue-related.ts
@@ -12508,6 +12522,19 @@ async function fetchTrainAuthority(repo, deps) {
12508
12522
  return { ok: false, error: e.message };
12509
12523
  }
12510
12524
  }
12525
+ async function mintWikiToken(repo, deps) {
12526
+ const res = await postJson("/wiki-mint", { repo }, deps, "POST", { noRetry: true });
12527
+ if (res.error) return { ok: false, error: res.error };
12528
+ const body = res.body ?? {};
12529
+ if (res.status === 426) return { ok: false, error: upgradeRequiredError({ status: 426 }, res.body) };
12530
+ if (res.status === 403) return { ok: false, error: `master-admin only (HTTP 403)${body.error ? ` \u2014 ${body.error}` : ""}` };
12531
+ if (res.status === 404) {
12532
+ return { ok: false, error: "the Hub API did not recognize /wiki-mint (HTTP 404) \u2014 the deployed Hub predates this command; it answers once a Hub release carrying it is deployed" };
12533
+ }
12534
+ if (!res.ok) return { ok: false, error: `wiki-mint HTTP ${res.status}${body.error ? ` \u2014 ${body.error}` : ""}` };
12535
+ if (!body.token || !body.expiresAt) return { ok: false, error: "malformed wiki-mint response (no token)" };
12536
+ return { ok: true, mint: { token: body.token, expiresAt: body.expiresAt, repository: body.repository ?? repo, permissions: body.permissions ?? {} } };
12537
+ }
12511
12538
  async function fetchProjectsList(deps) {
12512
12539
  if (!deps.baseUrl) return null;
12513
12540
  const token = await deps.token();
@@ -12645,6 +12672,32 @@ async function tenantDeploy(payload, deps) {
12645
12672
  return postJson("/tenant-deploy", payload, deps, "POST", { noRetry: true, timeoutMs: TENANT_DEPLOY_TIMEOUT_MS });
12646
12673
  }
12647
12674
 
12675
+ // src/wiki-publish-command.ts
12676
+ async function wikiPublish(deps, opts) {
12677
+ if (!opts.pagesDir) {
12678
+ deps.err("wiki publish: <pages-dir> is required");
12679
+ return false;
12680
+ }
12681
+ const script = deps.scriptPath();
12682
+ if (!deps.scriptExists(script)) {
12683
+ deps.err(`wiki publish: publish script not found at ${script} \u2014 run from the repo checkout root`);
12684
+ return false;
12685
+ }
12686
+ const minted = await deps.mint(opts.repo);
12687
+ if (!minted.ok) {
12688
+ deps.err(`wiki publish failed: could not mint a scoped wiki token for ${opts.repo}: ${minted.error}`);
12689
+ return false;
12690
+ }
12691
+ const env = { ...process.env, WIKI_REPO: opts.repo, WIKI_TOKEN: minted.mint.token };
12692
+ const code = deps.spawn("node", [script, opts.pagesDir], env);
12693
+ if (code !== 0) {
12694
+ deps.err(`wiki publish failed: publish script exited ${code} (token was scoped to ${opts.repo}, contents:write, expires ${minted.mint.expiresAt})`);
12695
+ return false;
12696
+ }
12697
+ deps.log(`wiki published for ${opts.repo} (scoped token, expired/discarded)`);
12698
+ return true;
12699
+ }
12700
+
12648
12701
  // src/project-readiness.ts
12649
12702
  function stagesForTrack(meta) {
12650
12703
  return branchesForTrack(resolveReleaseTrack(meta)).map((b) => b === "development" ? "dev" : b);
@@ -13788,7 +13841,9 @@ function vaultPointer(slug) {
13788
13841
  slug,
13789
13842
  root,
13790
13843
  tiers: {
13791
- project: `${root}/{dev,rc,main}/* (project-admin self-serve for this repo)`,
13844
+ // #2482: a stageless catalog entry (stages: []) lives at the slug ROOT — one value per repo
13845
+ // alongside the staged tree for per-stage entries. Both are the same project-admin wall (#2032).
13846
+ project: `${root}/<KEY> (stageless, one value per repo) + ${root}/{dev,rc,main}/* (staged entries) (project-admin self-serve for this repo)`,
13792
13847
  org: [`/mmi-future/{shared,cloudflare,mmi-hub,...}/* (org-infra, master-gated)`]
13793
13848
  },
13794
13849
  stages: ["dev", "rc", "main"],
@@ -15293,6 +15348,7 @@ function buildCodexActiveCacheCheck(input) {
15293
15348
  fix: CODEX_PLUGIN_RECOVERY
15294
15349
  };
15295
15350
  if (!input.isOrgRepo || !isSemverVersion2(input.releasedVersion)) return base;
15351
+ if (input.codexPresent === false) return base;
15296
15352
  if (isSemverVersion2(input.codexActiveVersion)) {
15297
15353
  if (compareVersions(input.codexActiveVersion, input.releasedVersion) < 0) {
15298
15354
  return {
@@ -15571,12 +15627,13 @@ function buildPluginUpdateReport(input) {
15571
15627
  ...codexMarketplace ? { codexMarketplace } : {},
15572
15628
  ...codexActiveCache ? { codexActiveCache } : {},
15573
15629
  ...opencodePlugin ? { opencodePlugin } : {},
15574
- ...isSemverVersion2(input.releasedVersion) ? { released: input.releasedVersion } : {}
15630
+ ...isSemverVersion2(input.releasedVersion) ? { released: input.releasedVersion } : {},
15631
+ ...input.releasedVersionSource ? { releasedSource: input.releasedVersionSource } : {}
15575
15632
  },
15576
15633
  recipes: PLUGIN_UPDATE_RECIPES
15577
15634
  };
15578
15635
  }
15579
- function renderPluginUpdateReport(report) {
15636
+ function renderPluginVersionBlock(report) {
15580
15637
  const v = report.versions;
15581
15638
  const show = (x) => x ?? "unknown";
15582
15639
  const versionRows = [
@@ -15587,16 +15644,10 @@ function renderPluginUpdateReport(report) {
15587
15644
  ["OpenCode plugin", show(v.opencodePlugin)]
15588
15645
  ];
15589
15646
  const pad = Math.max(...versionRows.map(([label]) => label.length));
15590
- const lines = [
15647
+ return [
15591
15648
  `MMI versions (target release: ${show(v.released)})`,
15592
- ...versionRows.map(([label, value]) => ` ${label.padEnd(pad)} ${value}`),
15593
- "",
15594
- "Update commands by surface"
15649
+ ...versionRows.map(([label, value]) => ` ${label.padEnd(pad)} ${value}`)
15595
15650
  ];
15596
- for (const surface of PLUGIN_GUIDE_SURFACES) {
15597
- lines.push(...renderSurfaceGuide(surface.label, report.recipes[surface.key]));
15598
- }
15599
- return lines;
15600
15651
  }
15601
15652
  function buildDoctorJsonPayload(input) {
15602
15653
  return {
@@ -16230,7 +16281,7 @@ function doctorHumanLines(input) {
16230
16281
  lines.push("", DOCTOR_VERBOSE_HINT);
16231
16282
  }
16232
16283
  if (!input.verbose) return lines;
16233
- lines.push("", ...renderPluginUpdateReport(input.updateReport));
16284
+ lines.push("", ...renderPluginVersionBlock(input.updateReport));
16234
16285
  lines.push(
16235
16286
  "",
16236
16287
  doctorSummaryLine({ checks: input.checks, updateReport: input.updateReport, shouldApply: input.shouldApply, healedCount: input.healedCount }),
@@ -16347,6 +16398,7 @@ function extractTomlServer(content, spec) {
16347
16398
  }
16348
16399
  function detectMcpState(target, spec) {
16349
16400
  if (!target.present) return { target, state: "skip", action: "skip", reasons: [] };
16401
+ if (target.format === "claude-cli") return detectClaudeCliMcpState(target, spec);
16350
16402
  if (target.content == null) {
16351
16403
  if (target.createWhenFileAbsent) return { target, state: "absent", action: "install", reasons: [] };
16352
16404
  return { target, state: "skip", action: "skip", reasons: [] };
@@ -16370,6 +16422,56 @@ function detectMcpState(target, spec) {
16370
16422
  function planMcpReconcile(targets, spec) {
16371
16423
  return targets.map((t) => detectMcpState(t, spec));
16372
16424
  }
16425
+ function parseClaudeMcpGetOutput(stdout) {
16426
+ const commandMatch = /^\s*Command:\s*(.+)$/m.exec(stdout);
16427
+ if (commandMatch == null) return null;
16428
+ const argsMatch = /^\s*Args:\s*(.*)$/m.exec(stdout);
16429
+ const env = {};
16430
+ const lines = stdout.replace(/\r\n/g, "\n").split("\n");
16431
+ const headerIdx = lines.findIndex((l) => /^\s*Environment:\s*$/.test(l));
16432
+ if (headerIdx !== -1) {
16433
+ for (const line of lines.slice(headerIdx + 1)) {
16434
+ if (line.trim() === "") continue;
16435
+ if (!/^[ \t]/.test(line)) break;
16436
+ const kv = /^\s*([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(line);
16437
+ if (kv != null) env[kv[1]] = kv[2];
16438
+ }
16439
+ }
16440
+ return { command: commandMatch[1].trim(), args: (argsMatch?.[1] ?? "").trim(), env };
16441
+ }
16442
+ function detectClaudeCliMcpState(target, spec) {
16443
+ if (target.content == null) {
16444
+ return { target, state: "absent", action: "install", reasons: [] };
16445
+ }
16446
+ const matchedName = target.claudeCliMatchedName ?? spec.name;
16447
+ const isLegacy = matchedName !== spec.name;
16448
+ const parsed = parseClaudeMcpGetOutput(target.content);
16449
+ if (parsed == null) {
16450
+ const reasons2 = isLegacy ? ["legacy-name", "unparseable"] : ["unparseable"];
16451
+ return { target, state: "drifted", action: "reconcile", reasons: reasons2, matchedName };
16452
+ }
16453
+ const reasons = [];
16454
+ if (isLegacy) reasons.push("legacy-name");
16455
+ if (textHasPlaywrightVisionCap(parsed.args)) reasons.push("vision-cap");
16456
+ if (!textHasCanonicalPlaywrightOutputDir(parsed.args)) reasons.push("output-dir");
16457
+ if (reasons.length === 0) return { target, state: "healthy", action: "none", reasons: [], matchedName };
16458
+ return { target, state: "drifted", action: "reconcile", reasons, matchedName };
16459
+ }
16460
+ var CLAUDE_MCP_USER_SCOPE = "user";
16461
+ function planClaudeCliMcpHeal(item, spec) {
16462
+ if (item.action !== "install" && item.action !== "reconcile") return [];
16463
+ const commands = [];
16464
+ if (item.action === "reconcile" && item.matchedName != null) {
16465
+ commands.push({ op: "remove", args: ["remove", item.matchedName, "-s", CLAUDE_MCP_USER_SCOPE] });
16466
+ }
16467
+ const parsed = item.target.content != null ? parseClaudeMcpGetOutput(item.target.content) : null;
16468
+ const envFlags = Object.entries(parsed?.env ?? {}).flatMap(([k, v]) => ["-e", `${k}=${v}`]);
16469
+ commands.push({
16470
+ op: "add",
16471
+ args: ["add", spec.name, "-s", CLAUDE_MCP_USER_SCOPE, ...envFlags, "--", spec.command, ...spec.args]
16472
+ });
16473
+ return commands;
16474
+ }
16373
16475
  function buildMcpReconcileCheck(plan) {
16374
16476
  const base = {
16375
16477
  ok: true,
@@ -16771,20 +16873,22 @@ function readRepoVersion() {
16771
16873
  }
16772
16874
  }
16773
16875
  async function fetchReleasedVersion() {
16774
- try {
16775
- const { stdout } = await execFileP2("gh", pluginManifestVersionArgs(), { timeout: 5e3 });
16776
- return parseManifestVersion(stdout);
16777
- } catch {
16778
- return void 0;
16779
- }
16876
+ return resolveReleasedVersion({
16877
+ npm: async () => (await runHostBin("npm", npmReleasedVersionArgs(), { timeout: NPM_VIEW_TIMEOUT_MS })).stdout,
16878
+ gh: async () => (await execFileP2("gh", pluginManifestVersionArgs(), { timeout: 5e3 })).stdout
16879
+ });
16780
16880
  }
16781
16881
  var NPM_UPDATE_TIMEOUT_MS = 12e4;
16782
16882
  var NPM_VIEW_TIMEOUT_MS = 15e3;
16783
16883
  var PLUGIN_PULL_TIMEOUT_MS = 3e4;
16784
- async function applyVersionAutoUpdate(report, log) {
16785
- const action = versionAutoUpdateAction(report, Boolean(process.env.CLAUDE_PLUGIN_ROOT));
16884
+ async function applyVersionAutoUpdate(report, log, releasedSource) {
16885
+ const action = versionAutoUpdateAction(report, Boolean(process.env.CLAUDE_PLUGIN_ROOT), releasedSource);
16786
16886
  if (action === "none") return { report, applied: "none" };
16787
16887
  const target = report.releasedVersion ?? "latest";
16888
+ if (action === "npm-unreachable") {
16889
+ log(` \u2717 mmi-cli ${report.currentVersion} is stale (released ${target}, resolved via gh) \u2014 npm registry unreachable this pass; when npm is back: npm install -g @mutmutco/cli@latest`);
16890
+ return { report, applied: "none" };
16891
+ }
16788
16892
  if (action === "plugin-pull") {
16789
16893
  try {
16790
16894
  const root = (await execFileP2("git", ["-C", process.env.CLAUDE_PLUGIN_ROOT, "rev-parse", "--show-toplevel"], { timeout: PLUGIN_PULL_TIMEOUT_MS })).stdout.trim();
@@ -17495,17 +17599,51 @@ function writeMcpConfigFile(path2, content) {
17495
17599
  return false;
17496
17600
  }
17497
17601
  }
17602
+ var CLAUDE_CLI_MCP_LABEL = "Claude Code MCP store (user scope)";
17603
+ var CLAUDE_CLI_MCP_PATH = "claude mcp (user scope)";
17604
+ async function claudeCliMcpTarget(spec) {
17605
+ const base = {
17606
+ host: "claude-cli",
17607
+ label: CLAUDE_CLI_MCP_LABEL,
17608
+ path: CLAUDE_CLI_MCP_PATH,
17609
+ format: "claude-cli",
17610
+ isRepoFile: false,
17611
+ createWhenFileAbsent: true
17612
+ };
17613
+ if (!await hostBinAvailable("claude")) return { ...base, present: false, content: null };
17614
+ for (const name of [spec.name, ...spec.legacyNames]) {
17615
+ try {
17616
+ const { stdout } = await runHostBin("claude", ["mcp", "get", name], { timeout: CLAUDE_PLUGIN_TIMEOUT_MS });
17617
+ return { ...base, present: true, content: stdout, claudeCliMatchedName: name };
17618
+ } catch {
17619
+ }
17620
+ }
17621
+ return { ...base, present: true, content: null };
17622
+ }
17623
+ async function runClaudeCliMcpCommands(commands) {
17624
+ for (const cmd of commands) {
17625
+ try {
17626
+ await runHostBin("claude", ["mcp", ...cmd.args], { timeout: CLAUDE_PLUGIN_TIMEOUT_MS });
17627
+ } catch {
17628
+ return false;
17629
+ }
17630
+ }
17631
+ return true;
17632
+ }
17498
17633
  async function reconcileMcpRegistrations(input) {
17499
17634
  if (!input.isOrgRepo) return buildMcpReconcileCheck([]);
17500
- const targets = mcpConfigTargets();
17635
+ const snapshot = async () => [...mcpConfigTargets(), await claudeCliMcpTarget(PLAYWRIGHT_MCP_SPEC)];
17636
+ const targets = await snapshot();
17501
17637
  if (!input.apply) return buildMcpReconcileCheck(planMcpReconcile(targets, PLAYWRIGHT_MCP_SPEC));
17502
- const results = applyMcpReconcile(targets, PLAYWRIGHT_MCP_SPEC, {
17638
+ const fileTargets = targets.filter((t) => t.format !== "claude-cli");
17639
+ const results = applyMcpReconcile(fileTargets, PLAYWRIGHT_MCP_SPEC, {
17503
17640
  repoWritesAllowed: input.repoWritesAllowed,
17504
17641
  write: writeMcpConfigFile
17505
17642
  });
17506
17643
  for (const r of results) {
17507
17644
  if (r.outcome === "wrote") {
17508
17645
  input.onHealed();
17646
+ input.onReloadRequired();
17509
17647
  const verb = r.action === "install" ? "registered" : "reconciled";
17510
17648
  input.log(` \u21BB ${verb} Playwright MCP in ${r.target.path} \u2014 restart the host to load it`);
17511
17649
  } else if (r.outcome === "unparseable") {
@@ -17514,7 +17652,22 @@ async function reconcileMcpRegistrations(input) {
17514
17652
  input.log(` \u26A0 ${r.target.label}: failed to write ${r.target.path}`);
17515
17653
  }
17516
17654
  }
17517
- return buildMcpReconcileCheck(planMcpReconcile(mcpConfigTargets(), PLAYWRIGHT_MCP_SPEC));
17655
+ const claudeTarget = targets.find((t) => t.format === "claude-cli");
17656
+ if (claudeTarget != null) {
17657
+ const item = detectMcpState(claudeTarget, PLAYWRIGHT_MCP_SPEC);
17658
+ if (item.action === "install" || item.action === "reconcile") {
17659
+ const commands = planClaudeCliMcpHeal(item, PLAYWRIGHT_MCP_SPEC);
17660
+ if (await runClaudeCliMcpCommands(commands)) {
17661
+ input.onHealed();
17662
+ input.onReloadRequired();
17663
+ const verb = item.action === "install" ? "registered" : "reconciled";
17664
+ input.log(` \u21BB ${verb} Playwright MCP via \`claude mcp add\` (${CLAUDE_CLI_MCP_PATH}) \u2014 restart Claude Code to load it`);
17665
+ } else {
17666
+ input.log(` \u26A0 ${CLAUDE_CLI_MCP_LABEL}: \`claude mcp add\` failed for ${PLAYWRIGHT_MCP_SPEC.name} \u2014 run it by hand`);
17667
+ }
17668
+ }
17669
+ }
17670
+ return buildMcpReconcileCheck(planMcpReconcile(await snapshot(), PLAYWRIGHT_MCP_SPEC));
17518
17671
  }
17519
17672
  function strayBrowserArtifactPaths() {
17520
17673
  const cwd = process.cwd();
@@ -17553,7 +17706,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
17553
17706
  const checks = [];
17554
17707
  const REWRITE_KEY = "url.https://github.com/.insteadOf";
17555
17708
  const CLONE_FIX = 'run: git config --global url."https://github.com/".insteadOf "git@github.com:"';
17556
- const [login, pathProbe, releasedVersion, cfg, callerArn, cloneProbe, isOrgRepo] = await Promise.all([
17709
+ const [login, pathProbe, releasedVersionResolution, cfg, callerArn, cloneProbe, isOrgRepo] = await Promise.all([
17557
17710
  githubLogin(),
17558
17711
  execFileP2(isWin ? "where" : "which", ["mmi-cli"]).then(() => true).catch(() => false),
17559
17712
  fetchReleasedVersion(),
@@ -17565,6 +17718,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
17565
17718
  // not the now-always-defaulted `cfg.sagaApiUrl`. Independent git read — resolved concurrently here.
17566
17719
  readOrigin ? isOrgRepoRoot(readOrigin) : isOrgRepoRoot()
17567
17720
  ]);
17721
+ const releasedVersion = releasedVersionResolution.version;
17568
17722
  const surface = detectSurface(process.env);
17569
17723
  const versionReportProbe = buildVersionLagReport({
17570
17724
  currentVersion: resolveClientVersion(),
@@ -17630,7 +17784,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
17630
17784
  });
17631
17785
  let selfUpdatedCli = false;
17632
17786
  if (repairFull && !process.env[DOCTOR_POST_SELF_UPDATE_ENV]) {
17633
- const updated = await applyVersionAutoUpdate(versionReport, (m) => io.err(m));
17787
+ const updated = await applyVersionAutoUpdate(versionReport, (m) => io.err(m), releasedVersionResolution.source);
17634
17788
  versionReport = updated.report;
17635
17789
  selfUpdatedCli = updated.applied === "npm";
17636
17790
  }
@@ -17978,15 +18132,16 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
17978
18132
  };
17979
18133
  }
17980
18134
  checks.push(cacheCleanupCheck);
18135
+ const canDriveCodex = surfaceToken(surface) === "codex" || await hostBinAvailable("codex");
17981
18136
  let codexActiveCacheCheck = buildCodexActiveCacheCheck({
17982
18137
  isOrgRepo,
17983
18138
  releasedVersion,
17984
18139
  codexCacheVersions: codexCacheVersions(),
17985
18140
  codexRecordVersion: codexRecordVersion(),
17986
- codexActiveVersion: await codexActiveVersion()
18141
+ codexActiveVersion: await codexActiveVersion(),
18142
+ codexPresent: canDriveCodex
17987
18143
  });
17988
18144
  if (!codexActiveCacheCheck.ok && repairFull) {
17989
- const canDriveCodex = surfaceToken(surface) === "codex" || await hostBinAvailable("codex");
17990
18145
  if (canDriveCodex && await applyPluginHeal("codex", surface, (m) => io.err(m), { force: true })) {
17991
18146
  markPluginReloadRequired();
17992
18147
  markHealed();
@@ -17996,7 +18151,8 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
17996
18151
  releasedVersion,
17997
18152
  codexCacheVersions: codexCacheVersions(),
17998
18153
  codexRecordVersion: codexRecordVersion(),
17999
- codexActiveVersion: await codexActiveVersion()
18154
+ codexActiveVersion: await codexActiveVersion(),
18155
+ codexPresent: canDriveCodex
18000
18156
  });
18001
18157
  }
18002
18158
  }
@@ -18130,7 +18286,8 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
18130
18286
  apply: Boolean(opts.apply),
18131
18287
  repoWritesAllowed,
18132
18288
  log: (m) => io.err(m),
18133
- onHealed: markHealed
18289
+ onHealed: markHealed,
18290
+ onReloadRequired: markPluginReloadRequired
18134
18291
  })
18135
18292
  );
18136
18293
  }
@@ -18204,7 +18361,8 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
18204
18361
  codexPluginVersions: sourceVersions("codex"),
18205
18362
  codexCacheVersions: cacheVersionsFor("codex"),
18206
18363
  opencodePluginVersions: opencodePluginVersionsForReport(),
18207
- releasedVersion
18364
+ releasedVersion,
18365
+ releasedVersionSource: releasedVersionResolution.source
18208
18366
  });
18209
18367
  }
18210
18368
  });
@@ -20741,6 +20899,29 @@ ${r.repo}: applied=[${r.applied.join("; ")}] skipped=[${r.skipped.join("; ")}]${
20741
20899
  }
20742
20900
  if (!audit.ok) process.exitCode = 1;
20743
20901
  });
20902
+ var wiki = program2.command("wiki").description("release wiki lane \u2014 publish generated pages via a short-lived, repo-scoped minted token");
20903
+ wiki.command("publish <pages-dir>").description("mint a 1h repo-scoped contents:write token (master-gated) and publish the generated .md pages to <repo>.wiki.git via scripts/wiki-publish.mjs; the token stays in memory (never printed/committed). Fails loud on the credential gap \u2014 never a silent skip").option("--repo <owner/repo>", "target repo (defaults to the cwd repo)").action(async (pagesDir, o) => {
20904
+ const repo = await resolveRepo(o.repo);
20905
+ if (!repo) return fail("wiki publish: could not determine the target repo (pass --repo owner/name)");
20906
+ const cfg = await loadConfig();
20907
+ const ok = await wikiPublish(
20908
+ {
20909
+ mint: (r) => mintWikiToken(r, registryClientDeps(cfg)),
20910
+ // scripts/wiki-publish.mjs lives at the repo root; Lane B runs there. Resolve against cwd.
20911
+ scriptPath: () => (0, import_node_path18.resolve)(process.cwd(), "scripts", "wiki-publish.mjs"),
20912
+ scriptExists: (p) => (0, import_node_fs18.existsSync)(p),
20913
+ spawn: (command, args, env) => {
20914
+ const r = (0, import_node_child_process11.spawnSync)(command, args, { stdio: "inherit", env });
20915
+ if (r.error) throw r.error;
20916
+ return r.status ?? 1;
20917
+ },
20918
+ log: (msg) => console.log(msg),
20919
+ err: (msg) => console.error(msg)
20920
+ },
20921
+ { repo, pagesDir }
20922
+ );
20923
+ if (!ok) process.exitCode = 1;
20924
+ });
20744
20925
  var bootstrap = program2.command("bootstrap").description("plan repo bootstrap operations; mutations require master-admin approval").option("--repo <owner/repo>", "target repo").option("--class <class>", "deployable | content", "deployable").option("--json", "machine-readable output").option("--apply", "reserved for future bootstrap execution after explicit master-admin approval").action((o) => {
20745
20926
  if (!o.repo) return fail("bootstrap: required option --repo <owner/repo> not specified");
20746
20927
  if (o.apply) return fail("bootstrap: execution is not implemented yet; use the dry-run plan and the existing /bootstrap skill");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "3.2.0",
3
+ "version": "3.3.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 plugin's session-start hook drives.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",