@mutmutco/cli 4.3.67 → 4.4.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 +323 -128
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -1741,8 +1741,8 @@ var Help = class {
1741
1741
  * @param {string} str
1742
1742
  * @returns {number}
1743
1743
  */
1744
- displayWidth(str) {
1745
- return (0, import_node_util.stripVTControlCharacters)(str).length;
1744
+ displayWidth(str2) {
1745
+ return (0, import_node_util.stripVTControlCharacters)(str2).length;
1746
1746
  }
1747
1747
  /**
1748
1748
  * Style the title for displaying in the help. Called with 'Usage:', 'Options:', etc.
@@ -1750,11 +1750,11 @@ var Help = class {
1750
1750
  * @param {string} str
1751
1751
  * @returns {string}
1752
1752
  */
1753
- styleTitle(str) {
1754
- return str;
1753
+ styleTitle(str2) {
1754
+ return str2;
1755
1755
  }
1756
- styleUsage(str) {
1757
- return str.split(" ").map((word) => {
1756
+ styleUsage(str2) {
1757
+ return str2.split(" ").map((word) => {
1758
1758
  if (word === "[options]") return this.styleOptionText(word);
1759
1759
  if (word === "[command]") return this.styleSubcommandText(word);
1760
1760
  if (word[0] === "[" || word[0] === "<")
@@ -1762,46 +1762,46 @@ var Help = class {
1762
1762
  return this.styleCommandText(word);
1763
1763
  }).join(" ");
1764
1764
  }
1765
- styleCommandDescription(str) {
1766
- return this.styleDescriptionText(str);
1765
+ styleCommandDescription(str2) {
1766
+ return this.styleDescriptionText(str2);
1767
1767
  }
1768
- styleOptionDescription(str) {
1769
- return this.styleDescriptionText(str);
1768
+ styleOptionDescription(str2) {
1769
+ return this.styleDescriptionText(str2);
1770
1770
  }
1771
- styleSubcommandDescription(str) {
1772
- return this.styleDescriptionText(str);
1771
+ styleSubcommandDescription(str2) {
1772
+ return this.styleDescriptionText(str2);
1773
1773
  }
1774
- styleArgumentDescription(str) {
1775
- return this.styleDescriptionText(str);
1774
+ styleArgumentDescription(str2) {
1775
+ return this.styleDescriptionText(str2);
1776
1776
  }
1777
- styleDescriptionText(str) {
1778
- return str;
1777
+ styleDescriptionText(str2) {
1778
+ return str2;
1779
1779
  }
1780
- styleOptionTerm(str) {
1781
- return this.styleOptionText(str);
1780
+ styleOptionTerm(str2) {
1781
+ return this.styleOptionText(str2);
1782
1782
  }
1783
- styleSubcommandTerm(str) {
1784
- return str.split(" ").map((word) => {
1783
+ styleSubcommandTerm(str2) {
1784
+ return str2.split(" ").map((word) => {
1785
1785
  if (word === "[options]") return this.styleOptionText(word);
1786
1786
  if (word[0] === "[" || word[0] === "<")
1787
1787
  return this.styleArgumentText(word);
1788
1788
  return this.styleSubcommandText(word);
1789
1789
  }).join(" ");
1790
1790
  }
1791
- styleArgumentTerm(str) {
1792
- return this.styleArgumentText(str);
1791
+ styleArgumentTerm(str2) {
1792
+ return this.styleArgumentText(str2);
1793
1793
  }
1794
- styleOptionText(str) {
1795
- return str;
1794
+ styleOptionText(str2) {
1795
+ return str2;
1796
1796
  }
1797
- styleArgumentText(str) {
1798
- return str;
1797
+ styleArgumentText(str2) {
1798
+ return str2;
1799
1799
  }
1800
- styleSubcommandText(str) {
1801
- return str;
1800
+ styleSubcommandText(str2) {
1801
+ return str2;
1802
1802
  }
1803
- styleCommandText(str) {
1804
- return str;
1803
+ styleCommandText(str2) {
1804
+ return str2;
1805
1805
  }
1806
1806
  /**
1807
1807
  * Calculate the pad width from the maximum term length.
@@ -1824,8 +1824,8 @@ var Help = class {
1824
1824
  * @param {string} str
1825
1825
  * @returns {boolean}
1826
1826
  */
1827
- preformatted(str) {
1828
- return /\n[^\S\r\n]/.test(str);
1827
+ preformatted(str2) {
1828
+ return /\n[^\S\r\n]/.test(str2);
1829
1829
  }
1830
1830
  /**
1831
1831
  * Format the "item", which consists of a term and description. Pad the term and wrap the description, indenting the following lines.
@@ -1871,9 +1871,9 @@ ${itemIndentStr}`);
1871
1871
  * @param {number} width
1872
1872
  * @returns {string}
1873
1873
  */
1874
- boxWrap(str, width) {
1875
- if (width < this.minWidthToWrap) return str;
1876
- const rawLines = str.split(/\r\n|\n/);
1874
+ boxWrap(str2, width) {
1875
+ if (width < this.minWidthToWrap) return str2;
1876
+ const rawLines = str2.split(/\r\n|\n/);
1877
1877
  const chunkPattern = /[\s]*[^\s]+/g;
1878
1878
  const wrappedLines = [];
1879
1879
  rawLines.forEach((line) => {
@@ -2163,9 +2163,9 @@ var DualOptions = class {
2163
2163
  return option.negate === (negativeValue === value);
2164
2164
  }
2165
2165
  };
2166
- function camelcase(str) {
2167
- return str.split("-").reduce((str2, word) => {
2168
- return str2 + word[0].toUpperCase() + word.slice(1);
2166
+ function camelcase(str2) {
2167
+ return str2.split("-").reduce((str3, word) => {
2168
+ return str3 + word[0].toUpperCase() + word.slice(1);
2169
2169
  });
2170
2170
  }
2171
2171
  function splitOptionFlags(flags) {
@@ -2325,14 +2325,14 @@ var Command = class _Command extends import_node_events.EventEmitter {
2325
2325
  this._showSuggestionAfterError = true;
2326
2326
  this._savedState = null;
2327
2327
  this._outputConfiguration = {
2328
- writeOut: (str) => import_node_process.default.stdout.write(str),
2329
- writeErr: (str) => import_node_process.default.stderr.write(str),
2330
- outputError: (str, write) => write(str),
2328
+ writeOut: (str2) => import_node_process.default.stdout.write(str2),
2329
+ writeErr: (str2) => import_node_process.default.stderr.write(str2),
2330
+ outputError: (str2, write) => write(str2),
2331
2331
  getOutHelpWidth: () => import_node_process.default.stdout.isTTY ? import_node_process.default.stdout.columns : void 0,
2332
2332
  getErrHelpWidth: () => import_node_process.default.stderr.isTTY ? import_node_process.default.stderr.columns : void 0,
2333
2333
  getOutHasColors: () => useColor() ?? (import_node_process.default.stdout.isTTY && import_node_process.default.stdout.hasColors?.()),
2334
2334
  getErrHasColors: () => useColor() ?? (import_node_process.default.stderr.isTTY && import_node_process.default.stderr.hasColors?.()),
2335
- stripColor: (str) => (0, import_node_util2.stripVTControlCharacters)(str)
2335
+ stripColor: (str2) => (0, import_node_util2.stripVTControlCharacters)(str2)
2336
2336
  };
2337
2337
  this._hidden = false;
2338
2338
  this._helpOption = void 0;
@@ -4060,18 +4060,18 @@ Expecting one of '${allowedValues.join("', '")}'`);
4060
4060
  * @param {string} [description]
4061
4061
  * @return {(this | string | undefined)} `this` command for chaining, or version string if no arguments
4062
4062
  */
4063
- version(str, flags, description) {
4064
- if (str === void 0) return this._version;
4065
- this._version = str;
4063
+ version(str2, flags, description) {
4064
+ if (str2 === void 0) return this._version;
4065
+ this._version = str2;
4066
4066
  flags = flags || "-V, --version";
4067
4067
  description = description || "output the version number";
4068
4068
  const versionOption = this.createOption(flags, description);
4069
4069
  this._versionOptionName = versionOption.attributeName();
4070
4070
  this._registerOption(versionOption);
4071
4071
  this.on("option:" + versionOption.name(), () => {
4072
- this._outputConfiguration.writeOut(`${str}
4072
+ this._outputConfiguration.writeOut(`${str2}
4073
4073
  `);
4074
- this._exit(0, "commander.version", str);
4074
+ this._exit(0, "commander.version", str2);
4075
4075
  });
4076
4076
  return this;
4077
4077
  }
@@ -4082,10 +4082,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
4082
4082
  * @param {object} [argsDescription]
4083
4083
  * @return {(string|Command)}
4084
4084
  */
4085
- description(str, argsDescription) {
4086
- if (str === void 0 && argsDescription === void 0)
4085
+ description(str2, argsDescription) {
4086
+ if (str2 === void 0 && argsDescription === void 0)
4087
4087
  return this._description;
4088
- this._description = str;
4088
+ this._description = str2;
4089
4089
  if (argsDescription) {
4090
4090
  this._argsDescription = argsDescription;
4091
4091
  }
@@ -4097,9 +4097,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
4097
4097
  * @param {string} [str]
4098
4098
  * @return {(string|Command)}
4099
4099
  */
4100
- summary(str) {
4101
- if (str === void 0) return this._summary;
4102
- this._summary = str;
4100
+ summary(str2) {
4101
+ if (str2 === void 0) return this._summary;
4102
+ this._summary = str2;
4103
4103
  return this;
4104
4104
  }
4105
4105
  /**
@@ -4147,8 +4147,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
4147
4147
  * @param {string} [str]
4148
4148
  * @return {(string|Command)}
4149
4149
  */
4150
- usage(str) {
4151
- if (str === void 0) {
4150
+ usage(str2) {
4151
+ if (str2 === void 0) {
4152
4152
  if (this._usage) return this._usage;
4153
4153
  const args = this.registeredArguments.map((arg) => {
4154
4154
  return humanReadableArgName(arg);
@@ -4159,7 +4159,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
4159
4159
  this.registeredArguments.length ? args : []
4160
4160
  ).join(" ");
4161
4161
  }
4162
- this._usage = str;
4162
+ this._usage = str2;
4163
4163
  return this;
4164
4164
  }
4165
4165
  /**
@@ -4168,9 +4168,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
4168
4168
  * @param {string} [str]
4169
4169
  * @return {(string|Command)}
4170
4170
  */
4171
- name(str) {
4172
- if (str === void 0) return this._name;
4173
- this._name = str;
4171
+ name(str2) {
4172
+ if (str2 === void 0) return this._name;
4173
+ this._name = str2;
4174
4174
  return this;
4175
4175
  }
4176
4176
  /**
@@ -4304,17 +4304,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
4304
4304
  let hasColors;
4305
4305
  let helpWidth;
4306
4306
  if (error) {
4307
- baseWrite = (str) => this._outputConfiguration.writeErr(str);
4307
+ baseWrite = (str2) => this._outputConfiguration.writeErr(str2);
4308
4308
  hasColors = this._outputConfiguration.getErrHasColors();
4309
4309
  helpWidth = this._outputConfiguration.getErrHelpWidth();
4310
4310
  } else {
4311
- baseWrite = (str) => this._outputConfiguration.writeOut(str);
4311
+ baseWrite = (str2) => this._outputConfiguration.writeOut(str2);
4312
4312
  hasColors = this._outputConfiguration.getOutHasColors();
4313
4313
  helpWidth = this._outputConfiguration.getOutHelpWidth();
4314
4314
  }
4315
- const write = (str) => {
4316
- if (!hasColors) str = this._outputConfiguration.stripColor(str);
4317
- return baseWrite(str);
4315
+ const write = (str2) => {
4316
+ if (!hasColors) str2 = this._outputConfiguration.stripColor(str2);
4317
+ return baseWrite(str2);
4318
4318
  };
4319
4319
  return { error, write, hasColors, helpWidth };
4320
4320
  }
@@ -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
@@ -7004,7 +7004,7 @@ init_github_client();
7004
7004
 
7005
7005
  // src/project-model.ts
7006
7006
  var PROJECT_TYPES = ["web-app", "hub-service", "content", "desktop-app", "desktop-game", "mobile-app", "non-deployable", "cli-tool", "worker"];
7007
- var DEPLOY_MODELS = ["hub-serverless", "serverless", "tenant-container", "solo-container", "static-cdn", "registry-publish", "content", "none"];
7007
+ var DEPLOY_MODELS = ["hub-serverless", "serverless", "tenant-container", "solo-container", "static-cdn", "registry-publish", "content", "none", "vercel"];
7008
7008
  var RELEASE_TRACKS = ["full", "direct", "trunk"];
7009
7009
  var REQUIRED_CHECK_BRANCHES = ["development", "rc", "main"];
7010
7010
  var PROJECT_TYPE_SET = new Set(PROJECT_TYPES);
@@ -9209,6 +9209,66 @@ async function secretsCopy(deps, opts) {
9209
9209
  }
9210
9210
  return true;
9211
9211
  }
9212
+ function transferKey(leaf, stage) {
9213
+ return stage ? `${stage}/${leaf}` : leaf;
9214
+ }
9215
+ async function secretsTransfer(deps, opts) {
9216
+ const fromRepo = opts.fromRepo.trim();
9217
+ const toRepo = opts.toRepo.trim();
9218
+ if (!fromRepo || !toRepo) {
9219
+ deps.err("secrets transfer: --from-repo and --to-repo are required (owner/repo)");
9220
+ return false;
9221
+ }
9222
+ if (fromRepo.toLowerCase() === toRepo.toLowerCase() && opts.fromStage === opts.toStage) {
9223
+ deps.err("secrets transfer: source and destination are the same coordinate \u2014 use `secrets copy` to move between stages of one project");
9224
+ return false;
9225
+ }
9226
+ const report = await probeCapabilities(deps, fromRepo);
9227
+ if (report?.role !== "master") {
9228
+ deps.err(report === void 0 ? "secrets transfer: could not verify your role (capabilities probe unavailable) \u2014 cross-project transfer is master-only and refuses on an unverified role" : `secrets transfer: master-admin only \u2014 your role on ${fromRepo} is ${report.role}. Cross-project value movement is master-gated; ask the master to run the transfer.`);
9229
+ return false;
9230
+ }
9231
+ const keys = [...new Set(opts.keys.flatMap((k) => k.split(/[,\s]+/)).filter(Boolean))];
9232
+ if (!keys.length) {
9233
+ deps.err("secrets transfer: --keys required (comma- or space-separated allowlist)");
9234
+ return false;
9235
+ }
9236
+ for (const key of keys) {
9237
+ if (!isValidSecretKey(key)) {
9238
+ deps.err(`invalid secret key ${JSON.stringify(key)}`);
9239
+ return false;
9240
+ }
9241
+ if (isSecretCopyBlocked(key)) {
9242
+ deps.err(`secrets transfer: ${key} is not transferable \u2014 generate encryption/stage-distinct keys per project`);
9243
+ return false;
9244
+ }
9245
+ }
9246
+ for (const key of keys) {
9247
+ const leaf = secretLeafName(key);
9248
+ const srcKey = transferKey(leaf, opts.fromStage);
9249
+ const dstKey = transferKey(leaf, opts.toStage);
9250
+ const srcPath = `${fromRepo} ${srcKey}`;
9251
+ const dstPath = `${toRepo} ${dstKey}`;
9252
+ if (opts.dryRun) {
9253
+ const probe = await fetchSecretValue(deps, srcKey, { repo: fromRepo });
9254
+ if (!probe) {
9255
+ deps.err(`secrets transfer: could not read source ${srcPath}`);
9256
+ return false;
9257
+ }
9258
+ deps.log(`would transfer ${srcPath} \u2192 ${dstPath}`);
9259
+ continue;
9260
+ }
9261
+ const value = await fetchSecretValue(deps, srcKey, { repo: fromRepo });
9262
+ if (!value) {
9263
+ deps.err(`secrets transfer: could not read source ${srcPath}`);
9264
+ return false;
9265
+ }
9266
+ const ok = await putSecret(deps, dstKey, value, { repo: toRepo });
9267
+ if (!ok) return false;
9268
+ deps.log(`transferred ${srcPath} \u2192 ${dstPath}`);
9269
+ }
9270
+ return true;
9271
+ }
9212
9272
  var CMD_METACHAR_RE = /[&|^%<>()!"]/;
9213
9273
  var CMD_METACHAR_G = /[&|^%<>()!"]/g;
9214
9274
  function argvQuote(token, force) {
@@ -9379,6 +9439,18 @@ function resolveUseEnvNames(keys, name) {
9379
9439
  }
9380
9440
  return { ok: true, entries };
9381
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
+ }
9382
9454
  var PRE_SPAWN_DRAIN_TIMEOUT_MS = 2e3;
9383
9455
  async function drainHttpPoolBeforeSpawn() {
9384
9456
  const drained = await Promise.race([
@@ -9430,6 +9502,8 @@ async function secretsUse(deps, keys, opts) {
9430
9502
  deps.err(named.error);
9431
9503
  return false;
9432
9504
  }
9505
+ const dollarHint = secretsUseDollarHint(command, named.entries.map((e) => e.envName));
9506
+ if (dollarHint) deps.err(dollarHint);
9433
9507
  const repo = await targetRepo(deps, opts);
9434
9508
  const slug = opts.slug?.toLowerCase();
9435
9509
  const env = { ...process.env };
@@ -13772,7 +13846,7 @@ function walk(dir, root, out) {
13772
13846
  return out;
13773
13847
  }
13774
13848
  function defaultListDocs(root) {
13775
- const docs = ["docs", "skills/org-guide", "skills/mmi-help", "skills/brand"].flatMap((path2) => {
13849
+ const docs = ["docs", "skills/org-guide", "skills/mmi-help"].flatMap((path2) => {
13776
13850
  const directory = (0, import_node_path12.join)(root, path2);
13777
13851
  return (0, import_node_fs14.existsSync)(directory) ? walk(directory, root, []) : [];
13778
13852
  }).filter(
@@ -14178,9 +14252,6 @@ function expectedHosts(cfg) {
14178
14252
  for (const env of ENV_PREFIXES) out.push(env ? `${env}.${base}` : base);
14179
14253
  }
14180
14254
  }
14181
- if (cfg.fofuSubdomain !== void 0) {
14182
- out.push(cfg.fofuSubdomain ? `${cfg.fofuSubdomain}.fofu.ai` : "fofu.ai");
14183
- }
14184
14255
  return uniq([...out, ...cfg.edgeHosts ?? []]);
14185
14256
  }
14186
14257
  function expectedJsOrigins(cfg) {
@@ -14245,10 +14316,7 @@ function parseOauthConfig(mmiConfig, slug) {
14245
14316
  );
14246
14317
  }
14247
14318
  const extraCallbackPaths = parseExtraCallbackPaths(raw.extraCallbackPaths);
14248
- const meta = mmiConfig ?? {};
14249
- const rawFofuSub = raw.fofuSubdomain;
14250
- const fofuSubdomain = meta.fofuEnabled === true ? typeof rawFofuSub === "string" ? rawFofuSub : defaultSubdomain(slug) : void 0;
14251
- return { subdomains, domains, callbackPath, extraCallbackPaths, fofuSubdomain, edgeHosts: Object.values(edgeDomainsByStage(mmiConfig)) };
14319
+ return { subdomains, domains, callbackPath, extraCallbackPaths, edgeHosts: Object.values(edgeDomainsByStage(mmiConfig)) };
14252
14320
  }
14253
14321
  function probeRedirectUri(callbackPath, port = 9123) {
14254
14322
  return `http://localhost:${port}${callbackPath}`;
@@ -14755,6 +14823,7 @@ function planTrainApplyRepoGuard(applyRepo, cwdRepo, rerun, what = "--apply") {
14755
14823
  };
14756
14824
  }
14757
14825
  async function resolveFoldPaths(deps, model) {
14826
+ if (model === "vercel") return [];
14758
14827
  const helper = "scripts/release-distribution.mjs";
14759
14828
  if (model === "hub-serverless" || model === "registry-publish") {
14760
14829
  if ((0, import_node_fs16.existsSync)(helper)) {
@@ -15801,10 +15870,10 @@ var rollout_plan_default = {
15801
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)."
15802
15871
  },
15803
15872
  baseline: {
15804
- version: "4.3.67",
15805
- tag: "v4.3.67",
15806
- commit: "61dc785d3abb",
15807
- npm: "@mutmutco/cli@4.3.67"
15873
+ version: "4.4.0",
15874
+ tag: "v4.4.0",
15875
+ commit: "8e1fae5ff484",
15876
+ npm: "@mutmutco/cli@4.4.0"
15808
15877
  },
15809
15878
  exitCriterion: "fleet-n-of-n",
15810
15879
  hubOnlyShortcut: "forbidden",
@@ -15821,14 +15890,14 @@ var rollout_plan_default = {
15821
15890
  repo: "mutmutco/mmi-hub",
15822
15891
  role: "canary",
15823
15892
  schedule: "train",
15824
- v3Target: "v4.3.67"
15893
+ v3Target: "v4.4.0"
15825
15894
  }
15826
15895
  ],
15827
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.",
15828
15897
  rollback: {
15829
15898
  independent: true,
15830
- mechanism: "npm dist-tag latest -> 4.3.67 and redeploy the Hub Lambda from tag v4.3.67 (61dc785d3abb); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15831
- v3Target: "v4.3.67 (@mutmutco/cli@4.3.67, tag commit 61dc785d3abb \u2014 last known-good release carrying the repo-index v4-only contract)"
15899
+ mechanism: "npm dist-tag latest -> 4.4.0 and redeploy the Hub Lambda from tag v4.4.0 (8e1fae5ff484); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15900
+ v3Target: "v4.4.0 (@mutmutco/cli@4.4.0, tag commit 8e1fae5ff484 \u2014 last known-good release carrying the repo-index v4-only contract)"
15832
15901
  }
15833
15902
  },
15834
15903
  {
@@ -15925,12 +15994,6 @@ var rollout_plan_default = {
15925
15994
  schedule: "train",
15926
15995
  v3Target: "v0.3.0"
15927
15996
  },
15928
- {
15929
- repo: "mutmutco/mm-fofu",
15930
- role: "member",
15931
- schedule: "train",
15932
- v3Target: "v0.1.0"
15933
- },
15934
15997
  {
15935
15998
  repo: "mutmutco/mmi-junior",
15936
15999
  role: "member",
@@ -18670,6 +18733,56 @@ function isReleaseShaDeployEnumerationCandidate(row, nonDeployEvents) {
18670
18733
  return true;
18671
18734
  }
18672
18735
 
18736
+ // src/vercel-deployments.ts
18737
+ var TERMINAL = /* @__PURE__ */ new Set(["success", "failure", "error", "inactive"]);
18738
+ function isTerminalVercelState(state) {
18739
+ return TERMINAL.has(state);
18740
+ }
18741
+ function str(v) {
18742
+ return typeof v === "string" && v.length > 0 ? v : void 0;
18743
+ }
18744
+ async function readVercelDeployment(api, repo, target, environment) {
18745
+ const query = target.sha ? `sha=${encodeURIComponent(target.sha)}` : `ref=${encodeURIComponent(target.ref ?? "")}`;
18746
+ const listed = await api(`repos/${repo}/deployments?${query}&per_page=30`);
18747
+ const rows = Array.isArray(listed) ? listed : [];
18748
+ const prefix = environment.toLowerCase();
18749
+ const hit = rows.find((d) => typeof d.id === "number" && (str(d.environment) ?? "").toLowerCase().startsWith(prefix));
18750
+ if (!hit) return void 0;
18751
+ const statuses = await api(`repos/${repo}/deployments/${hit.id}/statuses?per_page=1`);
18752
+ const latest = Array.isArray(statuses) ? statuses[0] : void 0;
18753
+ const state = str(latest?.state) ?? "none";
18754
+ const url = str(latest?.environment_url) ?? str(latest?.target_url);
18755
+ return {
18756
+ id: hit.id,
18757
+ environment: str(hit.environment) ?? environment,
18758
+ sha: str(hit.sha) ?? target.sha ?? "",
18759
+ ref: str(hit.ref) ?? target.ref ?? "",
18760
+ state,
18761
+ ...url ? { url } : {},
18762
+ ...str(latest?.log_url) ? { logUrl: str(latest?.log_url) } : {}
18763
+ };
18764
+ }
18765
+ var VERCEL_WATCH_ATTEMPTS = 120;
18766
+ var VERCEL_WATCH_DELAY_MS = 1e4;
18767
+ async function watchVercelProduction(api, repo, sha, sleep2, onTick) {
18768
+ let last;
18769
+ for (let attempt = 0; attempt < VERCEL_WATCH_ATTEMPTS; attempt++) {
18770
+ if (attempt > 0) await sleep2(VERCEL_WATCH_DELAY_MS);
18771
+ last = await readVercelDeployment(api, repo, { sha }, "Production");
18772
+ onTick?.(attempt, last);
18773
+ if (last && isTerminalVercelState(last.state)) return last;
18774
+ }
18775
+ return last;
18776
+ }
18777
+ async function probeVercelUrl(url, fetchImpl = fetch) {
18778
+ try {
18779
+ const res = await fetchImpl(url, { method: "GET", redirect: "follow", signal: AbortSignal.timeout(15e3) });
18780
+ return { ok: res.status < 500, status: res.status };
18781
+ } catch (e) {
18782
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
18783
+ }
18784
+ }
18785
+
18673
18786
  // src/train-apply-deploy.ts
18674
18787
  var DISPATCH_ATTEMPTS = 3;
18675
18788
  var DISPATCH_RETRY_DELAY_MS = 2e3;
@@ -18841,7 +18954,45 @@ function prodReleaseTargets(model) {
18841
18954
  return [];
18842
18955
  }
18843
18956
  function trainObservesShipment(model) {
18844
- return isCentralDispatchModel(model) || prodReleaseTargets(model).length > 0;
18957
+ return isCentralDispatchModel(model) || prodReleaseTargets(model).length > 0 || model === "vercel";
18958
+ }
18959
+ async function vercelDeployDispatch(deps, ctx, ref, watch, headSha) {
18960
+ const note = "no dispatch: Vercel deploys main on push (Production) \u2014 observed via the Vercel GitHub app Deployment";
18961
+ if (ref !== "main") return { note: "no dispatch on rc: a vercel repo is direct-track and has no rc stage", deployStatus: "pending" };
18962
+ if (!headSha) return { note, deployStatus: "pending" };
18963
+ const api = async (path2) => JSON.parse(await deps.run("gh", ["api", path2]));
18964
+ const row = (d, conclusion) => ({
18965
+ workflow: "vercel",
18966
+ conclusion,
18967
+ ...d?.logUrl ? { runUrl: d.logUrl } : { runUrlNote: "no Vercel Deployment posted for this SHA yet" },
18968
+ event: "push"
18969
+ });
18970
+ const verdict = (d) => d?.state === "success" ? "success" : d && isTerminalVercelState(d.state) ? "failure" : "pending";
18971
+ let read;
18972
+ try {
18973
+ if (!watch) {
18974
+ deps.warn?.("Release: Checking Vercel deployment status");
18975
+ read = await readVercelDeployment(api, ctx.repo, { sha: headSha }, "Production");
18976
+ } else {
18977
+ deps.warn?.("Release: Waiting for Vercel to deploy production");
18978
+ read = await watchVercelProduction(api, ctx.repo, headSha, resolveSleep(deps), (attempt, d) => {
18979
+ if (attempt > 0 && attempt % 6 === 0) deps.warn?.(`Vercel deployment ${d ? d.state : "not posted yet"} \u2014 ${attempt / 6}m elapsed`);
18980
+ });
18981
+ }
18982
+ } catch (e) {
18983
+ return { note: `${note}; the Deployment read FAILED (${describeReadError(e)}) \u2014 verify on Vercel; never re-tag`, workflowRuns: [row(void 0, "failure")], deployStatus: "failure" };
18984
+ }
18985
+ let deployStatus = verdict(read);
18986
+ let detail = read ? `Vercel Production deployment ${read.state}${read.url ? ` at ${read.url}` : ""}` : "no Vercel Deployment posted for this SHA (is the Vercel GitHub app connected?)";
18987
+ if (watch && deployStatus === "success" && read?.url) {
18988
+ const probe = await (deps.probeUrl ?? probeVercelUrl)(read.url);
18989
+ if (probe.ok) detail = `${detail}; URL answered HTTP ${probe.status}`;
18990
+ else {
18991
+ deployStatus = "failure";
18992
+ detail = `${detail}; URL probe FAILED (${probe.error ?? `HTTP ${probe.status}`})`;
18993
+ }
18994
+ }
18995
+ return { note: `${note}; ${detail}`, ...read?.logUrl ? { runUrl: read.logUrl } : {}, workflowRuns: [row(read, deployStatus)], deployStatus };
18845
18996
  }
18846
18997
  function shipLegsGreen(deploy, publish) {
18847
18998
  const green = (s) => s === "complete" || s === "skipped";
@@ -19023,6 +19174,7 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
19023
19174
  deployStatus: aggregateWorkflowRuns(workflowRuns)
19024
19175
  };
19025
19176
  }
19177
+ if (model === "vercel") return vercelDeployDispatch(deps, ctx, ref, watch, autoRunHeadSha);
19026
19178
  return { note: `no manual dispatch: ${model} repo deploys via its own push-triggered workflow`, deployStatus: "pending" };
19027
19179
  }
19028
19180
  async function readEdgeDomains(deps, slug) {
@@ -29517,12 +29669,12 @@ function registerDeployCommands(program3) {
29517
29669
  init_cli_shared();
29518
29670
 
29519
29671
  // src/project-set.ts
29520
- var UNSET_KEYS = ["oauth", "requiredRuntimeSecrets", "requiredBuildSecrets", "tenantTasks", "secrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "releaseChannel", "releaseLanguage", "dsManifestPath", "fofuEnabled", "consumesDesignSystem", "ci", "requiredChecks", "requiredCheckBranches", "ciExemptReason", "gate", "seedCanary"];
29672
+ var UNSET_KEYS = ["oauth", "requiredRuntimeSecrets", "requiredBuildSecrets", "tenantTasks", "secrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "releaseChannel", "releaseLanguage", "dsManifestPath", "consumesDesignSystem", "ci", "requiredChecks", "requiredCheckBranches", "ciExemptReason", "gate", "seedCanary"];
29521
29673
  var UNSET_KEY_SET = new Set(UNSET_KEYS);
29522
29674
  var RUNTIME_SECRET_STAGES = ["dev", "rc", "main"];
29523
29675
  var SECRET_CONSUMERS = ["runtime", "build", "lambda", "actions", "agent", "box"];
29524
29676
  var SECRET_ENV_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
29525
- 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)$/;
29677
+ 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)$/;
29526
29678
  function previewRegistryMetaMerge(existing, patch) {
29527
29679
  const out = { ...existing ?? {} };
29528
29680
  for (const [key, value] of Object.entries(patch)) {
@@ -29718,7 +29870,7 @@ function parseOauthVar(raw) {
29718
29870
  throw new Error(`org project set: oauth must be JSON, e.g. {"subdomains":["app"],"domains":["example.co"],"callbackPath":"${DEFAULT_CALLBACK_PATH}"}`);
29719
29871
  }
29720
29872
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
29721
- throw new Error("org project set: oauth must be a {subdomains,domains,callbackPath,extraCallbackPaths,fofuSubdomain} object");
29873
+ throw new Error("org project set: oauth must be a {subdomains,domains,callbackPath,extraCallbackPaths} object");
29722
29874
  }
29723
29875
  const map = parsed;
29724
29876
  const out = {};
@@ -29746,11 +29898,8 @@ function parseOauthVar(raw) {
29746
29898
  } catch (e) {
29747
29899
  throw new Error(`org project set: ${e.message}`);
29748
29900
  }
29749
- } else if (key === "fofuSubdomain") {
29750
- if (typeof value !== "string") throw new Error('org project set: oauth.fofuSubdomain must be a string ("" selects the apex fofu.ai)');
29751
- out.fofuSubdomain = value.trim();
29752
29901
  } else {
29753
- throw new Error(`org project set: oauth key "${key}" \u2014 expected only subdomains/domains/callbackPath/extraCallbackPaths/fofuSubdomain`);
29902
+ throw new Error(`org project set: oauth key "${key}" \u2014 expected only subdomains/domains/callbackPath/extraCallbackPaths`);
29754
29903
  }
29755
29904
  }
29756
29905
  return out;
@@ -29772,11 +29921,6 @@ function parsePublishRequiredVar(raw) {
29772
29921
  if (raw === "false") return false;
29773
29922
  throw new Error("org project set: publishRequired must be true or false");
29774
29923
  }
29775
- function parseFofuEnabledVar(raw) {
29776
- if (raw === "true") return true;
29777
- if (raw === "false") return false;
29778
- throw new Error("org project set: fofuEnabled must be true or false");
29779
- }
29780
29924
  function parseRuntimeVaultOnlyVar(raw) {
29781
29925
  if (raw === "true") return true;
29782
29926
  if (raw === "false") return false;
@@ -29934,7 +30078,6 @@ var SETTABLE_VAR_KEYS = [
29934
30078
  "releaseChannel",
29935
30079
  "releaseLanguage",
29936
30080
  "dsManifestPath",
29937
- "fofuEnabled",
29938
30081
  "consumesDesignSystem",
29939
30082
  "runtimeVaultOnly",
29940
30083
  "requiredGcpApis",
@@ -29963,12 +30106,11 @@ var SETTABLE_VAR_HINTS = {
29963
30106
  releaseChannel: "Slack channel id, e.g. C0BE0BRGAT0",
29964
30107
  releaseLanguage: "2-letter language code for release notes, e.g. tr (default en)",
29965
30108
  dsManifestPath: "relative path to a package.json, e.g. web/package.json",
29966
- fofuEnabled: "true|false",
29967
30109
  consumesDesignSystem: '"fofu"',
29968
30110
  runtimeVaultOnly: "true|false",
29969
30111
  seedCanary: "true|false",
29970
30112
  repos: 'JSON array, e.g. ["mutmutco/mm-foo"]',
29971
- oauth: "JSON {subdomains,domains,callbackPath,extraCallbackPaths,fofuSubdomain}",
30113
+ oauth: "JSON {subdomains,domains,callbackPath,extraCallbackPaths}",
29972
30114
  requiredGcpApis: "comma-string",
29973
30115
  requiredRuntimeSecrets: 'JSON stage map, e.g. {"dev":["KEY"],"rc":["KEY"],"main":["KEY"]}',
29974
30116
  requiredBuildSecrets: 'JSON flat array, e.g. ["NODE_AUTH_TOKEN=@github-packages-token"]',
@@ -30064,8 +30206,6 @@ function buildProjectSetPatch(input) {
30064
30206
  patch[key] = parseReposVar(raw);
30065
30207
  } else if (key === "publishRequired") {
30066
30208
  patch[key] = parsePublishRequiredVar(raw);
30067
- } else if (key === "fofuEnabled") {
30068
- patch[key] = parseFofuEnabledVar(raw);
30069
30209
  } else if (key === "runtimeVaultOnly") {
30070
30210
  patch[key] = parseRuntimeVaultOnlyVar(raw);
30071
30211
  } else if (key === "seedCanary") {
@@ -36269,6 +36409,30 @@ function registerSecretsCommands(program3) {
36269
36409
  });
36270
36410
  if (!ok) process.exitCode = 1;
36271
36411
  }));
36412
+ secrets.command("transfer").description("(master-only) copy secret VALUES from one project vault to another \u2014 the succession path `copy` cannot serve (#6687). Values never printed, never in argv; encryption/stage-distinct keys blocked; declare-first enforced on the destination").requiredOption("--from-repo <owner/repo>", "source repo whose vault holds the values").requiredOption("--to-repo <owner/repo>", "destination repo receiving the values").requiredOption("--keys <names>", "comma- or space-separated secret names (encryption keys blocked)").option("--from-stage <stage>", "source tier: dev, rc, or main (omit for the stageless canonical)").option("--to-stage <stage>", "destination tier: dev, rc, or main (omit for the stageless canonical)").option("--dry-run", "report the transfers, and verify each source is readable, without writing").action((o) => withSecrets(async (d) => {
36413
+ const stages = ["dev", "rc", "main"];
36414
+ const parseStage = (v, flag) => {
36415
+ if (v === void 0) return void 0;
36416
+ if (!stages.includes(v)) {
36417
+ fail(`secrets transfer: ${flag} must be dev, rc, or main`);
36418
+ return null;
36419
+ }
36420
+ return v;
36421
+ };
36422
+ const fromStage = parseStage(o.fromStage, "--from-stage");
36423
+ if (fromStage === null) return;
36424
+ const toStage = parseStage(o.toStage, "--to-stage");
36425
+ if (toStage === null) return;
36426
+ const ok = await secretsTransfer(d, {
36427
+ fromRepo: o.fromRepo,
36428
+ toRepo: o.toRepo,
36429
+ keys: o.keys.split(","),
36430
+ ...fromStage !== void 0 ? { fromStage } : {},
36431
+ ...toStage !== void 0 ? { toStage } : {},
36432
+ ...o.dryRun !== void 0 ? { dryRun: o.dryRun } : {}
36433
+ });
36434
+ if (!ok) process.exitCode = 1;
36435
+ }));
36272
36436
  secrets.command("import-rails-credentials").description("decrypt Rails credentials and import explicit mappings into the vault (values never printed)").requiredOption("--stage <dev|rc|main>", "target vault stage").option("--map <credential.path=ENV_KEY>", "explicit credential-to-env mapping; repeat for each key", collectMap, []).option("--app-dir <path>", "Rails app directory (defaults to cwd)").option("--credentials-file <path>", `encrypted credentials path relative to --app-dir (default: ${DEFAULT_RAILS_CREDENTIALS_FILE})`).option("--master-key-file <path>", `master key path relative to --app-dir (default: ${DEFAULT_RAILS_MASTER_KEY_FILE})`).option("--dry-run", "decrypt and show mapped key names without writing values").option("--remove-files", "delete credentials/master key files after every mapped value imports successfully").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action((o) => withSecrets(async (d) => {
36273
36437
  const stages = ["dev", "rc", "main"];
36274
36438
  if (!stages.includes(o.stage)) {
@@ -36578,7 +36742,7 @@ async function runStageLiveDown(deps, t) {
36578
36742
  var GITHUB_PACKAGES_TOKEN_REF = "@github-packages-token";
36579
36743
  var ENV_ID_RE = /^[A-Z_][A-Z0-9_]*$/;
36580
36744
  var BARE_VAULT_KEY_RE = /^[A-Z_][A-Z0-9_]*$/;
36581
- var CROSS_SLUG_REF_RE = /^(?:mm-fofu|_org\/[a-z0-9][a-z0-9-]*):[A-Z_][A-Z0-9_]*$/;
36745
+ var CROSS_SLUG_REF_RE = /^(?:_org\/[a-z0-9][a-z0-9-]*):[A-Z_][A-Z0-9_]*$/;
36582
36746
  function parseStageBuildSecretEntry(entry) {
36583
36747
  const trimmed = entry.trim();
36584
36748
  if (!trimmed) return null;
@@ -36650,7 +36814,6 @@ async function fetchVaultRef(fetchVault, ref) {
36650
36814
  if (colon === -1) return fetchVault(ref);
36651
36815
  const prefix = ref.slice(0, colon);
36652
36816
  const key = ref.slice(colon + 1);
36653
- if (prefix === "mm-fofu") return fetchVault(key, { slug: "mm-fofu" });
36654
36817
  if (prefix.startsWith("_org/")) {
36655
36818
  const provider = prefix.slice("_org/".length);
36656
36819
  return fetchVault(`${provider}/${key}`, { slug: "_org" });
@@ -36699,6 +36862,28 @@ function registerStageCommands(program3) {
36699
36862
  cfg
36700
36863
  };
36701
36864
  }
36865
+ async function stagePreview(project2) {
36866
+ if (project2?.deployModel !== "vercel") return null;
36867
+ const branch = await gitOut(["rev-parse", "--abbrev-ref", "HEAD"]);
36868
+ const repo = Array.isArray(project2.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : await repoSlug();
36869
+ try {
36870
+ const gh = defaultGitHubClient();
36871
+ const deployment = await readVercelDeployment((path2) => gh.rest("GET", path2), repo, { ref: branch }, "Preview");
36872
+ return { branch, ...deployment ? { deployment } : {} };
36873
+ } catch (e) {
36874
+ return { branch, error: e.message };
36875
+ }
36876
+ }
36877
+ function previewLine(preview) {
36878
+ if (!preview) return "";
36879
+ if (preview.error) return `
36880
+ vercel preview (${preview.branch}): read failed \u2014 ${preview.error}`;
36881
+ const d = preview.deployment;
36882
+ if (!d) return `
36883
+ vercel preview (${preview.branch}): no Preview deployment posted yet \u2014 push the branch, or connect the Vercel GitHub app`;
36884
+ return `
36885
+ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}`;
36886
+ }
36702
36887
  async function fetchStageVaultEnvMerge(project2, cfg) {
36703
36888
  if (!cfg.sagaApiUrl || !project2) return void 0;
36704
36889
  const names = requiredRuntimeSecretNames("dev", project2.requiredRuntimeSecrets, { includeGoogleOAuth: false });
@@ -36839,6 +37024,7 @@ function registerStageCommands(program3) {
36839
37024
  if (o.down && !o.live) return fail("stage: --down applies to --live only (local teardown is `mmi-cli stage stop --apply`)");
36840
37025
  if (o.live) return runStageLiveCommand(o);
36841
37026
  const { resolution: res, project: project2, cfg: stageCfg } = await resolveStage();
37027
+ const preview = await stagePreview(project2);
36842
37028
  if (o.apply) {
36843
37029
  if (res.source === "none") return failGraceful(`stage: ${res.gap}`);
36844
37030
  const cfg = res.derived.config;
@@ -36848,7 +37034,7 @@ function registerStageCommands(program3) {
36848
37034
  const result = await runStage(cfg, { ...stageScopedRunOpts({ timeoutMs: o.timeoutMs }), ...vaultOpts });
36849
37035
  const reportUrl = reportedStageUrl(res, result);
36850
37036
  const url = reportUrl ? ` \u2014 ${reportUrl}` : "";
36851
- return printLine(o.json ? JSON.stringify({ ...result, source: res.source, url: reportUrl }) : `mmi-cli stage: ${result.message}${url}`);
37037
+ return printLine(o.json ? JSON.stringify({ ...result, source: res.source, url: reportUrl, ...preview ? { preview } : {} }) : `mmi-cli stage: ${result.message}${url}${previewLine(preview)}`);
36852
37038
  } catch (e) {
36853
37039
  return failGraceful(`stage: ${e.message}`);
36854
37040
  } finally {
@@ -36856,8 +37042,8 @@ function registerStageCommands(program3) {
36856
37042
  }
36857
37043
  }
36858
37044
  const steps = stageStepsFor(res);
36859
- if (o.json) return console.log(JSON.stringify({ command: "stage", ...stageReceiptFields(res), steps }, null, 2));
36860
- console.log(renderSteps("mmi-cli stage: dry-run plan", steps));
37045
+ if (o.json) return console.log(JSON.stringify({ command: "stage", ...stageReceiptFields(res), steps, ...preview ? { preview } : {} }, null, 2));
37046
+ console.log(renderSteps("mmi-cli stage: dry-run plan", steps) + previewLine(preview));
36861
37047
  });
36862
37048
  stage.command("stop").description("stop the previous local stage process recorded in tmp/stage/state.json").option("--json", "machine-readable output").option("--apply", "kill the recorded process tree and remove the state file").action(async () => {
36863
37049
  const o = { json: rawFlag("--json"), apply: rawFlag("--apply") };
@@ -41817,7 +42003,7 @@ function formatBoxTable(boxes) {
41817
42003
  }
41818
42004
  return lines2.join("\n");
41819
42005
  }
41820
- function formatSshRecipe(box) {
42006
+ function formatSshRecipe(box, remoteCommand) {
41821
42007
  if (!box.address) {
41822
42008
  return `box ${box.name} has no public IPv4 \u2014 it is not directly reachable over ssh.`;
41823
42009
  }
@@ -41831,13 +42017,21 @@ function formatSshRecipe(box) {
41831
42017
  " --with-decryption --query Parameter.Value --output text --region eu-central-1 \\",
41832
42018
  ` | tr -d '\\r' > "$kf"`,
41833
42019
  'test -s "$kf" # never hand ssh an empty key file',
41834
- `ssh -i "$kf" -o StrictHostKeyChecking=accept-new root@${box.address} 'hostname; uptime'`
42020
+ `ssh -i "$kf" -o StrictHostKeyChecking=accept-new root@${box.address} ${shellSingleQuote(sshRemoteCommand(remoteCommand))}`
41835
42021
  ].join("\n");
41836
42022
  }
42023
+ var SSH_DEFAULT_PROBE = "hostname; uptime";
42024
+ function sshRemoteCommand(remoteCommand) {
42025
+ const trimmed = remoteCommand?.trim();
42026
+ return trimmed ? trimmed : SSH_DEFAULT_PROBE;
42027
+ }
42028
+ function shellSingleQuote(value) {
42029
+ return `'${value.replace(/'/g, `'\\''`)}'`;
42030
+ }
41837
42031
  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>`.";
41838
- function sshRecipeScript(box) {
42032
+ function sshRecipeScript(box, remoteCommand) {
41839
42033
  return `#!/usr/bin/env bash
41840
- ${formatSshRecipe(box)}
42034
+ ${formatSshRecipe(box, remoteCommand)}
41841
42035
  `;
41842
42036
  }
41843
42037
 
@@ -41912,7 +42106,7 @@ function registerBoxCommands(program3) {
41912
42106
  await failGraceful(e.message);
41913
42107
  }
41914
42108
  });
41915
- 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) => {
42109
+ 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) => {
41916
42110
  try {
41917
42111
  const { boxes, incomplete } = await fetchInventory();
41918
42112
  const result = lookupBox(name, boxes, incomplete);
@@ -41932,8 +42126,9 @@ function registerBoxCommands(program3) {
41932
42126
  await failGraceful("runtime box get: --script requires --ssh (it writes the ssh connect recipe)");
41933
42127
  return;
41934
42128
  }
42129
+ const remote = command && command.length ? command.join(" ") : void 0;
41935
42130
  const wroteScript = o.ssh && o.script ? o.script : null;
41936
- if (wroteScript) (0, import_node_fs42.writeFileSync)(wroteScript, sshRecipeScript(found), "utf8");
42131
+ if (wroteScript) (0, import_node_fs42.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
41937
42132
  if (o.json) {
41938
42133
  console.log(JSON.stringify({
41939
42134
  box: found,
@@ -41941,7 +42136,7 @@ function registerBoxCommands(program3) {
41941
42136
  ...wroteScript ? { script: { path: wroteScript, run: `bash "${wroteScript}"` } } : {}
41942
42137
  }, null, 2));
41943
42138
  } else if (wroteScript) console.log(`wrote ${wroteScript} \u2014 run: bash "${wroteScript}"`);
41944
- else if (o.ssh) console.log(`${formatSshRecipe(found)}
42139
+ else if (o.ssh) console.log(`${formatSshRecipe(found, remote)}
41945
42140
  ${SSH_RECIPE_AGENT_NOTE}`);
41946
42141
  else console.log(formatBoxTable([found]));
41947
42142
  if (!o.json) warnIncomplete2(incomplete);
@@ -45464,17 +45659,17 @@ function excessArgumentCorrection(extra, argv) {
45464
45659
  return sibling ? correctedPositionalCommand(sibling, extra[0], argv) : void 0;
45465
45660
  }
45466
45661
  var pendingHumanParseError;
45467
- function writeHumanParseError(str) {
45468
- pendingHumanParseError = str;
45662
+ function writeHumanParseError(str2) {
45663
+ pendingHumanParseError = str2;
45469
45664
  }
45470
- function envelopeAwareWriteErr(str) {
45471
- const plain = str.replace(/\[[0-9;]*m/g, "");
45665
+ function envelopeAwareWriteErr(str2) {
45666
+ const plain = str2.replace(/\[[0-9;]*m/g, "");
45472
45667
  if (plain.includes(PARSE_HINT_SENTINEL)) {
45473
45668
  if (unknownFlagJsonHandled && argvWantsJson3()) {
45474
45669
  pendingHumanParseError = void 0;
45475
45670
  return;
45476
45671
  }
45477
- const hint = str.replace(PARSE_HINT_SENTINEL, resolveParseHint());
45672
+ const hint = str2.replace(PARSE_HINT_SENTINEL, resolveParseHint());
45478
45673
  if (lastParseErrorKind === "bad-arguments") {
45479
45674
  process.stderr.write(hint);
45480
45675
  if (pendingHumanParseError) process.stderr.write(pendingHumanParseError);
@@ -45490,7 +45685,7 @@ function envelopeAwareWriteErr(str) {
45490
45685
  const badChoice = parseInvalidChoiceError(plain);
45491
45686
  if (badChoice) {
45492
45687
  if (!argvWantsJson3()) {
45493
- writeHumanParseError(str);
45688
+ writeHumanParseError(str2);
45494
45689
  return;
45495
45690
  }
45496
45691
  process.stderr.write(
@@ -45518,7 +45713,7 @@ function envelopeAwareWriteErr(str) {
45518
45713
  unknownFlagJsonHandled = true;
45519
45714
  return;
45520
45715
  }
45521
- writeHumanParseError(str.replace(/error: too many arguments[^\n]*/, `error: ${message2}`));
45716
+ writeHumanParseError(str2.replace(/error: too many arguments[^\n]*/, `error: ${message2}`));
45522
45717
  return;
45523
45718
  }
45524
45719
  const match = /unknown option '([^']+)'/.exec(plain);
@@ -45544,18 +45739,18 @@ function envelopeAwareWriteErr(str) {
45544
45739
  return;
45545
45740
  }
45546
45741
  if (positional) {
45547
- writeHumanParseError(str.replace(/unknown option '[^']+'/, unknownTargetFlagMessage(flag, positional)));
45742
+ writeHumanParseError(str2.replace(/unknown option '[^']+'/, unknownTargetFlagMessage(flag, positional)));
45548
45743
  return;
45549
45744
  }
45550
- writeHumanParseError(str);
45745
+ writeHumanParseError(str2);
45551
45746
  return;
45552
45747
  }
45553
45748
  if (unknownFlagJsonHandled && argvWantsJson3()) return;
45554
45749
  if (argvWantsJson3()) {
45555
- process.stderr.write(str);
45750
+ process.stderr.write(str2);
45556
45751
  return;
45557
45752
  }
45558
- writeHumanParseError(str);
45753
+ writeHumanParseError(str2);
45559
45754
  }
45560
45755
  var INVOKED_ARGV3 = process.argv.slice(2);
45561
45756
  var program2 = new Command();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.3.67",
3
+ "version": "4.4.0",
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",