@mutmutco/cli 4.3.27 → 4.3.28

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 +239 -30
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -3599,6 +3599,10 @@ var ERROR_CODES = {
3599
3599
  ERR_BAD_ENUM: "ERR_BAD_ENUM",
3600
3600
  /** An unknown flag or subcommand — usually a typo; carries a `did_you_mean`. */
3601
3601
  ERR_UNKNOWN_FLAG: "ERR_UNKNOWN_FLAG",
3602
+ /** A positional argument was passed to a command that does not take it (e.g. `oracle board read 496`,
3603
+ * a whole-board read given an issue number). Carries a `corrected_command` when exactly one sibling
3604
+ * answers the same verb positionally (#6354). */
3605
+ ERR_EXCESS_ARGUMENT: "ERR_EXCESS_ARGUMENT",
3602
3606
  /** A referenced resource (issue, repo, board item) does not exist. */
3603
3607
  ERR_NOT_FOUND: "ERR_NOT_FOUND",
3604
3608
  /** Missing / rejected credentials on a path that needs auth. */
@@ -3642,6 +3646,11 @@ var ERROR_CODE_REFERENCE = [
3642
3646
  meaning: "A flag or subcommand is not known to this CLI version.",
3643
3647
  typical_fix: "Check `did_you_mean`, route with `mmi-cli commands --json`, then inspect exact detail with `mmi-cli explain <command> --json`; update the CLI if it should exist."
3644
3648
  },
3649
+ {
3650
+ code: ERROR_CODES.ERR_EXCESS_ARGUMENT,
3651
+ meaning: "A positional argument was supplied to a command that takes none (or fewer).",
3652
+ typical_fix: "Run the command named by `corrected_command`; otherwise drop the extra argument and select the target with the flags in `mmi-cli explain <command> --json`."
3653
+ },
3645
3654
  {
3646
3655
  code: ERROR_CODES.ERR_NOT_FOUND,
3647
3656
  meaning: "The referenced issue, PR, repo, board item, or other resource was not found.",
@@ -4329,8 +4338,8 @@ function fail(msg, payload) {
4329
4338
  if (payload && json) {
4330
4339
  console.error(formatErrorEnvelope(canonicalMsg, payload));
4331
4340
  } else {
4332
- console.error(`mmi-cli ${canonicalMsg}`);
4333
4341
  if (!json) console.error(`run: mmi-cli explain ${commandFromFailMessage(msg)}`);
4342
+ console.error(`mmi-cli ${canonicalMsg}`);
4334
4343
  }
4335
4344
  hardExit(1);
4336
4345
  }
@@ -4755,6 +4764,26 @@ function synonymFlagFor(flag, ownFlags) {
4755
4764
  function unknownTargetFlagMessage(flag, positional) {
4756
4765
  return `unknown option '${flag}' \u2014 this command takes its target as a positional: ${positional}`;
4757
4766
  }
4767
+ function parseExcessArgumentsError(plain) {
4768
+ const m = /too many arguments(?: for '[^']*')?\. Expected \d+ arguments? but got \d+: (.+?)\.\s*$/.exec(plain.trim());
4769
+ if (!m) return void 0;
4770
+ const extra = m[1].split(",").map((s) => s.trim()).filter(Boolean);
4771
+ return extra.length ? { extra } : void 0;
4772
+ }
4773
+ function siblingTakingPositional(leaf, siblings) {
4774
+ const hits = siblings.filter((s) => s.path && s.names.includes(leaf));
4775
+ return hits.length === 1 ? hits[0] : void 0;
4776
+ }
4777
+ function correctedPositionalCommand(sibling, value, argv) {
4778
+ const i = argv.indexOf(value);
4779
+ const rest = i < 0 ? argv : [...argv.slice(0, i), ...argv.slice(i + 1)];
4780
+ return [`mmi-cli ${sibling.path}`, value, ...survivingFlagArgs(rest, "", sibling.ownFlags)].join(" ");
4781
+ }
4782
+ function excessArgumentMessage(extra, corrected) {
4783
+ const list = extra.map((t) => `'${t}'`).join(", ");
4784
+ const head = `unexpected positional argument ${list} \u2014 this command does not take it`;
4785
+ return corrected ? `${head}. Did you mean \`${corrected}\`?` : head;
4786
+ }
4758
4787
 
4759
4788
  // src/released-version-cache.ts
4760
4789
  var import_node_fs7 = require("node:fs");
@@ -9029,9 +9058,9 @@ async function secretsCopy(deps, opts) {
9029
9058
  deps.err("secrets copy: --from and --to must differ");
9030
9059
  return false;
9031
9060
  }
9032
- const keys = [...new Set(opts.keys.map((k) => k.trim()).filter(Boolean))];
9061
+ const keys = [...new Set(opts.keys.flatMap((k) => k.split(/[,\s]+/)).filter(Boolean))];
9033
9062
  if (!keys.length) {
9034
- deps.err("secrets copy: --keys required (comma-separated allowlist)");
9063
+ deps.err("secrets copy: --keys required (comma- or space-separated allowlist)");
9035
9064
  return false;
9036
9065
  }
9037
9066
  for (const key of keys) {
@@ -13963,11 +13992,13 @@ function expectedJsOrigins(cfg) {
13963
13992
  return uniq([...expectedHosts(cfg).map((h) => `https://${h}`), ...LOOPBACK]);
13964
13993
  }
13965
13994
  function expectedRedirectUris(cfg) {
13966
- const { callbackPath } = cfg;
13967
- return uniq([
13968
- ...expectedHosts(cfg).map((h) => `https://${h}${callbackPath}`),
13969
- ...LOOPBACK.map((l) => `${l}${callbackPath}`)
13970
- ]);
13995
+ const paths = [cfg.callbackPath, ...cfg.extraCallbackPaths ?? []];
13996
+ return uniq(
13997
+ paths.flatMap((path2) => [
13998
+ ...expectedHosts(cfg).map((h) => `https://${h}${path2}`),
13999
+ ...LOOPBACK.map((l) => `${l}${path2}`)
14000
+ ])
14001
+ );
13971
14002
  }
13972
14003
  function oauthSsmKeys() {
13973
14004
  return [...SSM_NAMES];
@@ -13988,6 +14019,18 @@ function parseOauthClientJson(input) {
13988
14019
  }
13989
14020
  return { clientId, clientSecret };
13990
14021
  }
14022
+ function parseExtraCallbackPaths(raw) {
14023
+ if (raw === void 0) return void 0;
14024
+ if (!Array.isArray(raw) || raw.length === 0 || raw.some((p) => typeof p !== "string" || !p.trim())) {
14025
+ throw new Error("oauth.extraCallbackPaths must be a non-empty array of callback path strings");
14026
+ }
14027
+ const paths = raw.map((p) => p.trim());
14028
+ const bad = paths.find((p) => !p.startsWith("/"));
14029
+ if (bad !== void 0) {
14030
+ throw new Error(`oauth.extraCallbackPaths entries must start with "/" (got ${JSON.stringify(bad)})`);
14031
+ }
14032
+ return uniq(paths);
14033
+ }
13991
14034
  function parseOauthConfig(mmiConfig, slug) {
13992
14035
  const rawUnknown = mmiConfig?.oauth;
13993
14036
  if (rawUnknown === void 0) throw new Error(`oauth is not configured for ${slug}`);
@@ -14002,12 +14045,15 @@ function parseOauthConfig(mmiConfig, slug) {
14002
14045
  throw new Error(`oauth.callbackPath must start with "/" (got ${JSON.stringify(callbackPath)})`);
14003
14046
  }
14004
14047
  if (callbackPath !== DEFAULT_CALLBACK_PATH) {
14005
- throw new Error(`oauth.callbackPath must be "${DEFAULT_CALLBACK_PATH}" (got ${JSON.stringify(callbackPath)})`);
14048
+ throw new Error(
14049
+ `oauth.callbackPath is the app-login callback and must be "${DEFAULT_CALLBACK_PATH}" (got ${JSON.stringify(callbackPath)}) \u2014 a second callback on the same client (e.g. a Workspace connect endpoint) goes in oauth.extraCallbackPaths`
14050
+ );
14006
14051
  }
14052
+ const extraCallbackPaths = parseExtraCallbackPaths(raw.extraCallbackPaths);
14007
14053
  const meta = mmiConfig ?? {};
14008
14054
  const rawFofuSub = raw.fofuSubdomain;
14009
14055
  const fofuSubdomain = meta.fofuEnabled === true ? typeof rawFofuSub === "string" ? rawFofuSub : defaultSubdomain(slug) : void 0;
14010
- return { subdomains, domains, callbackPath, fofuSubdomain };
14056
+ return { subdomains, domains, callbackPath, extraCallbackPaths, fofuSubdomain };
14011
14057
  }
14012
14058
  function probeRedirectUri(callbackPath, port = 9123) {
14013
14059
  return `http://localhost:${port}${callbackPath}`;
@@ -15535,10 +15581,10 @@ var rollout_plan_default = {
15535
15581
  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)."
15536
15582
  },
15537
15583
  baseline: {
15538
- version: "4.3.27",
15539
- tag: "v4.3.27",
15540
- commit: "924b5a7761c4",
15541
- npm: "@mutmutco/cli@4.3.27"
15584
+ version: "4.3.28",
15585
+ tag: "v4.3.28",
15586
+ commit: "9a58493b4404",
15587
+ npm: "@mutmutco/cli@4.3.28"
15542
15588
  },
15543
15589
  exitCriterion: "fleet-n-of-n",
15544
15590
  hubOnlyShortcut: "forbidden",
@@ -15555,14 +15601,14 @@ var rollout_plan_default = {
15555
15601
  repo: "mutmutco/mmi-hub",
15556
15602
  role: "canary",
15557
15603
  schedule: "train",
15558
- v3Target: "v4.3.27"
15604
+ v3Target: "v4.3.28"
15559
15605
  }
15560
15606
  ],
15561
15607
  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.",
15562
15608
  rollback: {
15563
15609
  independent: true,
15564
- mechanism: "npm dist-tag latest -> 4.3.27 and redeploy the Hub Lambda from tag v4.3.27 (924b5a7761c4); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15565
- v3Target: "v4.3.27 (@mutmutco/cli@4.3.27, tag commit 924b5a7761c4 \u2014 last known-good release carrying the repo-index v4-only contract)"
15610
+ mechanism: "npm dist-tag latest -> 4.3.28 and redeploy the Hub Lambda from tag v4.3.28 (9a58493b4404); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15611
+ v3Target: "v4.3.28 (@mutmutco/cli@4.3.28, tag commit 9a58493b4404 \u2014 last known-good release carrying the repo-index v4-only contract)"
15566
15612
  }
15567
15613
  },
15568
15614
  {
@@ -28959,7 +29005,7 @@ function parseOauthVar(raw) {
28959
29005
  throw new Error(`org project set: oauth must be JSON, e.g. {"subdomains":["app"],"domains":["example.co"],"callbackPath":"${DEFAULT_CALLBACK_PATH}"}`);
28960
29006
  }
28961
29007
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
28962
- throw new Error("org project set: oauth must be a {subdomains,domains,callbackPath,fofuSubdomain} object");
29008
+ throw new Error("org project set: oauth must be a {subdomains,domains,callbackPath,extraCallbackPaths,fofuSubdomain} object");
28963
29009
  }
28964
29010
  const map = parsed;
28965
29011
  const out = {};
@@ -28973,14 +29019,22 @@ function parseOauthVar(raw) {
28973
29019
  if (typeof value !== "string" || !value.trim()) throw new Error("org project set: oauth.callbackPath must be a non-empty string");
28974
29020
  const callbackPath = value.trim();
28975
29021
  if (callbackPath !== DEFAULT_CALLBACK_PATH) {
28976
- throw new Error(`org project set: oauth.callbackPath must be "${DEFAULT_CALLBACK_PATH}" (got ${JSON.stringify(callbackPath)})`);
29022
+ throw new Error(
29023
+ `org project set: oauth.callbackPath is the app-login callback and must be "${DEFAULT_CALLBACK_PATH}" (got ${JSON.stringify(callbackPath)}) \u2014 a second callback on the same client (e.g. a Workspace connect endpoint) goes in oauth.extraCallbackPaths`
29024
+ );
28977
29025
  }
28978
29026
  out.callbackPath = callbackPath;
29027
+ } else if (key === "extraCallbackPaths") {
29028
+ try {
29029
+ out.extraCallbackPaths = parseExtraCallbackPaths(value);
29030
+ } catch (e) {
29031
+ throw new Error(`org project set: ${e.message}`);
29032
+ }
28979
29033
  } else if (key === "fofuSubdomain") {
28980
29034
  if (typeof value !== "string") throw new Error('org project set: oauth.fofuSubdomain must be a string ("" selects the apex fofu.ai)');
28981
29035
  out.fofuSubdomain = value.trim();
28982
29036
  } else {
28983
- throw new Error(`org project set: oauth key "${key}" \u2014 expected only subdomains/domains/callbackPath/fofuSubdomain`);
29037
+ throw new Error(`org project set: oauth key "${key}" \u2014 expected only subdomains/domains/callbackPath/extraCallbackPaths/fofuSubdomain`);
28984
29038
  }
28985
29039
  }
28986
29040
  return out;
@@ -29180,7 +29234,7 @@ var SETTABLE_VAR_HINTS = {
29180
29234
  runtimeVaultOnly: "true|false",
29181
29235
  seedCanary: "true|false",
29182
29236
  repos: 'JSON array, e.g. ["mutmutco/mm-foo"]',
29183
- oauth: "JSON {subdomains,domains,callbackPath,fofuSubdomain}",
29237
+ oauth: "JSON {subdomains,domains,callbackPath,extraCallbackPaths,fofuSubdomain}",
29184
29238
  requiredGcpApis: "comma-string",
29185
29239
  requiredRuntimeSecrets: 'JSON stage map, e.g. {"dev":["KEY"],"rc":["KEY"],"main":["KEY"]}',
29186
29240
  requiredBuildSecrets: 'JSON flat array, e.g. ["NODE_AUTH_TOKEN=@github-packages-token"]',
@@ -29652,6 +29706,51 @@ function findEnvFiles(root) {
29652
29706
  return found.sort();
29653
29707
  }
29654
29708
 
29709
+ // src/stage-runtime-env.ts
29710
+ var SERVICE_NAME_RE = /^[A-Za-z0-9_.-]+$/;
29711
+ var ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
29712
+ var RUNTIME_ENV_OVERRIDE_RELPATH = "tmp/stage/runtime-env.override.yml";
29713
+ function labelsMarkRuntimeEnv(labels) {
29714
+ if (Array.isArray(labels)) {
29715
+ return labels.some((l) => typeof l === "string" && /^mmi\.runtime-env\s*=\s*["']?true["']?$/.test(l.trim()));
29716
+ }
29717
+ if (labels && typeof labels === "object") {
29718
+ return String(labels["mmi.runtime-env"] ?? "") === "true";
29719
+ }
29720
+ return false;
29721
+ }
29722
+ function runtimeEnvMarkedServices(composeConfigJson) {
29723
+ let parsed;
29724
+ try {
29725
+ parsed = JSON.parse(composeConfigJson);
29726
+ } catch {
29727
+ return [];
29728
+ }
29729
+ const services = parsed?.services;
29730
+ if (!services || typeof services !== "object" || Array.isArray(services)) return [];
29731
+ return Object.entries(services).filter(([name, svc]) => SERVICE_NAME_RE.test(name) && labelsMarkRuntimeEnv(svc?.labels)).map(([name]) => name).sort();
29732
+ }
29733
+ function renderRuntimeEnvOverride(services, keys) {
29734
+ const safeServices = services.filter((s) => SERVICE_NAME_RE.test(s)).sort();
29735
+ const safeKeys = [...new Set(keys.filter((k) => ENV_KEY_RE.test(k)))].sort();
29736
+ if (!safeServices.length || !safeKeys.length) return null;
29737
+ const lines2 = [
29738
+ "# Generated by `mmi-cli stage` (#6345) \u2014 do not edit, do not commit.",
29739
+ '# Passes the vault-resolved runtime env through to every service labelled mmi.runtime-env: "true",',
29740
+ "# exactly as the box deploy does. KEY NAMES ONLY: the values live in the compose process env (#2655).",
29741
+ "services:"
29742
+ ];
29743
+ for (const service of safeServices) {
29744
+ lines2.push(` ${service}:`, " environment:");
29745
+ for (const key of safeKeys) lines2.push(` ${key}: \${${key}:?}`);
29746
+ }
29747
+ return `${lines2.join("\n")}
29748
+ `;
29749
+ }
29750
+ function stageComposeFileEnv(files) {
29751
+ return { COMPOSE_PATH_SEPARATOR: ",", COMPOSE_FILE: files.join(",") };
29752
+ }
29753
+
29655
29754
  // src/stage-runner.ts
29656
29755
  var execFileP3 = (0, import_node_util5.promisify)(import_node_child_process11.execFile);
29657
29756
  var DOCKER_TIMEOUT_MS = 15e3;
@@ -30017,6 +30116,36 @@ function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
30017
30116
  ...stagePort != null && !composeResolvesPort(cwd) ? { PORT: String(stagePort) } : {}
30018
30117
  };
30019
30118
  }
30119
+ function stageComposeFiles(cwd) {
30120
+ const files = ["docker-compose.yml"];
30121
+ for (const conventional of ["docker-compose.override.yml", "docker-compose.override.yaml"]) {
30122
+ if ((0, import_node_fs28.existsSync)((0, import_node_path27.join)(cwd, conventional))) files.push(conventional);
30123
+ }
30124
+ return [...files, RUNTIME_ENV_OVERRIDE_RELPATH];
30125
+ }
30126
+ async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
30127
+ const keys = Object.keys(vaultEnvMerge ?? {});
30128
+ if (!keys.length || !(0, import_node_fs28.existsSync)((0, import_node_path27.join)(cwd, "docker-compose.yml"))) return {};
30129
+ let configJson;
30130
+ try {
30131
+ const { stdout } = await execFileP3("docker", ["compose", "config", "--format", "json"], {
30132
+ cwd,
30133
+ windowsHide: true,
30134
+ timeout: DOCKER_TIMEOUT_MS,
30135
+ maxBuffer: 1024 * 1024 * 8,
30136
+ env: { ...process.env, ...composeEnv }
30137
+ });
30138
+ configJson = stdout;
30139
+ } catch {
30140
+ return {};
30141
+ }
30142
+ const body = renderRuntimeEnvOverride(runtimeEnvMarkedServices(configJson), keys);
30143
+ if (!body) return {};
30144
+ const overridePath = (0, import_node_path27.join)(cwd, RUNTIME_ENV_OVERRIDE_RELPATH);
30145
+ (0, import_node_fs28.mkdirSync)((0, import_node_path27.join)(cwd, "tmp", "stage"), { recursive: true });
30146
+ (0, import_node_fs28.writeFileSync)(overridePath, body, "utf8");
30147
+ return stageComposeFileEnv(stageComposeFiles(cwd));
30148
+ }
30020
30149
  async function ensureStageRuntimeEnv(config, opts, cwd) {
30021
30150
  if (!config.ensureEnv) return;
30022
30151
  const target = (0, import_node_path27.join)(cwd, config.ensureEnv.target);
@@ -30196,6 +30325,7 @@ async function startStage(config = {}, opts = {}) {
30196
30325
  if (!opts.envPrepared) await ensureStageRuntimeEnv(config, opts, cwd);
30197
30326
  if (stagePort != null && portGuard) await ensureStagePortAvailable(stagePort, cwd, portGuard);
30198
30327
  const composeEnv = stageComposeEnv(config, stagePort, opts.vaultEnvMerge, cwd);
30328
+ const upEnv = { ...composeEnv, ...await prepareRuntimeEnvPassthrough(cwd, composeEnv, opts.vaultEnvMerge) };
30199
30329
  let up = sub(config.up.trim());
30200
30330
  if (opts.forceRecreate) up = appendForceRecreate(up);
30201
30331
  const identity = await resolveStageIdentity(cwd);
@@ -30208,7 +30338,7 @@ async function startStage(config = {}, opts = {}) {
30208
30338
  detached: process.platform !== "win32",
30209
30339
  windowsHide: true,
30210
30340
  stdio: "ignore",
30211
- env: { ...process.env, ...composeEnv }
30341
+ env: { ...process.env, ...upEnv }
30212
30342
  });
30213
30343
  const state = {
30214
30344
  pid: child2.pid ?? 0,
@@ -30222,6 +30352,9 @@ async function startStage(config = {}, opts = {}) {
30222
30352
  // #6343: record the non-secret half of the interpolation env so a LATER `stage stop` can run
30223
30353
  // `docker compose down` against the same file. `vaultEnvMerge` is omitted deliberately — no secret
30224
30354
  // value ever reaches disk (#2655); the stopping invocation re-fetches them.
30355
+ // #6345: the passthrough `COMPOSE_FILE` is omitted too. The override only ADDS container env to services
30356
+ // the project file already declares, so `down` removes the same containers without it — and a later
30357
+ // `stage stop` that resolved no secrets would otherwise die on the override's `${KEY:?}`.
30225
30358
  teardown: config.teardown?.trim() ? { command: sub(config.teardown.trim()), cwd, env: stageComposeEnv(config, stagePort, void 0, cwd) } : void 0
30226
30359
  };
30227
30360
  writeState(statePath, state);
@@ -30230,7 +30363,7 @@ async function startStage(config = {}, opts = {}) {
30230
30363
  if (state.healthUrl) await waitForHealth(state.healthUrl, opts.timeoutMs ?? 6e4, config.healthAnyStatus);
30231
30364
  else await waitForProcessStability(child2);
30232
30365
  } catch (e) {
30233
- await cleanupStageState(state, [statePath, globalStatePath], opts.timeoutMs ?? 6e4, cwd, composeEnv);
30366
+ await cleanupStageState(state, [statePath, globalStatePath], opts.timeoutMs ?? 6e4, cwd, upEnv);
30234
30367
  throw e;
30235
30368
  }
30236
30369
  const result = {
@@ -36001,7 +36134,17 @@ function registerStageCommands(program3) {
36001
36134
  return printLine(o.json ? JSON.stringify({ command: "stage stop", steps }, null, 2) : renderSteps("mmi-cli stage stop: dry-run plan", steps));
36002
36135
  }
36003
36136
  try {
36004
- const result = await stopStage({ cwd: process.cwd(), requiredIdentityCwd: process.cwd() });
36137
+ let vaultEnvMerge;
36138
+ try {
36139
+ const { project: project2, cfg: stageCfg } = await resolveStage();
36140
+ vaultEnvMerge = await fetchStageVaultEnvMerge(project2, stageCfg);
36141
+ } catch {
36142
+ }
36143
+ const result = await stopStage({
36144
+ cwd: process.cwd(),
36145
+ requiredIdentityCwd: process.cwd(),
36146
+ ...vaultEnvMerge ? { vaultEnvMerge } : {}
36147
+ });
36005
36148
  printLine(o.json ? JSON.stringify(result) : `mmi-cli stage stop: ${result.message}`);
36006
36149
  } catch (e) {
36007
36150
  fail(`stage stop: ${e.message}`);
@@ -44293,11 +44436,55 @@ function resolveParseHint() {
44293
44436
  `Run \`mmi-cli ${path2} --help\` for its signature, \`mmi-cli explain ${path2} --json\` for exact detail, or \`mmi-cli commands\` for the task map.`
44294
44437
  ].join("\n");
44295
44438
  }
44439
+ function positionalCommandEntries(house) {
44440
+ const acc = [];
44441
+ const walk2 = (node) => {
44442
+ if (commandMetadata(node)?.category !== "internal") {
44443
+ const args = node.registeredArguments ?? [];
44444
+ const path2 = commandPath2(node);
44445
+ const canonical = path2 ? canonicalPathFor(path2) ?? path2 : "";
44446
+ if (args[0] && canonical && (house === void 0 || houseForPath(canonical) === house)) {
44447
+ acc.push({
44448
+ path: canonical,
44449
+ names: [node.name(), ...node.aliases()],
44450
+ argName: args[0].name(),
44451
+ ownFlags: commandOwnLongFlags(node)
44452
+ });
44453
+ }
44454
+ }
44455
+ for (const child2 of node.commands) walk2(child2);
44456
+ };
44457
+ for (const child2 of program2.commands) walk2(child2);
44458
+ return acc;
44459
+ }
44460
+ function excessArgumentCorrection(extra, argv) {
44461
+ const invoked = resolveCommandFromArgv(program2, [...argv]);
44462
+ if (!invoked || extra.length !== 1) return void 0;
44463
+ const canonical = canonicalPathFor(commandPath2(invoked)) ?? commandPath2(invoked);
44464
+ const entries = positionalCommandEntries(houseForPath(canonical)).filter((e) => e.path !== canonical);
44465
+ const sibling = siblingTakingPositional(invoked.name(), entries);
44466
+ return sibling ? correctedPositionalCommand(sibling, extra[0], argv) : void 0;
44467
+ }
44468
+ var pendingHumanParseError;
44469
+ function writeHumanParseError(str) {
44470
+ pendingHumanParseError = str;
44471
+ }
44296
44472
  function envelopeAwareWriteErr(str) {
44297
44473
  const plain = str.replace(/\[[0-9;]*m/g, "");
44298
44474
  if (plain.includes(PARSE_HINT_SENTINEL)) {
44299
- if (unknownFlagJsonHandled && argvWantsJson3()) return;
44300
- process.stderr.write(str.replace(PARSE_HINT_SENTINEL, resolveParseHint()));
44475
+ if (unknownFlagJsonHandled && argvWantsJson3()) {
44476
+ pendingHumanParseError = void 0;
44477
+ return;
44478
+ }
44479
+ const hint = str.replace(PARSE_HINT_SENTINEL, resolveParseHint());
44480
+ if (lastParseErrorKind === "bad-arguments") {
44481
+ process.stderr.write(hint);
44482
+ if (pendingHumanParseError) process.stderr.write(pendingHumanParseError);
44483
+ } else {
44484
+ if (pendingHumanParseError) process.stderr.write(pendingHumanParseError);
44485
+ process.stderr.write(hint);
44486
+ }
44487
+ pendingHumanParseError = void 0;
44301
44488
  return;
44302
44489
  }
44303
44490
  lastParseErrorKind = classifyParseError(plain);
@@ -44305,7 +44492,7 @@ function envelopeAwareWriteErr(str) {
44305
44492
  const badChoice = parseInvalidChoiceError(plain);
44306
44493
  if (badChoice) {
44307
44494
  if (!argvWantsJson3()) {
44308
- process.stderr.write(str);
44495
+ writeHumanParseError(str);
44309
44496
  return;
44310
44497
  }
44311
44498
  process.stderr.write(
@@ -44318,6 +44505,24 @@ function envelopeAwareWriteErr(str) {
44318
44505
  unknownFlagJsonHandled = true;
44319
44506
  return;
44320
44507
  }
44508
+ const excess = parseExcessArgumentsError(plain);
44509
+ if (excess) {
44510
+ const argv = process.argv.slice(2);
44511
+ const corrected = excessArgumentCorrection(excess.extra, argv);
44512
+ const message2 = excessArgumentMessage(excess.extra, corrected);
44513
+ if (argvWantsJson3()) {
44514
+ process.stderr.write(
44515
+ formatErrorEnvelope(message2, {
44516
+ code: ERROR_CODES.ERR_EXCESS_ARGUMENT,
44517
+ ...corrected ? { corrected_command: corrected } : {}
44518
+ }) + "\n"
44519
+ );
44520
+ unknownFlagJsonHandled = true;
44521
+ return;
44522
+ }
44523
+ writeHumanParseError(str.replace(/error: too many arguments[^\n]*/, `error: ${message2}`));
44524
+ return;
44525
+ }
44321
44526
  const match = /unknown option '([^']+)'/.exec(plain);
44322
44527
  if (match) {
44323
44528
  const flag = match[1];
@@ -44341,14 +44546,18 @@ function envelopeAwareWriteErr(str) {
44341
44546
  return;
44342
44547
  }
44343
44548
  if (positional) {
44344
- process.stderr.write(str.replace(/unknown option '[^']+'/, unknownTargetFlagMessage(flag, positional)));
44549
+ writeHumanParseError(str.replace(/unknown option '[^']+'/, unknownTargetFlagMessage(flag, positional)));
44345
44550
  return;
44346
44551
  }
44347
- process.stderr.write(str);
44552
+ writeHumanParseError(str);
44348
44553
  return;
44349
44554
  }
44350
44555
  if (unknownFlagJsonHandled && argvWantsJson3()) return;
44351
- process.stderr.write(str);
44556
+ if (argvWantsJson3()) {
44557
+ process.stderr.write(str);
44558
+ return;
44559
+ }
44560
+ writeHumanParseError(str);
44352
44561
  }
44353
44562
  var INVOKED_ARGV3 = process.argv.slice(2);
44354
44563
  var program2 = new Command();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.3.27",
3
+ "version": "4.3.28",
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",