@mutmutco/cli 4.3.68 → 4.4.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/main.cjs +75 -51
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -6380,7 +6380,7 @@ function decorateAwsModel(entries, registry2, verdicts) {
6380
6380
  });
6381
6381
  }
6382
6382
  var DECLARED_ENTRIES = [
6383
- { name: "mmi-backup (mm-central, mmi-fofu, zuber, mmi-oguz)", cadence: "17 2 * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-backup \u2192 /opt/mmi-control/nightly-backup.sh (verify: mmi-cli devops runtime box get <box> --ssh)" },
6383
+ { name: "mmi-backup (mm-central, mm-work, zuber, mmi-oguz)", cadence: "17 2 * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-backup \u2192 /opt/mmi-control/nightly-backup.sh (verify: mmi-cli devops runtime box get <box> --ssh)" },
6384
6384
  // #3370/#6270: the one runner hygiene layer (runner-hygiene.yml was folded in and deleted). A workflow ran ON the runner and needed checkout +
6385
6385
  // setup-node before it can prune, so at 100% it dies at checkout and never prunes — the cleanup is
6386
6386
  // unavailable exactly when it is needed. This one is root cron on the box: no checkout, no node, and
@@ -9439,6 +9439,18 @@ function resolveUseEnvNames(keys, name) {
9439
9439
  }
9440
9440
  return { ok: true, entries };
9441
9441
  }
9442
+ function referencesDollarEnv(arg, name) {
9443
+ return arg.includes(`\${${name}}`) || new RegExp(`\\$${name}(?![A-Za-z0-9_])`).test(arg);
9444
+ }
9445
+ function secretsUseDollarHint(command, envNames) {
9446
+ for (const arg of command) {
9447
+ for (const name of envNames) {
9448
+ if (!referencesDollarEnv(arg, name)) continue;
9449
+ return `secrets use: $${name} is injected into the CHILD's env, not your shell \u2014 the caller expanded it (empty) before this ran. Wrap the consumer so expansion happens inside: bash -c 'your-command "$${name}"'`;
9450
+ }
9451
+ }
9452
+ return null;
9453
+ }
9442
9454
  var PRE_SPAWN_DRAIN_TIMEOUT_MS = 2e3;
9443
9455
  async function drainHttpPoolBeforeSpawn() {
9444
9456
  const drained = await Promise.race([
@@ -9490,6 +9502,8 @@ async function secretsUse(deps, keys, opts) {
9490
9502
  deps.err(named.error);
9491
9503
  return false;
9492
9504
  }
9505
+ const dollarHint = secretsUseDollarHint(command, named.entries.map((e) => e.envName));
9506
+ if (dollarHint) deps.err(dollarHint);
9493
9507
  const repo = await targetRepo(deps, opts);
9494
9508
  const slug = opts.slug?.toLowerCase();
9495
9509
  const env = { ...process.env };
@@ -14238,9 +14252,6 @@ function expectedHosts(cfg) {
14238
14252
  for (const env of ENV_PREFIXES) out.push(env ? `${env}.${base}` : base);
14239
14253
  }
14240
14254
  }
14241
- if (cfg.fofuSubdomain !== void 0) {
14242
- out.push(cfg.fofuSubdomain ? `${cfg.fofuSubdomain}.fofu.ai` : "fofu.ai");
14243
- }
14244
14255
  return uniq([...out, ...cfg.edgeHosts ?? []]);
14245
14256
  }
14246
14257
  function expectedJsOrigins(cfg) {
@@ -14305,10 +14316,7 @@ function parseOauthConfig(mmiConfig, slug) {
14305
14316
  );
14306
14317
  }
14307
14318
  const extraCallbackPaths = parseExtraCallbackPaths(raw.extraCallbackPaths);
14308
- const meta = mmiConfig ?? {};
14309
- const rawFofuSub = raw.fofuSubdomain;
14310
- const fofuSubdomain = meta.fofuEnabled === true ? typeof rawFofuSub === "string" ? rawFofuSub : defaultSubdomain(slug) : void 0;
14311
- return { subdomains, domains, callbackPath, extraCallbackPaths, fofuSubdomain, edgeHosts: Object.values(edgeDomainsByStage(mmiConfig)) };
14319
+ return { subdomains, domains, callbackPath, extraCallbackPaths, edgeHosts: Object.values(edgeDomainsByStage(mmiConfig)) };
14312
14320
  }
14313
14321
  function probeRedirectUri(callbackPath, port = 9123) {
14314
14322
  return `http://localhost:${port}${callbackPath}`;
@@ -15862,10 +15870,10 @@ var rollout_plan_default = {
15862
15870
  note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
15863
15871
  },
15864
15872
  baseline: {
15865
- version: "4.3.68",
15866
- tag: "v4.3.68",
15867
- commit: "e9edc6e353ec",
15868
- npm: "@mutmutco/cli@4.3.68"
15873
+ version: "4.4.1",
15874
+ tag: "v4.4.1",
15875
+ commit: "a4bb2ac4bdb4",
15876
+ npm: "@mutmutco/cli@4.4.1"
15869
15877
  },
15870
15878
  exitCriterion: "fleet-n-of-n",
15871
15879
  hubOnlyShortcut: "forbidden",
@@ -15882,14 +15890,14 @@ var rollout_plan_default = {
15882
15890
  repo: "mutmutco/mmi-hub",
15883
15891
  role: "canary",
15884
15892
  schedule: "train",
15885
- v3Target: "v4.3.68"
15893
+ v3Target: "v4.4.1"
15886
15894
  }
15887
15895
  ],
15888
15896
  rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
15889
15897
  rollback: {
15890
15898
  independent: true,
15891
- mechanism: "npm dist-tag latest -> 4.3.68 and redeploy the Hub Lambda from tag v4.3.68 (e9edc6e353ec); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15892
- v3Target: "v4.3.68 (@mutmutco/cli@4.3.68, tag commit e9edc6e353ec \u2014 last known-good release carrying the repo-index v4-only contract)"
15899
+ mechanism: "npm dist-tag latest -> 4.4.1 and redeploy the Hub Lambda from tag v4.4.1 (a4bb2ac4bdb4); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15900
+ v3Target: "v4.4.1 (@mutmutco/cli@4.4.1, tag commit a4bb2ac4bdb4 \u2014 last known-good release carrying the repo-index v4-only contract)"
15893
15901
  }
15894
15902
  },
15895
15903
  {
@@ -15986,12 +15994,6 @@ var rollout_plan_default = {
15986
15994
  schedule: "train",
15987
15995
  v3Target: "v0.3.0"
15988
15996
  },
15989
- {
15990
- repo: "mutmutco/mm-fofu",
15991
- role: "member",
15992
- schedule: "train",
15993
- v3Target: "v0.1.0"
15994
- },
15995
15997
  {
15996
15998
  repo: "mutmutco/mmi-junior",
15997
15999
  role: "member",
@@ -21626,6 +21628,9 @@ async function supersedeContentCausedDeployLeg(deps, cwd) {
21626
21628
  function supersedeablePublishLegShape(rec) {
21627
21629
  return rec.state === "failed" || rec.state === "pending" && rec.runId == null;
21628
21630
  }
21631
+ function deferredGatewayDeployLegShape(rec) {
21632
+ return rec.state === "pending" && rec.runId == null && rec.workflow === "jerv-gateway";
21633
+ }
21629
21634
  async function supersedeContentCausedPublishLeg(deps, cwd) {
21630
21635
  const path2 = releaseLedgerPath(cwd);
21631
21636
  return withFileLock(`${path2}.lock`, { label: "release ledger" }, async () => {
@@ -21651,7 +21656,9 @@ async function supersedeContentCausedPublishLeg(deps, cwd) {
21651
21656
  const closed = await alignmentLegClosedLive(deps, prior);
21652
21657
  if (closed) phases.alignment = closed;
21653
21658
  }
21654
- const unresolved = ["promotion", "ordinaryCi", "githubRelease", "deploy", "alignment"].filter((phase) => phases[phase].state === "pending" || phases[phase].state === "failed");
21659
+ let gatewayDeploy;
21660
+ const deferredGateway = isJervHubRepo(prior.repo) && deferredGatewayDeployLegShape(phases.deploy);
21661
+ const unresolved = ["promotion", "ordinaryCi", "githubRelease", "deploy", "alignment"].filter((phase) => !(phase === "deploy" && deferredGateway)).filter((phase) => phases[phase].state === "pending" || phases[phase].state === "failed");
21655
21662
  if (unresolved.length > 0) {
21656
21663
  throw new Error(
21657
21664
  `release --supersede-publish: ${prior.tag} has unresolved legs besides publish (${unresolved.map((p) => `${p}=${phases[p].state}`).join(", ")}) \u2014 the supersession closes only the publish leg of an otherwise-complete release; resolve those first, nothing was written (#6638)`
@@ -21704,6 +21711,25 @@ async function supersedeContentCausedPublishLeg(deps, cwd) {
21704
21711
  note: `closed the deleted ${prior.tag} publish failure by archiving its ledger after origin and GitHub both proved absent; start a fresh release train to carry the cause fix`
21705
21712
  };
21706
21713
  }
21714
+ if (deferredGateway) {
21715
+ const dispatch = await appendJervGatewayReleaseDeploy(
21716
+ deps,
21717
+ prior.repo,
21718
+ prior.tag,
21719
+ prior.tagSha,
21720
+ { note: `Jerv Gateway operator-host deploy leg for ${prior.tag} run during the publish supersession`, deployStatus: "success" }
21721
+ );
21722
+ phases.deploy = {
21723
+ ...phaseEntry(deployPhaseInput("registry-publish", dispatch, { releaseSha: prior.tagSha })),
21724
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
21725
+ };
21726
+ if (dispatch.deployStatus !== "success") {
21727
+ throw new Error(
21728
+ `release --supersede-publish: the deferred ${prior.tag} Jerv Gateway operator-host deploy leg did not verify (${dispatch.note}) \u2014 run \`scripts/jerv-gateway-release-deploy.sh --tag ${prior.tag}\` to a healthy receipt naming the released tag and commit, then retry; nothing was written (#6742)`
21729
+ );
21730
+ }
21731
+ gatewayDeploy = { workflow: "jerv-gateway", state: "complete", note: dispatch.note };
21732
+ }
21707
21733
  const note = `superseded: content-caused publish failure on the immutable promoted ${prior.tag} \u2014 the cause fix rides the next patch train (master-approved closure, #6638)`;
21708
21734
  atomicWriteJson(path2, {
21709
21735
  ...prior,
@@ -21718,7 +21744,8 @@ async function supersedeContentCausedPublishLeg(deps, cwd) {
21718
21744
  repo: prior.repo,
21719
21745
  tag: prior.tag,
21720
21746
  tagSha: prior.tagSha,
21721
- note: `closed the ${prior.tag} publish leg as superseded (skipped) \u2014 the ledger is phases-green; rerun the train doctor to confirm, then run the next patch train (hotfix or release) to carry the cause fix`
21747
+ note: `closed the ${prior.tag} publish leg as superseded (skipped) \u2014 the ledger is phases-green; rerun the train doctor to confirm, then run the next patch train (hotfix or release) to carry the cause fix`,
21748
+ ...gatewayDeploy ? { gatewayDeploy } : {}
21722
21749
  };
21723
21750
  });
21724
21751
  }
@@ -29667,12 +29694,12 @@ function registerDeployCommands(program3) {
29667
29694
  init_cli_shared();
29668
29695
 
29669
29696
  // src/project-set.ts
29670
- var UNSET_KEYS = ["oauth", "requiredRuntimeSecrets", "requiredBuildSecrets", "tenantTasks", "secrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "releaseChannel", "releaseLanguage", "dsManifestPath", "fofuEnabled", "consumesDesignSystem", "ci", "requiredChecks", "requiredCheckBranches", "ciExemptReason", "gate", "seedCanary"];
29697
+ var UNSET_KEYS = ["oauth", "requiredRuntimeSecrets", "requiredBuildSecrets", "tenantTasks", "secrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "releaseChannel", "releaseLanguage", "dsManifestPath", "consumesDesignSystem", "ci", "requiredChecks", "requiredCheckBranches", "ciExemptReason", "gate", "seedCanary"];
29671
29698
  var UNSET_KEY_SET = new Set(UNSET_KEYS);
29672
29699
  var RUNTIME_SECRET_STAGES = ["dev", "rc", "main"];
29673
29700
  var SECRET_CONSUMERS = ["runtime", "build", "lambda", "actions", "agent", "box"];
29674
29701
  var SECRET_ENV_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
29675
- var BUILD_SECRET_REF_RE = /^(?:[A-Z_][A-Z0-9_]*=)?(?:[A-Z_][A-Z0-9_]*|(?:mm-fofu|_org\/[a-z0-9][a-z0-9-]*):[A-Z_][A-Z0-9_]*|@github-packages-token)$/;
29702
+ var BUILD_SECRET_REF_RE = /^(?:[A-Z_][A-Z0-9_]*=)?(?:[A-Z_][A-Z0-9_]*|(?:_org\/[a-z0-9][a-z0-9-]*):[A-Z_][A-Z0-9_]*|@github-packages-token)$/;
29676
29703
  function previewRegistryMetaMerge(existing, patch) {
29677
29704
  const out = { ...existing ?? {} };
29678
29705
  for (const [key, value] of Object.entries(patch)) {
@@ -29868,7 +29895,7 @@ function parseOauthVar(raw) {
29868
29895
  throw new Error(`org project set: oauth must be JSON, e.g. {"subdomains":["app"],"domains":["example.co"],"callbackPath":"${DEFAULT_CALLBACK_PATH}"}`);
29869
29896
  }
29870
29897
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
29871
- throw new Error("org project set: oauth must be a {subdomains,domains,callbackPath,extraCallbackPaths,fofuSubdomain} object");
29898
+ throw new Error("org project set: oauth must be a {subdomains,domains,callbackPath,extraCallbackPaths} object");
29872
29899
  }
29873
29900
  const map = parsed;
29874
29901
  const out = {};
@@ -29896,11 +29923,8 @@ function parseOauthVar(raw) {
29896
29923
  } catch (e) {
29897
29924
  throw new Error(`org project set: ${e.message}`);
29898
29925
  }
29899
- } else if (key === "fofuSubdomain") {
29900
- if (typeof value !== "string") throw new Error('org project set: oauth.fofuSubdomain must be a string ("" selects the apex fofu.ai)');
29901
- out.fofuSubdomain = value.trim();
29902
29926
  } else {
29903
- throw new Error(`org project set: oauth key "${key}" \u2014 expected only subdomains/domains/callbackPath/extraCallbackPaths/fofuSubdomain`);
29927
+ throw new Error(`org project set: oauth key "${key}" \u2014 expected only subdomains/domains/callbackPath/extraCallbackPaths`);
29904
29928
  }
29905
29929
  }
29906
29930
  return out;
@@ -29922,11 +29946,6 @@ function parsePublishRequiredVar(raw) {
29922
29946
  if (raw === "false") return false;
29923
29947
  throw new Error("org project set: publishRequired must be true or false");
29924
29948
  }
29925
- function parseFofuEnabledVar(raw) {
29926
- if (raw === "true") return true;
29927
- if (raw === "false") return false;
29928
- throw new Error("org project set: fofuEnabled must be true or false");
29929
- }
29930
29949
  function parseRuntimeVaultOnlyVar(raw) {
29931
29950
  if (raw === "true") return true;
29932
29951
  if (raw === "false") return false;
@@ -30084,7 +30103,6 @@ var SETTABLE_VAR_KEYS = [
30084
30103
  "releaseChannel",
30085
30104
  "releaseLanguage",
30086
30105
  "dsManifestPath",
30087
- "fofuEnabled",
30088
30106
  "consumesDesignSystem",
30089
30107
  "runtimeVaultOnly",
30090
30108
  "requiredGcpApis",
@@ -30113,12 +30131,11 @@ var SETTABLE_VAR_HINTS = {
30113
30131
  releaseChannel: "Slack channel id, e.g. C0BE0BRGAT0",
30114
30132
  releaseLanguage: "2-letter language code for release notes, e.g. tr (default en)",
30115
30133
  dsManifestPath: "relative path to a package.json, e.g. web/package.json",
30116
- fofuEnabled: "true|false",
30117
30134
  consumesDesignSystem: '"fofu"',
30118
30135
  runtimeVaultOnly: "true|false",
30119
30136
  seedCanary: "true|false",
30120
30137
  repos: 'JSON array, e.g. ["mutmutco/mm-foo"]',
30121
- oauth: "JSON {subdomains,domains,callbackPath,extraCallbackPaths,fofuSubdomain}",
30138
+ oauth: "JSON {subdomains,domains,callbackPath,extraCallbackPaths}",
30122
30139
  requiredGcpApis: "comma-string",
30123
30140
  requiredRuntimeSecrets: 'JSON stage map, e.g. {"dev":["KEY"],"rc":["KEY"],"main":["KEY"]}',
30124
30141
  requiredBuildSecrets: 'JSON flat array, e.g. ["NODE_AUTH_TOKEN=@github-packages-token"]',
@@ -30214,8 +30231,6 @@ function buildProjectSetPatch(input) {
30214
30231
  patch[key] = parseReposVar(raw);
30215
30232
  } else if (key === "publishRequired") {
30216
30233
  patch[key] = parsePublishRequiredVar(raw);
30217
- } else if (key === "fofuEnabled") {
30218
- patch[key] = parseFofuEnabledVar(raw);
30219
30234
  } else if (key === "runtimeVaultOnly") {
30220
30235
  patch[key] = parseRuntimeVaultOnlyVar(raw);
30221
30236
  } else if (key === "seedCanary") {
@@ -36752,7 +36767,7 @@ async function runStageLiveDown(deps, t) {
36752
36767
  var GITHUB_PACKAGES_TOKEN_REF = "@github-packages-token";
36753
36768
  var ENV_ID_RE = /^[A-Z_][A-Z0-9_]*$/;
36754
36769
  var BARE_VAULT_KEY_RE = /^[A-Z_][A-Z0-9_]*$/;
36755
- var CROSS_SLUG_REF_RE = /^(?:mm-fofu|_org\/[a-z0-9][a-z0-9-]*):[A-Z_][A-Z0-9_]*$/;
36770
+ var CROSS_SLUG_REF_RE = /^(?:_org\/[a-z0-9][a-z0-9-]*):[A-Z_][A-Z0-9_]*$/;
36756
36771
  function parseStageBuildSecretEntry(entry) {
36757
36772
  const trimmed = entry.trim();
36758
36773
  if (!trimmed) return null;
@@ -36824,7 +36839,6 @@ async function fetchVaultRef(fetchVault, ref) {
36824
36839
  if (colon === -1) return fetchVault(ref);
36825
36840
  const prefix = ref.slice(0, colon);
36826
36841
  const key = ref.slice(colon + 1);
36827
- if (prefix === "mm-fofu") return fetchVault(key, { slug: "mm-fofu" });
36828
36842
  if (prefix.startsWith("_org/")) {
36829
36843
  const provider = prefix.slice("_org/".length);
36830
36844
  return fetchVault(`${provider}/${key}`, { slug: "_org" });
@@ -42014,7 +42028,7 @@ function formatBoxTable(boxes) {
42014
42028
  }
42015
42029
  return lines2.join("\n");
42016
42030
  }
42017
- function formatSshRecipe(box) {
42031
+ function formatSshRecipe(box, remoteCommand) {
42018
42032
  if (!box.address) {
42019
42033
  return `box ${box.name} has no public IPv4 \u2014 it is not directly reachable over ssh.`;
42020
42034
  }
@@ -42028,13 +42042,21 @@ function formatSshRecipe(box) {
42028
42042
  " --with-decryption --query Parameter.Value --output text --region eu-central-1 \\",
42029
42043
  ` | tr -d '\\r' > "$kf"`,
42030
42044
  'test -s "$kf" # never hand ssh an empty key file',
42031
- `ssh -i "$kf" -o StrictHostKeyChecking=accept-new root@${box.address} 'hostname; uptime'`
42045
+ `ssh -i "$kf" -o StrictHostKeyChecking=accept-new root@${box.address} ${shellSingleQuote(sshRemoteCommand(remoteCommand))}`
42032
42046
  ].join("\n");
42033
42047
  }
42048
+ var SSH_DEFAULT_PROBE = "hostname; uptime";
42049
+ function sshRemoteCommand(remoteCommand) {
42050
+ const trimmed = remoteCommand?.trim();
42051
+ return trimmed ? trimmed : SSH_DEFAULT_PROBE;
42052
+ }
42053
+ function shellSingleQuote(value) {
42054
+ return `'${value.replace(/'/g, `'\\''`)}'`;
42055
+ }
42034
42056
  var SSH_RECIPE_AGENT_NOTE = "# agent callers: this is multi-line, so running it inline is denied (#1473/#2125).\n# Use `mmi-cli devops runtime box get <name> --ssh --script <path>` to write it to a file, then `bash <path>`.";
42035
- function sshRecipeScript(box) {
42057
+ function sshRecipeScript(box, remoteCommand) {
42036
42058
  return `#!/usr/bin/env bash
42037
- ${formatSshRecipe(box)}
42059
+ ${formatSshRecipe(box, remoteCommand)}
42038
42060
  `;
42039
42061
  }
42040
42062
 
@@ -42109,7 +42131,7 @@ function registerBoxCommands(program3) {
42109
42131
  await failGraceful(e.message);
42110
42132
  }
42111
42133
  });
42112
- box.command("get <name>").description("one box by name \u2014 its address and ssh key path; --ssh prints a ready-to-run connect recipe").option("--json", "machine-readable output").option("--ssh", "print a copy-paste ssh recipe (materializes the key to a temp file; never echoes it)").option("--script <path>", "with --ssh: write the recipe to <path> and print the one line to run \u2014 the runnable form for agent callers, whose transport denies a multi-line inline body (#3385)").action(async (name, o) => {
42134
+ box.command("get <name> [command...]").description("one box by name \u2014 its address and ssh key path; --ssh prints a ready-to-run connect recipe").option("--json", "machine-readable output").option("--ssh", "print a copy-paste ssh recipe (materializes the key to a temp file; never echoes it)").option("--script <path>", "with --ssh: write the recipe to <path> and print the one line to run \u2014 the runnable form for agent callers, whose transport denies a multi-line inline body (#3385)").action(async (name, command, o) => {
42113
42135
  try {
42114
42136
  const { boxes, incomplete } = await fetchInventory();
42115
42137
  const result = lookupBox(name, boxes, incomplete);
@@ -42129,8 +42151,9 @@ function registerBoxCommands(program3) {
42129
42151
  await failGraceful("runtime box get: --script requires --ssh (it writes the ssh connect recipe)");
42130
42152
  return;
42131
42153
  }
42154
+ const remote = command && command.length ? command.join(" ") : void 0;
42132
42155
  const wroteScript = o.ssh && o.script ? o.script : null;
42133
- if (wroteScript) (0, import_node_fs42.writeFileSync)(wroteScript, sshRecipeScript(found), "utf8");
42156
+ if (wroteScript) (0, import_node_fs42.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
42134
42157
  if (o.json) {
42135
42158
  console.log(JSON.stringify({
42136
42159
  box: found,
@@ -42138,7 +42161,7 @@ function registerBoxCommands(program3) {
42138
42161
  ...wroteScript ? { script: { path: wroteScript, run: `bash "${wroteScript}"` } } : {}
42139
42162
  }, null, 2));
42140
42163
  } else if (wroteScript) console.log(`wrote ${wroteScript} \u2014 run: bash "${wroteScript}"`);
42141
- else if (o.ssh) console.log(`${formatSshRecipe(found)}
42164
+ else if (o.ssh) console.log(`${formatSshRecipe(found, remote)}
42142
42165
  ${SSH_RECIPE_AGENT_NOTE}`);
42143
42166
  else console.log(formatBoxTable([found]));
42144
42167
  if (!o.json) warnIncomplete2(incomplete);
@@ -44615,7 +44638,8 @@ function renderReleaseSupersedeDeploy(r) {
44615
44638
  return `mmi-cli devops release --supersede-deploy --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}`;
44616
44639
  }
44617
44640
  function renderReleaseSupersedePublish(r) {
44618
- return `mmi-cli devops release --supersede-publish --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}`;
44641
+ const base = `mmi-cli devops release --supersede-publish --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}`;
44642
+ return r.gatewayDeploy ? `${base}; the deferred Jerv Gateway operator-host deploy leg was run and verified during the supersession (jerv-gateway=complete): ${r.gatewayDeploy.note}` : base;
44619
44643
  }
44620
44644
  function renderReleasePublishRetry(r) {
44621
44645
  return `mmi-cli devops release --retry-publish: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}; ${r.runUrl}`;
@@ -44847,7 +44871,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
44847
44871
  { flags: "--dev", description: "full-track repos release development -> main directly, skipping rc (refuses if rc carries content not in development; no-op on direct-track repos) (#1062)" },
44848
44872
  { flags: "--abort", description: "with --apply, delete only a proven unpublished failed release candidate on any registered deploy model and restore local main for a same-version recut (#3944/#5650/#5657)" },
44849
44873
  { flags: "--supersede-deploy", description: "with --apply, master-approved closure of a content-caused failed deploy leg on a PUBLISHED release whose immutable tag can never deploy green \u2014 the next patch train then carries the fix (#6553)" },
44850
- { flags: "--supersede-publish", description: "with --apply, master-approved closure of a content-caused failed publish leg on a fully PROMOTED release whose immutable tag can never publish green \u2014 the next patch train then carries the fix (#6638)" },
44874
+ { flags: "--supersede-publish", description: "with --apply, master-approved closure of a content-caused failed publish leg on a fully PROMOTED release whose immutable tag can never publish green \u2014 a deferred Jerv-Hub operator-host Gateway deploy leg is run and verified as part of the closure \u2014 the next patch train then carries the fix (#6638/#6742)" },
44851
44875
  { flags: "--retry-publish <run-id>", description: "with --apply, retry one proven failed or cancelled Hub publish release run (#3949/#5797)" }
44852
44876
  ];
44853
44877
  for (const f of RELEASE_ONLY_FLAGS) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.3.68",
3
+ "version": "4.4.1",
4
4
  "description": "MMI Future CLI — the org dev toolbox and shared cross-IDE engine for every registry-declared MMI coding surface.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",