@mutmutco/cli 4.3.45 → 4.3.47

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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/main.cjs +2183 -1946
  3. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -5,6 +5,14 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res, err) => function __init() {
9
+ if (err) throw err[0];
10
+ try {
11
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
+ } catch (e) {
13
+ throw err = [e], e;
14
+ }
15
+ };
8
16
  var __export = (target, all) => {
9
17
  for (var name in all)
10
18
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,306 +35,1403 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
35
  ));
28
36
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
37
 
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- DEFAULT_PRIORITY: () => DEFAULT_PRIORITY,
34
- argvWantsJson: () => argvWantsJson3,
35
- awsCallerArn: () => awsCallerArn,
36
- ciAuditDeps: () => ciAuditDeps2,
37
- classifyParseError: () => classifyParseError,
38
- commandOwnLongFlags: () => commandOwnLongFlags,
39
- envHealLockPath: () => envHealLockPath,
40
- isOrgRegisteredRepo: () => isOrgRegisteredRepo,
41
- parseInvalidChoiceError: () => parseInvalidChoiceError,
42
- positionalTargetForm: () => positionalTargetForm,
43
- registryClientDeps: () => registryClientDeps,
44
- repoSlug: () => repoSlug,
45
- suggestCommandPath: () => suggestCommandPath,
46
- unknownCommandCandidates: () => unknownCommandCandidates
47
- });
48
- module.exports = __toCommonJS(index_exports);
49
-
50
- // node_modules/commander/lib/error.js
51
- var CommanderError = class extends Error {
52
- /**
53
- * Constructs the CommanderError class
54
- * @param {number} exitCode suggested exit code which could be used with process.exit
55
- * @param {string} code an id string representing the error
56
- * @param {string} message human-readable description of the error
57
- */
58
- constructor(exitCode, code, message2) {
59
- super(message2);
60
- Error.captureStackTrace(this, this.constructor);
61
- this.name = this.constructor.name;
62
- this.code = code;
63
- this.exitCode = exitCode;
64
- this.nestedError = void 0;
65
- }
66
- };
67
- var InvalidArgumentError = class extends CommanderError {
68
- /**
69
- * Constructs the InvalidArgumentError class
70
- * @param {string} [message] explanation of why argument is invalid
71
- */
72
- constructor(message2) {
73
- super(1, "commander.invalidArgument", message2);
74
- Error.captureStackTrace(this, this.constructor);
75
- this.name = this.constructor.name;
76
- }
77
- };
78
-
79
- // node_modules/commander/lib/argument.js
80
- var Argument = class {
81
- /**
82
- * Initialize a new command argument with the given name and description.
83
- * The default is that the argument is required, and you can explicitly
84
- * indicate this with <> around the name. Put [] around the name for an optional argument.
85
- *
86
- * @param {string} name
87
- * @param {string} [description]
88
- */
89
- constructor(name, description) {
90
- this.description = description || "";
91
- this.variadic = false;
92
- this.parseArg = void 0;
93
- this.defaultValue = void 0;
94
- this.defaultValueDescription = void 0;
95
- this.argChoices = void 0;
96
- switch (name[0]) {
97
- case "<":
98
- this.required = true;
99
- this._name = name.slice(1, -1);
100
- break;
101
- case "[":
102
- this.required = false;
103
- this._name = name.slice(1, -1);
104
- break;
105
- default:
106
- this.required = true;
107
- this._name = name;
108
- break;
109
- }
110
- if (this._name.endsWith("...")) {
111
- this.variadic = true;
112
- this._name = this._name.slice(0, -3);
38
+ // src/clean-exit.ts
39
+ function globalDispatcher() {
40
+ return globalThis[UNDICI_GLOBAL_DISPATCHER_SYMBOL];
41
+ }
42
+ function destroyHttpPool() {
43
+ try {
44
+ const dispatcher = globalDispatcher();
45
+ if (dispatcher?.destroy) {
46
+ void dispatcher.destroy();
47
+ return true;
113
48
  }
49
+ } catch {
114
50
  }
115
- /**
116
- * Return argument name.
117
- *
118
- * @return {string}
119
- */
120
- name() {
121
- return this._name;
51
+ return false;
52
+ }
53
+ async function closeHttpPool() {
54
+ const dispatcher = globalDispatcher();
55
+ if (!dispatcher) return;
56
+ if (dispatcher === closingDispatcher && closingDispatcherPromise) {
57
+ await closingDispatcherPromise;
58
+ return;
122
59
  }
123
- /**
124
- * @package
125
- */
126
- _collectValue(value, previous) {
127
- if (previous === this.defaultValue || !Array.isArray(previous)) {
128
- return [value];
60
+ closingDispatcher = dispatcher;
61
+ closingDispatcherPromise = (async () => {
62
+ try {
63
+ if (dispatcher.close) await dispatcher.close();
64
+ else if (dispatcher.destroy) await dispatcher.destroy();
65
+ } catch {
66
+ try {
67
+ if (dispatcher.destroy) await dispatcher.destroy();
68
+ } catch {
69
+ }
129
70
  }
130
- previous.push(value);
131
- return previous;
71
+ })();
72
+ await closingDispatcherPromise;
73
+ }
74
+ function hardExit(code) {
75
+ destroyHttpPool();
76
+ process.exit(code);
77
+ }
78
+ function flushStream(stream) {
79
+ return new Promise((resolve7) => {
80
+ try {
81
+ stream.write("", () => resolve7());
82
+ } catch {
83
+ resolve7();
84
+ }
85
+ });
86
+ }
87
+ async function flushStdio(timeoutMs = STDIO_FLUSH_TIMEOUT_MS) {
88
+ await Promise.race([
89
+ Promise.all([flushStream(process.stdout), flushStream(process.stderr)]),
90
+ new Promise((resolve7) => {
91
+ setTimeout(resolve7, timeoutMs).unref?.();
92
+ })
93
+ ]);
94
+ }
95
+ async function cleanExit(code) {
96
+ process.exitCode = code;
97
+ await closeHttpPool();
98
+ await flushStdio();
99
+ await new Promise((resolve7) => setImmediate(resolve7));
100
+ return void 0;
101
+ }
102
+ async function finishCliRun(watchdogMs = CLI_EXIT_WATCHDOG_MS) {
103
+ const timer = setTimeout(() => {
104
+ console.error(
105
+ `mmi-cli: command finished but the process did not exit within ${watchdogMs}ms \u2014 a handle leaked (${describeActiveHandles()}); forcing exit (#2904). Please report this.`
106
+ );
107
+ void flushStdio().finally(() => process.exit(typeof process.exitCode === "number" ? process.exitCode : 0));
108
+ }, watchdogMs);
109
+ timer.unref?.();
110
+ await closeHttpPool();
111
+ }
112
+ function describeActiveHandles() {
113
+ let summary;
114
+ try {
115
+ const counts = /* @__PURE__ */ new Map();
116
+ for (const type of process.getActiveResourcesInfo()) counts.set(type, (counts.get(type) ?? 0) + 1);
117
+ summary = [...counts.entries()].sort().map(([type, n]) => n > 1 ? `${type}\xD7${n}` : type).join(", ") || "none reported";
118
+ } catch {
119
+ summary = "unavailable";
132
120
  }
133
- /**
134
- * Set the default value, and optionally supply the description to be displayed in the help.
135
- *
136
- * @param {*} value
137
- * @param {string} [description]
138
- * @return {Argument}
139
- */
140
- default(value, description) {
141
- this.defaultValue = value;
142
- this.defaultValueDescription = description;
143
- return this;
121
+ try {
122
+ const children = (process._getActiveHandles?.() ?? []).filter((h) => h?.constructor?.name === "ChildProcess").map((h) => `${h.spawnfile ?? "?"}(pid=${h.pid ?? "?"})`);
123
+ if (children.length) summary += `; live children: ${children.join(", ")}`;
124
+ } catch {
144
125
  }
145
- /**
146
- * Set the custom handler for processing CLI command arguments into argument values.
147
- *
148
- * @param {Function} [fn]
149
- * @return {Argument}
150
- */
151
- argParser(fn) {
152
- this.parseArg = fn;
153
- return this;
126
+ return summary;
127
+ }
128
+ async function failGraceful(msg) {
129
+ console.error(`mmi-cli ${msg}`);
130
+ return cleanExit(1);
131
+ }
132
+ var UNDICI_GLOBAL_DISPATCHER_SYMBOL, closingDispatcher, closingDispatcherPromise, STDIO_FLUSH_TIMEOUT_MS, CLI_EXIT_WATCHDOG_MS;
133
+ var init_clean_exit = __esm({
134
+ "src/clean-exit.ts"() {
135
+ "use strict";
136
+ UNDICI_GLOBAL_DISPATCHER_SYMBOL = Object.getOwnPropertySymbols(globalThis).find(
137
+ (s) => s.description === "undici.globalDispatcher.1" || s.description?.startsWith("undici.globalDispatcher.")
138
+ ) ?? /* @__PURE__ */ Symbol.for("undici.globalDispatcher.1");
139
+ STDIO_FLUSH_TIMEOUT_MS = 2e3;
140
+ CLI_EXIT_WATCHDOG_MS = (() => {
141
+ const testOverride = Number(process.env.MMI_CLI_TEST_EXIT_WATCHDOG_MS);
142
+ return process.env.NODE_ENV === "test" && Number.isFinite(testOverride) && testOverride >= 25 ? testOverride : 1e4;
143
+ })();
154
144
  }
155
- /**
156
- * Only allow argument value to be one of choices.
157
- *
158
- * @param {string[]} values
159
- * @return {Argument}
160
- */
161
- choices(values) {
162
- this.argChoices = values.slice();
163
- this.parseArg = (arg, previous) => {
164
- if (!this.argChoices.includes(arg)) {
165
- throw new InvalidArgumentError(
166
- `Allowed choices are ${this.argChoices.join(", ")}.`
167
- );
168
- }
169
- if (this.variadic) {
170
- return this._collectValue(arg, previous);
171
- }
172
- return arg;
173
- };
174
- return this;
145
+ });
146
+
147
+ // src/hub-url.ts
148
+ function defaultHubUrl() {
149
+ return process.env.MMI_HUB_URL || DEFAULT_HUB_URL;
150
+ }
151
+ var DEFAULT_HUB_URL;
152
+ var init_hub_url = __esm({
153
+ "src/hub-url.ts"() {
154
+ "use strict";
155
+ DEFAULT_HUB_URL = "https://tqxxwzftic.execute-api.eu-central-1.amazonaws.com";
175
156
  }
176
- /**
177
- * Make argument required.
178
- *
179
- * @returns {Argument}
180
- */
181
- argRequired() {
182
- this.required = true;
183
- return this;
157
+ });
158
+
159
+ // ../infra/compat.mjs
160
+ function parseSemver(s) {
161
+ if (typeof s !== "string") return null;
162
+ const m = /^(\d+)\.(\d+)\.(\d+)(?:[-+]|$)/.exec(s.trim());
163
+ if (!m) return null;
164
+ return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) };
165
+ }
166
+ function versionAtLeast(v, min) {
167
+ const a = parseSemver(v);
168
+ const b = parseSemver(min);
169
+ if (!a || !b) return false;
170
+ if (a.major !== b.major) return a.major > b.major;
171
+ if (a.minor !== b.minor) return a.minor > b.minor;
172
+ return a.patch >= b.patch;
173
+ }
174
+ var CLIENT_VERSION_HEADER;
175
+ var init_compat = __esm({
176
+ "../infra/compat.mjs"() {
177
+ "use strict";
178
+ CLIENT_VERSION_HEADER = "x-client-version";
184
179
  }
185
- /**
186
- * Make argument optional.
187
- *
188
- * @returns {Argument}
189
- */
190
- argOptional() {
191
- this.required = false;
192
- return this;
180
+ });
181
+
182
+ // src/client-version.ts
183
+ function resolveClientVersionManifestCandidates(distDir = __dirname) {
184
+ return [
185
+ (0, import_node_path2.join)(distDir, "..", "..", ".claude-plugin", "plugin.json"),
186
+ (0, import_node_path2.join)(distDir, "..", "package.json")
187
+ ];
188
+ }
189
+ function readVersionFromManifest(path2) {
190
+ try {
191
+ const version = JSON.parse((0, import_node_fs2.readFileSync)(path2, "utf8")).version;
192
+ return typeof version === "string" && version.trim() ? version.trim() : null;
193
+ } catch {
194
+ return null;
193
195
  }
194
- };
195
- function humanReadableArgName(arg) {
196
- const nameOutput = arg.name() + (arg.variadic === true ? "..." : "");
197
- return arg.required ? "<" + nameOutput + ">" : "[" + nameOutput + "]";
198
196
  }
199
-
200
- // node_modules/commander/lib/command.js
201
- var import_node_events = require("node:events");
202
- var import_node_child_process = __toESM(require("node:child_process"), 1);
203
- var import_node_path = __toESM(require("node:path"), 1);
204
- var import_node_fs = __toESM(require("node:fs"), 1);
205
- var import_node_process = __toESM(require("node:process"), 1);
206
- var import_node_util2 = require("node:util");
207
-
208
- // node_modules/commander/lib/help.js
209
- var import_node_util = require("node:util");
210
- var Help = class {
211
- constructor() {
212
- this.helpWidth = void 0;
213
- this.minWidthToWrap = 40;
214
- this.sortSubcommands = false;
215
- this.sortOptions = false;
216
- this.showGlobalOptions = false;
197
+ function resolveClientVersion() {
198
+ for (const manifest of resolveClientVersionManifestCandidates()) {
199
+ const version = readVersionFromManifest(manifest);
200
+ if (version) return version;
217
201
  }
218
- /**
219
- * prepareContext is called by Commander after applying overrides from `Command.configureHelp()`
220
- * and just before calling `formatHelp()`.
221
- *
222
- * Commander just uses the helpWidth and the rest is provided for optional use by more complex subclasses.
223
- *
224
- * @param {{ error?: boolean, helpWidth?: number, outputHasColors?: boolean }} contextOptions
225
- */
226
- prepareContext(contextOptions) {
227
- this.helpWidth = this.helpWidth ?? contextOptions.helpWidth ?? 80;
202
+ return "0.0.0";
203
+ }
204
+ function clientVersionHeaders() {
205
+ return { [CLIENT_VERSION_HEADER]: resolveClientVersion() };
206
+ }
207
+ function upgradeRequiredError(res, body) {
208
+ const minVersion = body && typeof body === "object" && typeof body.minVersion === "string" ? body.minVersion : "a newer version";
209
+ return `Hub requires mmi-cli >= ${minVersion} \u2014 run mmi-cli doctor (installed ${resolveClientVersion()})`;
210
+ }
211
+ var import_node_fs2, import_node_path2;
212
+ var init_client_version = __esm({
213
+ "src/client-version.ts"() {
214
+ "use strict";
215
+ import_node_fs2 = require("node:fs");
216
+ import_node_path2 = require("node:path");
217
+ init_compat();
228
218
  }
229
- /**
230
- * Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one.
231
- *
232
- * @param {Command} cmd
233
- * @returns {Command[]}
234
- */
235
- visibleCommands(cmd) {
236
- const visibleCommands = cmd.commands.filter((cmd2) => !cmd2._hidden);
237
- const helpCommand = cmd._getHelpCommand();
238
- if (helpCommand && !helpCommand._hidden) {
239
- visibleCommands.push(helpCommand);
240
- }
241
- if (this.sortSubcommands) {
242
- visibleCommands.sort((a, b) => {
243
- return a.name().localeCompare(b.name());
244
- });
219
+ });
220
+
221
+ // src/error-codes.ts
222
+ function buildErrorEnvelope(message2, payload) {
223
+ const env = { error_code: payload.code, message: message2 };
224
+ if (payload.offending_flag !== void 0) env.offending_flag = payload.offending_flag;
225
+ if (payload.expected !== void 0) env.expected = payload.expected;
226
+ if (payload.did_you_mean !== void 0) env.did_you_mean = payload.did_you_mean;
227
+ if (payload.corrected_command !== void 0) env.corrected_command = payload.corrected_command;
228
+ if (payload.current_parent !== void 0) env.current_parent = payload.current_parent;
229
+ if (payload.issue_ref !== void 0) env.issue_ref = payload.issue_ref;
230
+ if (payload.board_status !== void 0) env.board_status = payload.board_status;
231
+ return env;
232
+ }
233
+ function formatErrorEnvelope(message2, payload) {
234
+ return JSON.stringify(buildErrorEnvelope(message2, payload));
235
+ }
236
+ function levenshtein(a, b) {
237
+ if (a === b) return 0;
238
+ if (a.length === 0) return b.length;
239
+ if (b.length === 0) return a.length;
240
+ let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
241
+ let curr = new Array(b.length + 1);
242
+ for (let i = 1; i <= a.length; i++) {
243
+ curr[0] = i;
244
+ for (let j = 1; j <= b.length; j++) {
245
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
246
+ curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
245
247
  }
246
- return visibleCommands;
248
+ [prev, curr] = [curr, prev];
247
249
  }
248
- /**
249
- * Compare options for sort.
250
- *
251
- * @param {Option} a
252
- * @param {Option} b
253
- * @returns {number}
254
- */
255
- compareOptions(a, b) {
256
- const getSortKey = (option) => {
257
- return option.short ? option.short.replace(/^-/, "") : option.long.replace(/^--/, "");
258
- };
259
- return getSortKey(a).localeCompare(getSortKey(b));
250
+ return prev[b.length];
251
+ }
252
+ function didYouMean(input, candidates) {
253
+ const strip = (s) => s.replace(/^-+/, "");
254
+ const target = strip(input);
255
+ if (!target) return void 0;
256
+ let best;
257
+ for (const candidate of candidates) {
258
+ const cand = strip(candidate);
259
+ if (!cand) continue;
260
+ if (cand === target) continue;
261
+ if (cand === `${target}-file` || target === `${cand}-file`) continue;
262
+ const distance = levenshtein(target, cand);
263
+ const prefix = cand.startsWith(target) || target.startsWith(cand);
264
+ const threshold = Math.max(2, Math.ceil(cand.length * 0.4));
265
+ if (!prefix && distance > threshold) continue;
266
+ const better = !best || distance < best.distance || distance === best.distance && prefix && !best.prefix;
267
+ if (better) best = { flag: candidate, distance, prefix };
260
268
  }
261
- /**
262
- * Get an array of the visible options. Includes a placeholder for the implicit help option, if there is one.
263
- *
264
- * @param {Command} cmd
265
- * @returns {Option[]}
266
- */
267
- visibleOptions(cmd) {
268
- const visibleOptions = cmd.options.filter((option) => !option.hidden);
269
- const helpOption = cmd._getHelpOption();
270
- if (helpOption && !helpOption.hidden) {
271
- const removeShort = helpOption.short && cmd._findOption(helpOption.short);
272
- const removeLong = helpOption.long && cmd._findOption(helpOption.long);
273
- if (!removeShort && !removeLong) {
274
- visibleOptions.push(helpOption);
275
- } else if (helpOption.long && !removeLong) {
276
- visibleOptions.push(
277
- cmd.createOption(helpOption.long, helpOption.description)
278
- );
279
- } else if (helpOption.short && !removeShort) {
280
- visibleOptions.push(
281
- cmd.createOption(helpOption.short, helpOption.description)
282
- );
269
+ return best?.flag;
270
+ }
271
+ var ERROR_CODES, ERROR_CODE_REFERENCE;
272
+ var init_error_codes = __esm({
273
+ "src/error-codes.ts"() {
274
+ "use strict";
275
+ ERROR_CODES = {
276
+ /** A required flag was not supplied (e.g. `issue create` without `--priority`). */
277
+ ERR_MISSING_FLAG: "ERR_MISSING_FLAG",
278
+ /** Two mutually-exclusive flags were both supplied (e.g. `--title` with `--title-file`). */
279
+ ERR_CONFLICTING_FLAGS: "ERR_CONFLICTING_FLAGS",
280
+ /** A flag was supplied but its resolved value is empty (e.g. an empty `--title-file` or empty stdin). */
281
+ ERR_EMPTY_INPUT: "ERR_EMPTY_INPUT",
282
+ /** A supplied value has the right source but an invalid shape (e.g. a title with line breaks). */
283
+ ERR_INVALID_INPUT: "ERR_INVALID_INPUT",
284
+ /** A flag's value is outside its allowed set (e.g. `--priority nope`). */
285
+ ERR_BAD_ENUM: "ERR_BAD_ENUM",
286
+ /** An unknown flag or subcommand — usually a typo; carries a `did_you_mean`. */
287
+ ERR_UNKNOWN_FLAG: "ERR_UNKNOWN_FLAG",
288
+ /** A positional argument was passed to a command that does not take it (e.g. `oracle board read 496`,
289
+ * a whole-board read given an issue number). Carries a `corrected_command` when exactly one sibling
290
+ * answers the same verb positionally (#6354). */
291
+ ERR_EXCESS_ARGUMENT: "ERR_EXCESS_ARGUMENT",
292
+ /** A referenced resource (issue, repo, board item) does not exist. */
293
+ ERR_NOT_FOUND: "ERR_NOT_FOUND",
294
+ /** Missing / rejected credentials on a path that needs auth. */
295
+ ERR_NO_AUTH: "ERR_NO_AUTH",
296
+ /** The operation partially succeeded (some units done, some failed). */
297
+ ERR_PARTIAL: "ERR_PARTIAL",
298
+ /** The request was well-formed and every referent resolved, but the target's CURRENT STATE forbids the
299
+ * mutation (HTTP 409 semantics) — e.g. reparenting an issue that already has a parent. Deliberately not
300
+ * one code per API constraint: retry is futile until the named state is changed, and that is the fact a
301
+ * caller has to act on, whichever rule produced it. */
302
+ ERR_STATE_CONFLICT: "ERR_STATE_CONFLICT"
303
+ };
304
+ ERROR_CODE_REFERENCE = [
305
+ {
306
+ code: ERROR_CODES.ERR_MISSING_FLAG,
307
+ meaning: "A required flag was not supplied.",
308
+ typical_fix: "Run `mmi-cli explain <command>` and retry with the required flag."
309
+ },
310
+ {
311
+ code: ERROR_CODES.ERR_CONFLICTING_FLAGS,
312
+ meaning: "Two mutually-exclusive flags were supplied together.",
313
+ typical_fix: "Pass only one of the conflicting flags (see `offending_flag`) and retry."
314
+ },
315
+ {
316
+ code: ERROR_CODES.ERR_EMPTY_INPUT,
317
+ meaning: "A flag was supplied but resolved to an empty value (e.g. an empty file or empty stdin).",
318
+ typical_fix: "Provide non-empty content for the flag in `offending_flag` (a file with text, or a real pipe/heredoc for stdin)."
319
+ },
320
+ {
321
+ code: ERROR_CODES.ERR_INVALID_INPUT,
322
+ meaning: "A supplied value has an invalid shape for the flag.",
323
+ typical_fix: "Correct the value named by `offending_flag` and retry."
324
+ },
325
+ {
326
+ code: ERROR_CODES.ERR_BAD_ENUM,
327
+ meaning: "A flag value is outside the allowed enum.",
328
+ typical_fix: "Use one of the values in `expected`; casing and separators are often normalized by the CLI when supported."
329
+ },
330
+ {
331
+ code: ERROR_CODES.ERR_UNKNOWN_FLAG,
332
+ meaning: "A flag or subcommand is not known to this CLI version.",
333
+ 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."
334
+ },
335
+ {
336
+ code: ERROR_CODES.ERR_EXCESS_ARGUMENT,
337
+ meaning: "A positional argument was supplied to a command that takes none (or fewer).",
338
+ 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`."
339
+ },
340
+ {
341
+ code: ERROR_CODES.ERR_NOT_FOUND,
342
+ meaning: "The referenced issue, PR, repo, board item, or other resource was not found.",
343
+ typical_fix: "Verify the identifier and repo, then rerun with an explicit `--repo <owner/repo>` when local repo detection is ambiguous."
344
+ },
345
+ {
346
+ code: ERROR_CODES.ERR_NO_AUTH,
347
+ meaning: "The command needs credentials that are missing, expired, or rejected.",
348
+ typical_fix: "Run `mmi-cli doctor --self` or refresh the relevant GitHub/Hub session before retrying."
349
+ },
350
+ {
351
+ code: ERROR_CODES.ERR_PARTIAL,
352
+ meaning: "A batch operation completed some units and failed others.",
353
+ typical_fix: "Read the per-unit results, fix the failed inputs, and rerun only the failed units."
354
+ },
355
+ {
356
+ code: ERROR_CODES.ERR_STATE_CONFLICT,
357
+ meaning: "The request was valid and every referent resolved, but the target resource's current state forbids it.",
358
+ typical_fix: "A plain retry fails identically. Read the state field the envelope names (e.g. `current_parent`), change that state deliberately with the command that owns it, then retry."
283
359
  }
284
- }
285
- if (this.sortOptions) {
286
- visibleOptions.sort(this.compareOptions);
287
- }
288
- return visibleOptions;
289
- }
290
- /**
291
- * Get an array of the visible global options. (Not including help.)
292
- *
293
- * @param {Command} cmd
294
- * @returns {Option[]}
295
- */
296
- visibleGlobalOptions(cmd) {
297
- if (!this.showGlobalOptions) return [];
298
- const globalOptions = [];
299
- for (let ancestorCmd = cmd.parent; ancestorCmd; ancestorCmd = ancestorCmd.parent) {
300
- const visibleOptions = ancestorCmd.options.filter(
301
- (option) => !option.hidden
302
- );
303
- globalOptions.push(...visibleOptions);
304
- }
305
- if (this.sortOptions) {
306
- globalOptions.sort(this.compareOptions);
307
- }
308
- return globalOptions;
360
+ ];
309
361
  }
310
- /**
311
- * Get an array of the arguments if any have a description.
312
- *
313
- * @param {Command} cmd
314
- * @returns {Argument[]}
315
- */
316
- visibleArguments(cmd) {
317
- if (cmd._argsDescription) {
318
- cmd.registeredArguments.forEach((argument) => {
319
- argument.description = argument.description || cmd._argsDescription[argument.name()] || "";
320
- });
321
- }
322
- if (cmd.registeredArguments.find((argument) => argument.description)) {
323
- return cmd.registeredArguments;
324
- }
325
- return [];
362
+ });
363
+
364
+ // src/github-client.ts
365
+ function classifyGhTokenExecFailure(e) {
366
+ const err = e;
367
+ const stderr = typeof err?.stderr === "string" ? err.stderr.trim() : "";
368
+ const detail = (stderr || err?.message || String(e)).replace(/\s+/g, " ").slice(0, 200);
369
+ if (err?.code === "ENOENT") return { state: "absent", detail: "gh is not installed on PATH" };
370
+ if (err?.killed === true || (err?.signal ?? null) !== null) return { state: "failed", detail: `gh auth token did not complete (${detail})` };
371
+ if (/not logged (in|into)|no oauth token|authentication token not found|gh auth login/i.test(`${stderr} ${err?.message ?? ""}`)) {
372
+ return { state: "absent", detail: "gh is installed but not logged in" };
326
373
  }
327
- /**
328
- * Get the command term to show in the list of subcommands.
329
- *
374
+ return { state: "failed", detail };
375
+ }
376
+ async function githubTokenRead() {
377
+ if (process.env.GH_TOKEN) return { state: "ok", token: process.env.GH_TOKEN };
378
+ if (process.env.GITHUB_TOKEN) return { state: "ok", token: process.env.GITHUB_TOKEN };
379
+ cachedGhCliTokenRead ??= execFileP("gh", ["auth", "token"]).then(({ stdout }) => {
380
+ const token = stdout.trim();
381
+ return token ? { state: "ok", token } : { state: "absent", detail: "`gh auth token` exited 0 without printing a token" };
382
+ }).catch((e) => classifyGhTokenExecFailure(e));
383
+ return cachedGhCliTokenRead;
384
+ }
385
+ async function githubToken() {
386
+ const read = await githubTokenRead();
387
+ if (read.state === "failed") {
388
+ throw new GitHubApiError(
389
+ `GitHub identity read FAILED: \`gh auth token\` ${read.detail} \u2014 this is NOT "no token", so the call is refused rather than sent anonymously (an anonymous 401/404 would read as a permission or missing-object answer). Retry, or set GH_TOKEN/GITHUB_TOKEN for this process.`,
390
+ { status: 0 }
391
+ );
392
+ }
393
+ return read.state === "ok" ? read.token : void 0;
394
+ }
395
+ function rateLimitResetNote(resetEpochSeconds, now = Date.now()) {
396
+ if (!resetEpochSeconds) return "rate limit exhausted; reset time unknown";
397
+ const resetMs = resetEpochSeconds * 1e3;
398
+ const minutes = Math.max(0, Math.ceil((resetMs - now) / 6e4));
399
+ return `rate limit exhausted; resets at ${new Date(resetMs).toISOString()} (~${minutes}m)`;
400
+ }
401
+ function rateLimitFromResponse(res, detail) {
402
+ const remaining = res.headers.get("x-ratelimit-remaining");
403
+ const resetHeader = res.headers.get("x-ratelimit-reset");
404
+ const reset = resetHeader && /^\d+$/.test(resetHeader) ? Number(resetHeader) : void 0;
405
+ const rateLimited = (res.status === 403 || res.status === 429) && remaining === "0" || /rate limit|secondary rate|abuse detection/i.test(detail);
406
+ return { rateLimited, ...rateLimited && reset !== void 0 ? { rateLimitReset: reset } : {} };
407
+ }
408
+ function joinUrl(base, path2) {
409
+ if (path2.startsWith("http://") || path2.startsWith("https://")) return path2;
410
+ return `${base.replace(/\/+$/, "")}/${path2.replace(/^\/+/, "")}`;
411
+ }
412
+ function withPerPage(url) {
413
+ if (/[?&]per_page=/.test(url)) return url;
414
+ return url.includes("?") ? `${url}&per_page=100` : `${url}?per_page=100`;
415
+ }
416
+ function apiOrigin(baseUrl) {
417
+ return new URL(baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).origin;
418
+ }
419
+ function nextLink(linkHeader) {
420
+ if (!linkHeader) return void 0;
421
+ for (const part of linkHeader.split(",")) {
422
+ const match = part.match(/<([^>]+)>\s*;\s*rel="next"/);
423
+ if (match) return match[1];
424
+ }
425
+ return void 0;
426
+ }
427
+ function safeNextLink(linkHeader, allowedOrigin) {
428
+ const next = nextLink(linkHeader);
429
+ if (!next) return void 0;
430
+ let origin;
431
+ try {
432
+ origin = new URL(next).origin;
433
+ } catch {
434
+ throw new GitHubApiError(`pagination link rejected: invalid URL (${next})`, { status: 0 });
435
+ }
436
+ if (origin !== allowedOrigin) {
437
+ throw new GitHubApiError(`pagination link rejected: cross-origin ${origin} (expected ${allowedOrigin})`, { status: 0 });
438
+ }
439
+ return next;
440
+ }
441
+ async function errorFromResponse(res) {
442
+ let detail = "";
443
+ try {
444
+ const text = await res.text();
445
+ try {
446
+ const parsed = JSON.parse(text);
447
+ detail = parsed.message ?? text;
448
+ } catch {
449
+ detail = text;
450
+ }
451
+ } catch {
452
+ detail = "";
453
+ }
454
+ const suffix = detail ? `: ${detail.trim()}` : "";
455
+ const limit = rateLimitFromResponse(res, detail);
456
+ const budgetNote = limit.rateLimited ? ` \u2014 ${rateLimitResetNote(limit.rateLimitReset)}` : "";
457
+ return new GitHubApiError(`HTTP ${res.status}${suffix}${budgetNote} (${res.url})`, {
458
+ status: res.status,
459
+ ...limit.rateLimited ? { rateLimited: true, rateLimitReset: limit.rateLimitReset } : {}
460
+ });
461
+ }
462
+ function createGitHubClient(options = {}) {
463
+ const baseUrl = options.baseUrl ?? process.env.GITHUB_API_URL ?? "https://api.github.com";
464
+ const allowedOrigin = apiOrigin(baseUrl);
465
+ const token = options.token ?? githubToken;
466
+ const defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
467
+ const fetchImpl = options.fetchImpl ?? fetch;
468
+ const externalSignal = options.signal;
469
+ async function request(method, url, init = {}) {
470
+ const t = await token();
471
+ const headers = {
472
+ Accept: "application/vnd.github+json",
473
+ "X-GitHub-Api-Version": "2022-11-28",
474
+ "User-Agent": "mmi-cli",
475
+ ...t ? { Authorization: `Bearer ${t}` } : {},
476
+ ...init.body !== void 0 ? { "Content-Type": "application/json" } : {},
477
+ ...init.headers
478
+ };
479
+ const timeoutSignal = AbortSignal.timeout(init.timeoutMs ?? defaultTimeoutMs);
480
+ const res = await fetchImpl(url, {
481
+ method,
482
+ headers,
483
+ body: init.body !== void 0 ? JSON.stringify(init.body) : void 0,
484
+ signal: externalSignal ? AbortSignal.any([externalSignal, timeoutSignal]) : timeoutSignal
485
+ });
486
+ if (!res.ok) throw await errorFromResponse(res);
487
+ return res;
488
+ }
489
+ async function parseJson2(res) {
490
+ if (res.status === 204) return void 0;
491
+ const text = await res.text();
492
+ if (!text) return void 0;
493
+ return JSON.parse(text);
494
+ }
495
+ return {
496
+ async rest(method, path2, init) {
497
+ const res = await request(method, joinUrl(baseUrl, path2), init);
498
+ return parseJson2(res);
499
+ },
500
+ async restPaginate(path2, init) {
501
+ const items = [];
502
+ let url = withPerPage(joinUrl(baseUrl, path2));
503
+ while (url) {
504
+ const res = await request("GET", url, init);
505
+ const page = await parseJson2(res);
506
+ if (!Array.isArray(page)) {
507
+ throw new GitHubApiError(
508
+ `pagination page was not a JSON array (got ${page === void 0 ? "an empty body" : typeof page}) \u2014 the list read is PARTIAL and must not be treated as complete: ${url}. Retry the call.`,
509
+ { status: res.status }
510
+ );
511
+ }
512
+ items.push(...page);
513
+ url = safeNextLink(res.headers.get("link"), allowedOrigin);
514
+ }
515
+ return items;
516
+ },
517
+ async graphql(query, variables, init) {
518
+ const res = await request("POST", joinUrl(baseUrl, "graphql"), {
519
+ ...init,
520
+ body: { query, ...variables ? { variables } : {} }
521
+ });
522
+ const parsed = await parseJson2(res);
523
+ if (parsed?.errors?.length) {
524
+ const message2 = parsed.errors.map((e) => e.message ?? e.type ?? "unknown GraphQL error").join("; ");
525
+ const rateLimited = parsed.errors.some((e) => e.type === "RATE_LIMITED" || /rate limit/i.test(e.message ?? ""));
526
+ if (rateLimited) {
527
+ const resetHeader = res.headers.get("x-ratelimit-reset");
528
+ const reset = resetHeader && /^\d+$/.test(resetHeader) ? Number(resetHeader) : void 0;
529
+ throw new GitHubApiError(`GraphQL: ${message2} \u2014 GraphQL ${rateLimitResetNote(reset)}; REST may still have headroom`, {
530
+ status: 200,
531
+ graphqlErrors: parsed.errors,
532
+ rateLimited: true,
533
+ rateLimitReset: reset
534
+ });
535
+ }
536
+ throw new GitHubApiError(`GraphQL: ${message2}`, { status: 200, graphqlErrors: parsed.errors });
537
+ }
538
+ if (!parsed || parsed.data === void 0 || parsed.data === null) {
539
+ throw new GitHubApiError("GraphQL response did not include data", { status: 200 });
540
+ }
541
+ return parsed.data;
542
+ }
543
+ };
544
+ }
545
+ function defaultGitHubClient() {
546
+ cachedDefaultClient ??= createGitHubClient();
547
+ return cachedDefaultClient;
548
+ }
549
+ var cachedGhCliTokenRead, GitHubApiError, DEFAULT_TIMEOUT_MS, cachedDefaultClient;
550
+ var init_github_client = __esm({
551
+ "src/github-client.ts"() {
552
+ "use strict";
553
+ init_cli_shared();
554
+ GitHubApiError = class extends Error {
555
+ status;
556
+ stderr;
557
+ graphqlErrors;
558
+ /** #4588: true when GitHub refused THIS call for budget (primary or secondary rate limit),
559
+ * read from the refusing response itself — never the rate_limit endpoint. */
560
+ rateLimited;
561
+ /** Epoch seconds when the exhausted pool resets (`X-Ratelimit-Reset` of the refusing response). */
562
+ rateLimitReset;
563
+ constructor(message2, opts = {}) {
564
+ super(message2);
565
+ this.name = "GitHubApiError";
566
+ this.status = opts.status ?? 0;
567
+ this.stderr = message2;
568
+ this.graphqlErrors = opts.graphqlErrors;
569
+ this.rateLimited = opts.rateLimited;
570
+ this.rateLimitReset = opts.rateLimitReset;
571
+ }
572
+ };
573
+ DEFAULT_TIMEOUT_MS = 2e4;
574
+ }
575
+ });
576
+
577
+ // src/fetch-retry.ts
578
+ async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
579
+ const attempts = opts.attempts ?? 3;
580
+ const baseDelayMs = opts.baseDelayMs ?? 250;
581
+ const retryOn = opts.retryOn ?? ((res) => res.status >= 500);
582
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
583
+ let lastErr;
584
+ for (let i = 0; i < attempts; i++) {
585
+ const isLast = i === attempts - 1;
586
+ const attemptInit = opts.timeoutMs ? { ...init, signal: AbortSignal.timeout(opts.timeoutMs) } : init;
587
+ try {
588
+ const res = await fetchImpl(url, attemptInit);
589
+ if (!isLast && retryOn(res)) {
590
+ await sleep2(baseDelayMs * 2 ** i);
591
+ continue;
592
+ }
593
+ return res;
594
+ } catch (e) {
595
+ lastErr = e;
596
+ if (isLast) throw e;
597
+ await sleep2(baseDelayMs * 2 ** i);
598
+ }
599
+ }
600
+ throw lastErr;
601
+ }
602
+ var init_fetch_retry = __esm({
603
+ "src/fetch-retry.ts"() {
604
+ "use strict";
605
+ }
606
+ });
607
+
608
+ // src/hub-auth.ts
609
+ function normalizeBaseUrl(baseUrl) {
610
+ return baseUrl.replace(/\/$/, "");
611
+ }
612
+ function tokenFingerprint(token) {
613
+ return (0, import_node_crypto.createHash)("sha256").update(token).digest("hex");
614
+ }
615
+ function defaultHubSessionCachePath(env = process.env) {
616
+ if (env.MMI_HUB_SESSION_CACHE) return env.MMI_HUB_SESSION_CACHE;
617
+ if (process.platform === "win32") {
618
+ const base2 = env.LOCALAPPDATA || (0, import_node_path3.join)((0, import_node_os.homedir)(), "AppData", "Local");
619
+ return (0, import_node_path3.join)(base2, "MMI Future", "mmi-cli", "hub-session.json");
620
+ }
621
+ const base = env.XDG_STATE_HOME || (0, import_node_path3.join)((0, import_node_os.homedir)(), ".local", "state");
622
+ return (0, import_node_path3.join)(base, "mmi-cli", "hub-session.json");
623
+ }
624
+ function roleFromToken(token) {
625
+ const part = token?.split(".")[1];
626
+ if (!part) return void 0;
627
+ try {
628
+ const claims = JSON.parse(Buffer.from(part, "base64url").toString("utf8"));
629
+ return claims.role === "owner" ? "owner" : void 0;
630
+ } catch {
631
+ return void 0;
632
+ }
633
+ }
634
+ function readCache(path2, apiUrl, now, githubTokenFingerprint) {
635
+ try {
636
+ const session = JSON.parse((0, import_node_fs3.readFileSync)(path2, "utf8"));
637
+ if (!session.token || !session.expiresAt || session.apiUrl !== apiUrl) return null;
638
+ if (session.githubTokenFingerprint !== githubTokenFingerprint) return null;
639
+ if (new Date(session.expiresAt).getTime() <= now.getTime() + REFRESH_WINDOW_MS) return null;
640
+ return { ...session, role: roleFromToken(session.token) };
641
+ } catch {
642
+ return null;
643
+ }
644
+ }
645
+ function writeCache(path2, session) {
646
+ (0, import_node_fs3.mkdirSync)((0, import_node_path3.dirname)(path2), { recursive: true });
647
+ const tmp = `${path2}.${process.pid}.${Date.now()}.tmp`;
648
+ (0, import_node_fs3.writeFileSync)(tmp, JSON.stringify(session, null, 2) + "\n", { encoding: "utf8", mode: 384 });
649
+ try {
650
+ (0, import_node_fs3.chmodSync)(tmp, 384);
651
+ } catch {
652
+ }
653
+ (0, import_node_fs3.renameSync)(tmp, path2);
654
+ try {
655
+ (0, import_node_fs3.chmodSync)(path2, 384);
656
+ } catch {
657
+ }
658
+ }
659
+ async function hubAuthSession(deps) {
660
+ if (!deps.baseUrl) return void 0;
661
+ const apiUrl = normalizeBaseUrl(deps.baseUrl);
662
+ const envToken = process.env.MMI_HUB_TOKEN;
663
+ if (envToken) {
664
+ return { ...{ token: envToken }, expiresAt: new Date(Date.now() + 30 * 60 * 1e3).toISOString(), apiUrl, githubTokenFingerprint: "env-override" };
665
+ }
666
+ const now = deps.now?.() ?? /* @__PURE__ */ new Date();
667
+ const cachePath = deps.cachePath ?? defaultHubSessionCachePath();
668
+ const ghToken = await deps.githubToken();
669
+ if (!ghToken) return void 0;
670
+ const githubTokenFingerprint = tokenFingerprint(ghToken);
671
+ const cached = readCache(cachePath, apiUrl, now, githubTokenFingerprint);
672
+ if (cached) return cached;
673
+ try {
674
+ const res = await fetchWithRetry(
675
+ deps.fetch ?? fetch,
676
+ `${apiUrl}/auth/session`,
677
+ { method: "POST", headers: { ...clientVersionHeaders(), Authorization: `Bearer ${ghToken}` } },
678
+ { attempts: EXCHANGE_ATTEMPTS, timeoutMs: EXCHANGE_TIMEOUT_MS }
679
+ );
680
+ if (!res.ok) return void 0;
681
+ const body = await res.json();
682
+ if (!body.token || !body.expiresAt) return void 0;
683
+ const session = {
684
+ token: body.token,
685
+ expiresAt: body.expiresAt,
686
+ login: typeof body.login === "string" ? body.login : void 0,
687
+ // #3513: from the SIGNED token, not the response body — one rule at every hop, so the cache
688
+ // read and the network read cannot disagree about what counts as an assertion.
689
+ role: roleFromToken(body.token),
690
+ apiUrl,
691
+ githubTokenFingerprint
692
+ };
693
+ try {
694
+ writeCache(cachePath, session);
695
+ } catch {
696
+ }
697
+ return session;
698
+ } catch {
699
+ return void 0;
700
+ }
701
+ }
702
+ async function hubAuthToken(deps) {
703
+ return (await hubAuthSession(deps))?.token;
704
+ }
705
+ var import_node_crypto, import_node_fs3, import_node_path3, import_node_os, REFRESH_WINDOW_MS, EXCHANGE_TIMEOUT_MS, EXCHANGE_ATTEMPTS;
706
+ var init_hub_auth = __esm({
707
+ "src/hub-auth.ts"() {
708
+ "use strict";
709
+ import_node_crypto = require("node:crypto");
710
+ import_node_fs3 = require("node:fs");
711
+ import_node_path3 = require("node:path");
712
+ import_node_os = require("node:os");
713
+ init_fetch_retry();
714
+ init_client_version();
715
+ REFRESH_WINDOW_MS = 10 * 60 * 1e3;
716
+ EXCHANGE_TIMEOUT_MS = 8e3;
717
+ EXCHANGE_ATTEMPTS = 2;
718
+ }
719
+ });
720
+
721
+ // src/house-map.ts
722
+ function isHouseRoot(token) {
723
+ return HOUSE_ROOTS.includes(token);
724
+ }
725
+ function isDeclaredHouseAlias(houseToken, lookupPath) {
726
+ const segments = lookupPath.split(" ");
727
+ for (let end = segments.length; end > 0; end -= 1) {
728
+ const key = segments.slice(0, end).join(" ");
729
+ const aliases = HOUSE_ALIASES[key];
730
+ if (aliases) return aliases.includes(houseToken);
731
+ }
732
+ return false;
733
+ }
734
+ function houseForPath(path2) {
735
+ if (path2 === "") return "core";
736
+ const segments = path2.split(" ");
737
+ for (let end = segments.length; end > 0; end -= 1) {
738
+ const key = segments.slice(0, end).join(" ");
739
+ const house = HOUSE_MAP[key];
740
+ if (house) return house;
741
+ }
742
+ return void 0;
743
+ }
744
+ function canonicalPathFor(path2) {
745
+ const house = houseForPath(path2);
746
+ if (!house) return void 0;
747
+ return house === "core" ? path2 : `${house} ${path2}`;
748
+ }
749
+ function canonicalArgvFor(args) {
750
+ const lookup = args.filter((tok) => !tok.startsWith("-")).slice(0, 2).join(" ");
751
+ const house = houseForPath(lookup);
752
+ return house && house !== "core" ? [house, ...args] : args;
753
+ }
754
+ var HOUSE_ROOTS, COMPAT_SHIM_DEATH_WAVE, FLAT_ALIAS_CUT_NOTE, HOUSE_QUESTIONS, HOUSE_MAP, HOUSE_ALIASES;
755
+ var init_house_map = __esm({
756
+ "src/house-map.ts"() {
757
+ "use strict";
758
+ HOUSE_ROOTS = ["oracle", "harbour", "devops", "vault", "learning"];
759
+ COMPAT_SHIM_DEATH_WAVE = "Wave 3";
760
+ FLAT_ALIAS_CUT_NOTE = `flat Wave 0 alias \u2014 removed in ${COMPAT_SHIM_DEATH_WAVE} (#4316, #4438); only the canonical house-prefixed path parses`;
761
+ HOUSE_QUESTIONS = {
762
+ oracle: "Is it live?",
763
+ harbour: "Is it bounded?",
764
+ devops: "Is it legal?",
765
+ vault: "Is it guarded?",
766
+ learning: "Did it learn?",
767
+ core: "Is it one door?"
768
+ };
769
+ HOUSE_MAP = {
770
+ // --- oracle — live truth ------------------------------------------------------------------------
771
+ board: "oracle",
772
+ // board management (read + the guarded mutations that keep it live)
773
+ issue: "oracle",
774
+ // issues are live org truth
775
+ wave: "oracle",
776
+ // read-side multi-worktree board visibility (`wave status`)
777
+ next: "oracle",
778
+ // read-side board: the next actionable item
779
+ docs: "oracle",
780
+ // generated docs surfaces — the live org knowledge routing index
781
+ org: "oracle",
782
+ // org projects/registry/access reads (subgroup overrides below)
783
+ "org project": "oracle",
784
+ // registry projections (declared + projected live truth)
785
+ "org access": "oracle",
786
+ // org access role/audit reads
787
+ "org config": "oracle",
788
+ // live org configuration read
789
+ // --- harbour — declared lanes -------------------------------------------------------------------
790
+ "org schedules": "harbour",
791
+ // register/run/park schedule rows under the one lane contract
792
+ // --- devops — shipping --------------------------------------------------------------------------
793
+ pr: "devops",
794
+ ci: "devops",
795
+ // the CI/gate audit
796
+ rcand: "devops",
797
+ release: "devops",
798
+ hotfix: "devops",
799
+ train: "devops",
800
+ "wave land": "devops",
801
+ // the write-side serial merge train is shipping, not board observation
802
+ bootstrap: "devops",
803
+ // repo provisioning + propagate
804
+ runtime: "devops",
805
+ // tenant/deploy/box/edge — shipping and central deploy
806
+ "org rules": "devops",
807
+ // org-managed repository rule delivery (.gitignore)
808
+ // --- vault — secrets ----------------------------------------------------------------------------
809
+ secrets: "vault",
810
+ "org oauth": "vault",
811
+ // OAuth credential planning/set/verify
812
+ // --- learning — self-improvement ----------------------------------------------------------------
813
+ report: "learning",
814
+ // friction reports
815
+ "skill-lesson": "learning",
816
+ "closure-rate": "learning",
817
+ // closure rate per loop kind, computed at read (#4440)
818
+ pickup: "learning",
819
+ // cloud-agent adoption gate (#5708)
820
+ // --- core — the front door itself ---------------------------------------------------------------
821
+ commands: "core",
822
+ whoami: "core",
823
+ spawn: "core",
824
+ // this repo's process-spawn contract
825
+ tests: "core",
826
+ // this repo's test-policy contract
827
+ dist: "core",
828
+ // this repo's committed dist/BOM drift receipt (#5576)
829
+ doctor: "core",
830
+ stage: "core",
831
+ plugin: "core",
832
+ // plugin lifecycle + guards (CLI house owns plugins)
833
+ explain: "core",
834
+ // command-surface help
835
+ status: "core",
836
+ // repo-orientation snapshot (front door — torn toward oracle, kept core)
837
+ onboard: "core"
838
+ // repo-readiness orientation (front door — torn toward oracle, kept core)
839
+ };
840
+ HOUSE_ALIASES = {
841
+ issue: ["devops"],
842
+ // filing/managing issues is oracle, but reads as shipping work
843
+ "issue create": ["devops"]
844
+ };
845
+ }
846
+ });
847
+
848
+ // src/stdin-inject.ts
849
+ function stdinHasPipedInput(statFd = () => (0, import_node_fs4.fstatSync)(0), getIsTTY = () => process.stdin.isTTY) {
850
+ try {
851
+ const stat4 = statFd();
852
+ if (stat4.isFIFO() || stat4.isFile()) return true;
853
+ if (stat4.isCharacterDevice()) return false;
854
+ if (stat4.isSocket()) return false;
855
+ if (getIsTTY() === true) return false;
856
+ return true;
857
+ } catch {
858
+ return false;
859
+ }
860
+ }
861
+ async function readStdin(opts = {}) {
862
+ if (injectedStdin !== void 0) return injectedStdin;
863
+ if (!stdinHasPipedInput()) return "";
864
+ const maxBytes = opts.maxBytes ?? STDIN_MAX_BYTES;
865
+ const timeoutMs = opts.timeoutMs ?? STDIN_DRAIN_TIMEOUT_MS;
866
+ const chunks = [];
867
+ let total = 0;
868
+ const drain = (async () => {
869
+ for await (const chunk of process.stdin) {
870
+ const buf = chunk;
871
+ const room = maxBytes - total;
872
+ if (buf.length >= room) {
873
+ chunks.push(buf.subarray(0, room));
874
+ total = maxBytes;
875
+ break;
876
+ }
877
+ chunks.push(buf);
878
+ total += buf.length;
879
+ }
880
+ })().catch(() => {
881
+ });
882
+ let timer;
883
+ const timeout = new Promise((resolve7) => {
884
+ timer = setTimeout(resolve7, timeoutMs);
885
+ });
886
+ try {
887
+ await Promise.race([drain, timeout]);
888
+ } finally {
889
+ if (timer) clearTimeout(timer);
890
+ try {
891
+ process.stdin.unref();
892
+ } catch {
893
+ }
894
+ }
895
+ return Buffer.concat(chunks).toString("utf8");
896
+ }
897
+ var import_node_fs4, injectedStdin, STDIN_MAX_BYTES, STDIN_DRAIN_TIMEOUT_MS;
898
+ var init_stdin_inject = __esm({
899
+ "src/stdin-inject.ts"() {
900
+ "use strict";
901
+ import_node_fs4 = require("node:fs");
902
+ STDIN_MAX_BYTES = 8 * 1024 * 1024;
903
+ STDIN_DRAIN_TIMEOUT_MS = 5e3;
904
+ }
905
+ });
906
+
907
+ // src/cli-shared.ts
908
+ function killProcessTree(pid) {
909
+ try {
910
+ if (process.platform === "win32") {
911
+ (0, import_node_child_process2.execFileSync)("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
912
+ } else {
913
+ process.kill(pid, "SIGKILL");
914
+ }
915
+ return true;
916
+ } catch {
917
+ return false;
918
+ }
919
+ }
920
+ function execFileHard(file, args, options) {
921
+ const { timeout, step, ...rest } = options;
922
+ const started = Date.now();
923
+ return new Promise((resolve7, reject) => {
924
+ const child2 = (0, import_node_child_process2.execFile)(file, args, { encoding: "utf8", windowsHide: true, ...rest, timeout: 0 }, (error, stdout, stderr) => {
925
+ clearTimeout(timer);
926
+ if (expired) return;
927
+ if (error) reject(error);
928
+ else resolve7({ stdout: String(stdout), stderr: String(stderr) });
929
+ });
930
+ let expired = false;
931
+ const timer = setTimeout(() => {
932
+ expired = true;
933
+ const killed = child2.pid ? killProcessTree(child2.pid) : false;
934
+ child2.stdout?.destroy();
935
+ child2.stderr?.destroy();
936
+ child2.unref();
937
+ reject(new ExecDeadlineError(step, timeout, Date.now() - started, killed));
938
+ }, timeout);
939
+ timer.unref?.();
940
+ });
941
+ }
942
+ async function githubLogin() {
943
+ cachedGithubLogin ??= execFileP("gh", ["api", "user", "--jq", ".login"]).then(({ stdout }) => stdout.trim() || void 0).catch(() => void 0);
944
+ return cachedGithubLogin;
945
+ }
946
+ async function hubHeaders(extra = {}) {
947
+ const cfg = await loadConfig();
948
+ const t = await hubAuthToken({ baseUrl: cfg.sagaApiUrl ?? defaultHubUrl(), githubToken });
949
+ const base = { ...clientVersionHeaders(), ...extra };
950
+ return t ? { ...base, Authorization: `Bearer ${t}` } : base;
951
+ }
952
+ async function loadConfig() {
953
+ return { sagaApiUrl: defaultHubUrl() };
954
+ }
955
+ async function originRemoteUrl() {
956
+ return gitOut(["config", "--get", "remote.origin.url"]);
957
+ }
958
+ async function isOrgRepoRoot(readOrigin = originRemoteUrl) {
959
+ return /[:/]mutmutco\//i.test(await readOrigin());
960
+ }
961
+ async function repoProject(cfg) {
962
+ const remote = await gitOut(["remote", "get-url", "origin"]);
963
+ const repo = remote.replace(/\.git$/, "").split("/").pop() || "-";
964
+ return cfg.project || repo;
965
+ }
966
+ function argvWantsJson() {
967
+ return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
968
+ }
969
+ function argvWantsMachineFailure() {
970
+ return argvWantsJson() || process.argv.some((a) => a === "--validate-only" || a === "--dry-run");
971
+ }
972
+ function commandFromFailMessage(msg) {
973
+ const head = msg.split(":", 1)[0].trim();
974
+ if (!head || head.startsWith("-")) return "mmi-cli";
975
+ if (!/^[a-z][a-z0-9-]*(?: [a-z][a-z0-9-]*){0,5}$/.test(head)) return "mmi-cli";
976
+ return canonicalPathFor(head) ?? head;
977
+ }
978
+ function canonicalFailMessage(msg) {
979
+ const colon = msg.indexOf(":");
980
+ if (colon < 0) return msg;
981
+ const command = commandFromFailMessage(msg);
982
+ return command === "mmi-cli" ? msg : `${command}${msg.slice(colon)}`;
983
+ }
984
+ function fail(msg, payload) {
985
+ const json = argvWantsMachineFailure();
986
+ const canonicalMsg = canonicalFailMessage(msg);
987
+ if (payload && json) {
988
+ console.error(formatErrorEnvelope(canonicalMsg, payload));
989
+ } else {
990
+ if (!json) console.error(`run: mmi-cli explain ${commandFromFailMessage(msg)}`);
991
+ console.error(`mmi-cli ${canonicalMsg}`);
992
+ }
993
+ hardExit(1);
994
+ }
995
+ async function failGracefulEnvelope(msg, payload) {
996
+ if (payload && argvWantsMachineFailure()) {
997
+ console.error(formatErrorEnvelope(msg, payload));
998
+ return cleanExit(1);
999
+ }
1000
+ return failGraceful(msg);
1001
+ }
1002
+ async function planMutation(opts, args, commandName, planFn) {
1003
+ const validateOnly = opts.validateOnly === true;
1004
+ const dryRun = opts.dryRun === true;
1005
+ if (!validateOnly && !dryRun) return null;
1006
+ const planned = planFn ? await planFn(opts, args) : { command: commandName, args };
1007
+ return validateOnly ? { ok: true, planned } : { dry_run: true, planned };
1008
+ }
1009
+ function commandPath(cmd) {
1010
+ const parts = [];
1011
+ let node = cmd;
1012
+ while (node && node.parent) {
1013
+ parts.unshift(node.name());
1014
+ node = node.parent;
1015
+ }
1016
+ return parts.join(" ") || cmd.name();
1017
+ }
1018
+ function jsonParity(cmd) {
1019
+ if (!cmd.options.some((o) => o.long === "--json")) {
1020
+ cmd.option("--json", "machine-readable output (default; accepted for parity)");
1021
+ }
1022
+ return cmd;
1023
+ }
1024
+ function mutating(cmd, planFn) {
1025
+ const marker = cmd;
1026
+ if (marker.__mmiMutating) return cmd;
1027
+ marker.__mmiMutating = true;
1028
+ if (!cmd.options.some((o) => o.long === "--json")) cmd.option("--json", "machine-readable output");
1029
+ cmd.option("--dry-run", "resolve + validate, print the planned action as JSON, and exit without writing");
1030
+ cmd.option("--validate-only", "validate flags/enums/refs, print {ok,planned} or the C2 error envelope, and exit without writing");
1031
+ const registerAction = cmd.action.bind(cmd);
1032
+ cmd.action = ((handler) => registerAction(async (...actionArgs) => {
1033
+ const command = actionArgs[actionArgs.length - 1];
1034
+ const opts = command.opts();
1035
+ const positionals = actionArgs.slice(0, -2);
1036
+ const out = await planMutation(opts, positionals, commandPath(command), planFn);
1037
+ if (out) {
1038
+ console.log(JSON.stringify(out));
1039
+ return;
1040
+ }
1041
+ return handler(...actionArgs);
1042
+ }));
1043
+ return cmd;
1044
+ }
1045
+ function installProcessBackstop() {
1046
+ process.on("unhandledRejection", (reason) => void failGraceful(reason instanceof Error ? reason.message : String(reason)));
1047
+ process.on("uncaughtException", (err) => void failGraceful(err instanceof Error ? err.message : String(err)));
1048
+ }
1049
+ function rawFlag(flag) {
1050
+ return process.argv.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
1051
+ }
1052
+ function rawValue(flag, fallback) {
1053
+ const index = process.argv.indexOf(flag);
1054
+ if (index >= 0 && process.argv[index + 1]) return process.argv[index + 1];
1055
+ const inline = process.argv.find((arg) => arg.startsWith(`${flag}=`));
1056
+ return inline ? inline.slice(flag.length + 1) : fallback;
1057
+ }
1058
+ function printLine(value) {
1059
+ (0, import_node_fs5.writeSync)(1, `${value}
1060
+ `);
1061
+ }
1062
+ var import_node_child_process2, import_node_fs5, import_node_util3, rawExecFileP, DEFAULT_EXEC_TIMEOUT_MS, execFileP, GIT_TIMEOUT_MS, ExecDeadlineError, cachedGithubLogin, gitOut, consoleIo;
1063
+ var init_cli_shared = __esm({
1064
+ "src/cli-shared.ts"() {
1065
+ "use strict";
1066
+ import_node_child_process2 = require("node:child_process");
1067
+ import_node_fs5 = require("node:fs");
1068
+ import_node_util3 = require("node:util");
1069
+ init_hub_url();
1070
+ init_client_version();
1071
+ init_clean_exit();
1072
+ init_error_codes();
1073
+ init_github_client();
1074
+ init_hub_auth();
1075
+ init_house_map();
1076
+ init_stdin_inject();
1077
+ rawExecFileP = (0, import_node_util3.promisify)(import_node_child_process2.execFile);
1078
+ DEFAULT_EXEC_TIMEOUT_MS = 1e4;
1079
+ execFileP = (file, args, options = {}) => (
1080
+ // encoding 'utf8' guarantees string stdout/stderr at runtime; the cast pins the type because
1081
+ // promisify(execFile)'s overloads widen to string|Buffer when options is spread in.
1082
+ rawExecFileP(file, args, { encoding: "utf8", windowsHide: true, timeout: DEFAULT_EXEC_TIMEOUT_MS, killSignal: "SIGTERM", ...options })
1083
+ );
1084
+ GIT_TIMEOUT_MS = DEFAULT_EXEC_TIMEOUT_MS;
1085
+ ExecDeadlineError = class extends Error {
1086
+ constructor(step, timeoutMs, elapsedMs, killed) {
1087
+ super(
1088
+ `\`${step}\` did not finish within ${Math.round(timeoutMs / 1e3)}s (gave up after ${Math.round(elapsedMs / 1e3)}s). It stopped responding and did not exit when asked to, so the timeout alone could not end it; ${killed ? "its process tree was force-terminated" : "its process tree could NOT be terminated and may still be running"}.`
1089
+ );
1090
+ this.step = step;
1091
+ this.timeoutMs = timeoutMs;
1092
+ this.elapsedMs = elapsedMs;
1093
+ this.killed = killed;
1094
+ this.name = "ExecDeadlineError";
1095
+ }
1096
+ step;
1097
+ timeoutMs;
1098
+ elapsedMs;
1099
+ killed;
1100
+ };
1101
+ gitOut = async (args) => {
1102
+ try {
1103
+ return (await execFileP("git", [...args])).stdout.trim();
1104
+ } catch {
1105
+ return "";
1106
+ }
1107
+ };
1108
+ consoleIo = { log: (m) => console.log(m), err: (m) => console.error(m) };
1109
+ }
1110
+ });
1111
+
1112
+ // src/board-write.ts
1113
+ var board_write_exports = {};
1114
+ __export(board_write_exports, {
1115
+ writeHubBoardField: () => writeHubBoardField
1116
+ });
1117
+ async function writeHubBoardField(request) {
1118
+ const config = await loadConfig();
1119
+ const res = await fetch(`${config.sagaApiUrl?.replace(/\/$/, "")}/board/field`, {
1120
+ method: "POST",
1121
+ headers: await hubHeaders({ "content-type": "application/json" }),
1122
+ body: JSON.stringify(request),
1123
+ signal: AbortSignal.timeout(25e3)
1124
+ });
1125
+ const body = await res.json().catch(() => null);
1126
+ if (!res.ok || body?.updated !== true) throw new Error(`board field write HTTP ${res.status}: ${body?.error ?? "unconfirmed result"}`);
1127
+ }
1128
+ var init_board_write = __esm({
1129
+ "src/board-write.ts"() {
1130
+ "use strict";
1131
+ init_cli_shared();
1132
+ }
1133
+ });
1134
+
1135
+ // src/index.ts
1136
+ var index_exports = {};
1137
+ __export(index_exports, {
1138
+ DEFAULT_PRIORITY: () => DEFAULT_PRIORITY,
1139
+ argvWantsJson: () => argvWantsJson3,
1140
+ awsCallerArn: () => awsCallerArn,
1141
+ ciAuditDeps: () => ciAuditDeps2,
1142
+ classifyParseError: () => classifyParseError,
1143
+ commandOwnLongFlags: () => commandOwnLongFlags,
1144
+ envHealLockPath: () => envHealLockPath,
1145
+ isOrgRegisteredRepo: () => isOrgRegisteredRepo,
1146
+ parseInvalidChoiceError: () => parseInvalidChoiceError,
1147
+ positionalTargetForm: () => positionalTargetForm,
1148
+ registryClientDeps: () => registryClientDeps,
1149
+ repoSlug: () => repoSlug,
1150
+ suggestCommandPath: () => suggestCommandPath,
1151
+ unknownCommandCandidates: () => unknownCommandCandidates
1152
+ });
1153
+ module.exports = __toCommonJS(index_exports);
1154
+
1155
+ // node_modules/commander/lib/error.js
1156
+ var CommanderError = class extends Error {
1157
+ /**
1158
+ * Constructs the CommanderError class
1159
+ * @param {number} exitCode suggested exit code which could be used with process.exit
1160
+ * @param {string} code an id string representing the error
1161
+ * @param {string} message human-readable description of the error
1162
+ */
1163
+ constructor(exitCode, code, message2) {
1164
+ super(message2);
1165
+ Error.captureStackTrace(this, this.constructor);
1166
+ this.name = this.constructor.name;
1167
+ this.code = code;
1168
+ this.exitCode = exitCode;
1169
+ this.nestedError = void 0;
1170
+ }
1171
+ };
1172
+ var InvalidArgumentError = class extends CommanderError {
1173
+ /**
1174
+ * Constructs the InvalidArgumentError class
1175
+ * @param {string} [message] explanation of why argument is invalid
1176
+ */
1177
+ constructor(message2) {
1178
+ super(1, "commander.invalidArgument", message2);
1179
+ Error.captureStackTrace(this, this.constructor);
1180
+ this.name = this.constructor.name;
1181
+ }
1182
+ };
1183
+
1184
+ // node_modules/commander/lib/argument.js
1185
+ var Argument = class {
1186
+ /**
1187
+ * Initialize a new command argument with the given name and description.
1188
+ * The default is that the argument is required, and you can explicitly
1189
+ * indicate this with <> around the name. Put [] around the name for an optional argument.
1190
+ *
1191
+ * @param {string} name
1192
+ * @param {string} [description]
1193
+ */
1194
+ constructor(name, description) {
1195
+ this.description = description || "";
1196
+ this.variadic = false;
1197
+ this.parseArg = void 0;
1198
+ this.defaultValue = void 0;
1199
+ this.defaultValueDescription = void 0;
1200
+ this.argChoices = void 0;
1201
+ switch (name[0]) {
1202
+ case "<":
1203
+ this.required = true;
1204
+ this._name = name.slice(1, -1);
1205
+ break;
1206
+ case "[":
1207
+ this.required = false;
1208
+ this._name = name.slice(1, -1);
1209
+ break;
1210
+ default:
1211
+ this.required = true;
1212
+ this._name = name;
1213
+ break;
1214
+ }
1215
+ if (this._name.endsWith("...")) {
1216
+ this.variadic = true;
1217
+ this._name = this._name.slice(0, -3);
1218
+ }
1219
+ }
1220
+ /**
1221
+ * Return argument name.
1222
+ *
1223
+ * @return {string}
1224
+ */
1225
+ name() {
1226
+ return this._name;
1227
+ }
1228
+ /**
1229
+ * @package
1230
+ */
1231
+ _collectValue(value, previous) {
1232
+ if (previous === this.defaultValue || !Array.isArray(previous)) {
1233
+ return [value];
1234
+ }
1235
+ previous.push(value);
1236
+ return previous;
1237
+ }
1238
+ /**
1239
+ * Set the default value, and optionally supply the description to be displayed in the help.
1240
+ *
1241
+ * @param {*} value
1242
+ * @param {string} [description]
1243
+ * @return {Argument}
1244
+ */
1245
+ default(value, description) {
1246
+ this.defaultValue = value;
1247
+ this.defaultValueDescription = description;
1248
+ return this;
1249
+ }
1250
+ /**
1251
+ * Set the custom handler for processing CLI command arguments into argument values.
1252
+ *
1253
+ * @param {Function} [fn]
1254
+ * @return {Argument}
1255
+ */
1256
+ argParser(fn) {
1257
+ this.parseArg = fn;
1258
+ return this;
1259
+ }
1260
+ /**
1261
+ * Only allow argument value to be one of choices.
1262
+ *
1263
+ * @param {string[]} values
1264
+ * @return {Argument}
1265
+ */
1266
+ choices(values) {
1267
+ this.argChoices = values.slice();
1268
+ this.parseArg = (arg, previous) => {
1269
+ if (!this.argChoices.includes(arg)) {
1270
+ throw new InvalidArgumentError(
1271
+ `Allowed choices are ${this.argChoices.join(", ")}.`
1272
+ );
1273
+ }
1274
+ if (this.variadic) {
1275
+ return this._collectValue(arg, previous);
1276
+ }
1277
+ return arg;
1278
+ };
1279
+ return this;
1280
+ }
1281
+ /**
1282
+ * Make argument required.
1283
+ *
1284
+ * @returns {Argument}
1285
+ */
1286
+ argRequired() {
1287
+ this.required = true;
1288
+ return this;
1289
+ }
1290
+ /**
1291
+ * Make argument optional.
1292
+ *
1293
+ * @returns {Argument}
1294
+ */
1295
+ argOptional() {
1296
+ this.required = false;
1297
+ return this;
1298
+ }
1299
+ };
1300
+ function humanReadableArgName(arg) {
1301
+ const nameOutput = arg.name() + (arg.variadic === true ? "..." : "");
1302
+ return arg.required ? "<" + nameOutput + ">" : "[" + nameOutput + "]";
1303
+ }
1304
+
1305
+ // node_modules/commander/lib/command.js
1306
+ var import_node_events = require("node:events");
1307
+ var import_node_child_process = __toESM(require("node:child_process"), 1);
1308
+ var import_node_path = __toESM(require("node:path"), 1);
1309
+ var import_node_fs = __toESM(require("node:fs"), 1);
1310
+ var import_node_process = __toESM(require("node:process"), 1);
1311
+ var import_node_util2 = require("node:util");
1312
+
1313
+ // node_modules/commander/lib/help.js
1314
+ var import_node_util = require("node:util");
1315
+ var Help = class {
1316
+ constructor() {
1317
+ this.helpWidth = void 0;
1318
+ this.minWidthToWrap = 40;
1319
+ this.sortSubcommands = false;
1320
+ this.sortOptions = false;
1321
+ this.showGlobalOptions = false;
1322
+ }
1323
+ /**
1324
+ * prepareContext is called by Commander after applying overrides from `Command.configureHelp()`
1325
+ * and just before calling `formatHelp()`.
1326
+ *
1327
+ * Commander just uses the helpWidth and the rest is provided for optional use by more complex subclasses.
1328
+ *
1329
+ * @param {{ error?: boolean, helpWidth?: number, outputHasColors?: boolean }} contextOptions
1330
+ */
1331
+ prepareContext(contextOptions) {
1332
+ this.helpWidth = this.helpWidth ?? contextOptions.helpWidth ?? 80;
1333
+ }
1334
+ /**
1335
+ * Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one.
1336
+ *
1337
+ * @param {Command} cmd
1338
+ * @returns {Command[]}
1339
+ */
1340
+ visibleCommands(cmd) {
1341
+ const visibleCommands = cmd.commands.filter((cmd2) => !cmd2._hidden);
1342
+ const helpCommand = cmd._getHelpCommand();
1343
+ if (helpCommand && !helpCommand._hidden) {
1344
+ visibleCommands.push(helpCommand);
1345
+ }
1346
+ if (this.sortSubcommands) {
1347
+ visibleCommands.sort((a, b) => {
1348
+ return a.name().localeCompare(b.name());
1349
+ });
1350
+ }
1351
+ return visibleCommands;
1352
+ }
1353
+ /**
1354
+ * Compare options for sort.
1355
+ *
1356
+ * @param {Option} a
1357
+ * @param {Option} b
1358
+ * @returns {number}
1359
+ */
1360
+ compareOptions(a, b) {
1361
+ const getSortKey = (option) => {
1362
+ return option.short ? option.short.replace(/^-/, "") : option.long.replace(/^--/, "");
1363
+ };
1364
+ return getSortKey(a).localeCompare(getSortKey(b));
1365
+ }
1366
+ /**
1367
+ * Get an array of the visible options. Includes a placeholder for the implicit help option, if there is one.
1368
+ *
1369
+ * @param {Command} cmd
1370
+ * @returns {Option[]}
1371
+ */
1372
+ visibleOptions(cmd) {
1373
+ const visibleOptions = cmd.options.filter((option) => !option.hidden);
1374
+ const helpOption = cmd._getHelpOption();
1375
+ if (helpOption && !helpOption.hidden) {
1376
+ const removeShort = helpOption.short && cmd._findOption(helpOption.short);
1377
+ const removeLong = helpOption.long && cmd._findOption(helpOption.long);
1378
+ if (!removeShort && !removeLong) {
1379
+ visibleOptions.push(helpOption);
1380
+ } else if (helpOption.long && !removeLong) {
1381
+ visibleOptions.push(
1382
+ cmd.createOption(helpOption.long, helpOption.description)
1383
+ );
1384
+ } else if (helpOption.short && !removeShort) {
1385
+ visibleOptions.push(
1386
+ cmd.createOption(helpOption.short, helpOption.description)
1387
+ );
1388
+ }
1389
+ }
1390
+ if (this.sortOptions) {
1391
+ visibleOptions.sort(this.compareOptions);
1392
+ }
1393
+ return visibleOptions;
1394
+ }
1395
+ /**
1396
+ * Get an array of the visible global options. (Not including help.)
1397
+ *
1398
+ * @param {Command} cmd
1399
+ * @returns {Option[]}
1400
+ */
1401
+ visibleGlobalOptions(cmd) {
1402
+ if (!this.showGlobalOptions) return [];
1403
+ const globalOptions = [];
1404
+ for (let ancestorCmd = cmd.parent; ancestorCmd; ancestorCmd = ancestorCmd.parent) {
1405
+ const visibleOptions = ancestorCmd.options.filter(
1406
+ (option) => !option.hidden
1407
+ );
1408
+ globalOptions.push(...visibleOptions);
1409
+ }
1410
+ if (this.sortOptions) {
1411
+ globalOptions.sort(this.compareOptions);
1412
+ }
1413
+ return globalOptions;
1414
+ }
1415
+ /**
1416
+ * Get an array of the arguments if any have a description.
1417
+ *
1418
+ * @param {Command} cmd
1419
+ * @returns {Argument[]}
1420
+ */
1421
+ visibleArguments(cmd) {
1422
+ if (cmd._argsDescription) {
1423
+ cmd.registeredArguments.forEach((argument) => {
1424
+ argument.description = argument.description || cmd._argsDescription[argument.name()] || "";
1425
+ });
1426
+ }
1427
+ if (cmd.registeredArguments.find((argument) => argument.description)) {
1428
+ return cmd.registeredArguments;
1429
+ }
1430
+ return [];
1431
+ }
1432
+ /**
1433
+ * Get the command term to show in the list of subcommands.
1434
+ *
330
1435
  * @param {Command} cmd
331
1436
  * @returns {string}
332
1437
  */
@@ -2772,1661 +3877,654 @@ Expecting one of '${allowedValues.join("', '")}'`);
2772
3877
  }
2773
3878
  const config = errorOptions || {};
2774
3879
  const exitCode = config.exitCode || 1;
2775
- const code = config.code || "commander.error";
2776
- this._exit(exitCode, code, message2);
2777
- }
2778
- /**
2779
- * Apply any option related environment variables, if option does
2780
- * not have a value from cli or client code.
2781
- *
2782
- * @private
2783
- */
2784
- _parseOptionsEnv() {
2785
- this.options.forEach((option) => {
2786
- if (option.envVar && option.envVar in import_node_process.default.env) {
2787
- const optionKey = option.attributeName();
2788
- if (this.getOptionValue(optionKey) === void 0 || ["default", "config", "env"].includes(
2789
- this.getOptionValueSource(optionKey)
2790
- )) {
2791
- if (option.required || option.optional) {
2792
- this.emit(`optionEnv:${option.name()}`, import_node_process.default.env[option.envVar]);
2793
- } else {
2794
- this.emit(`optionEnv:${option.name()}`);
2795
- }
2796
- }
2797
- }
2798
- });
2799
- }
2800
- /**
2801
- * Apply any implied option values, if option is undefined or default value.
2802
- *
2803
- * @private
2804
- */
2805
- _parseOptionsImplied() {
2806
- const dualHelper = new DualOptions(this.options);
2807
- const hasCustomOptionValue = (optionKey) => {
2808
- return this.getOptionValue(optionKey) !== void 0 && !["default", "implied"].includes(this.getOptionValueSource(optionKey));
2809
- };
2810
- this.options.filter(
2811
- (option) => option.implied !== void 0 && hasCustomOptionValue(option.attributeName()) && dualHelper.valueFromOption(
2812
- this.getOptionValue(option.attributeName()),
2813
- option
2814
- )
2815
- ).forEach((option) => {
2816
- Object.keys(option.implied).filter((impliedKey) => !hasCustomOptionValue(impliedKey)).forEach((impliedKey) => {
2817
- this.setOptionValueWithSource(
2818
- impliedKey,
2819
- option.implied[impliedKey],
2820
- "implied"
2821
- );
2822
- });
2823
- });
2824
- }
2825
- /**
2826
- * Argument `name` is missing.
2827
- *
2828
- * @param {string} name
2829
- * @private
2830
- */
2831
- missingArgument(name) {
2832
- const message2 = `error: missing required argument '${name}'`;
2833
- this.error(message2, { code: "commander.missingArgument" });
2834
- }
2835
- /**
2836
- * `Option` is missing an argument.
2837
- *
2838
- * @param {Option} option
2839
- * @private
2840
- */
2841
- optionMissingArgument(option) {
2842
- const message2 = `error: option '${option.flags}' argument missing`;
2843
- this.error(message2, { code: "commander.optionMissingArgument" });
2844
- }
2845
- /**
2846
- * `Option` does not have a value, and is a mandatory option.
2847
- *
2848
- * @param {Option} option
2849
- * @private
2850
- */
2851
- missingMandatoryOptionValue(option) {
2852
- const message2 = `error: required option '${option.flags}' not specified`;
2853
- this.error(message2, { code: "commander.missingMandatoryOptionValue" });
2854
- }
2855
- /**
2856
- * `Option` conflicts with another option.
2857
- *
2858
- * @param {Option} option
2859
- * @param {Option} conflictingOption
2860
- * @private
2861
- */
2862
- _conflictingOption(option, conflictingOption) {
2863
- const findBestOptionFromValue = (option2) => {
2864
- const optionKey = option2.attributeName();
2865
- const optionValue = this.getOptionValue(optionKey);
2866
- const negativeOption = this.options.find(
2867
- (target) => target.negate && optionKey === target.attributeName()
2868
- );
2869
- const positiveOption = this.options.find(
2870
- (target) => !target.negate && optionKey === target.attributeName()
2871
- );
2872
- if (negativeOption && (negativeOption.presetArg === void 0 && optionValue === false || negativeOption.presetArg !== void 0 && optionValue === negativeOption.presetArg)) {
2873
- return negativeOption;
2874
- }
2875
- return positiveOption || option2;
2876
- };
2877
- const getErrorMessage = (option2) => {
2878
- const bestOption = findBestOptionFromValue(option2);
2879
- const optionKey = bestOption.attributeName();
2880
- const source = this.getOptionValueSource(optionKey);
2881
- if (source === "env") {
2882
- return `environment variable '${bestOption.envVar}'`;
2883
- }
2884
- return `option '${bestOption.flags}'`;
2885
- };
2886
- const message2 = `error: ${getErrorMessage(option)} cannot be used with ${getErrorMessage(conflictingOption)}`;
2887
- this.error(message2, { code: "commander.conflictingOption" });
2888
- }
2889
- /**
2890
- * Unknown option `flag`.
2891
- *
2892
- * @param {string} flag
2893
- * @private
2894
- */
2895
- unknownOption(flag) {
2896
- if (this._allowUnknownOption) return;
2897
- let suggestion = "";
2898
- if (flag.startsWith("--") && this._showSuggestionAfterError) {
2899
- let candidateFlags = [];
2900
- let command = this;
2901
- do {
2902
- const moreFlags = command.createHelp().visibleOptions(command).filter((option) => option.long).map((option) => option.long);
2903
- candidateFlags = candidateFlags.concat(moreFlags);
2904
- command = command.parent;
2905
- } while (command && !command._enablePositionalOptions);
2906
- suggestion = suggestSimilar(flag, candidateFlags);
2907
- }
2908
- const message2 = `error: unknown option '${flag}'${suggestion}`;
2909
- this.error(message2, { code: "commander.unknownOption" });
2910
- }
2911
- /**
2912
- * Excess arguments, more than expected.
2913
- *
2914
- * @param {string[]} receivedArgs
2915
- * @private
2916
- */
2917
- _excessArguments(receivedArgs) {
2918
- if (this._allowExcessArguments) return;
2919
- const expected = this.registeredArguments.length;
2920
- const s = expected === 1 ? "" : "s";
2921
- const received = receivedArgs.length;
2922
- const forSubcommand = this.parent ? ` for '${this.name()}'` : "";
2923
- const details = receivedArgs.join(", ");
2924
- const message2 = `error: too many arguments${forSubcommand}. Expected ${expected} argument${s} but got ${received}: ${details}.`;
2925
- this.error(message2, { code: "commander.excessArguments" });
2926
- }
2927
- /**
2928
- * Unknown command.
2929
- *
2930
- * @private
2931
- */
2932
- unknownCommand() {
2933
- const unknownName = this.args[0];
2934
- let suggestion = "";
2935
- if (this._showSuggestionAfterError) {
2936
- const candidateNames = [];
2937
- this.createHelp().visibleCommands(this).forEach((command) => {
2938
- candidateNames.push(command.name());
2939
- if (command.alias()) candidateNames.push(command.alias());
2940
- });
2941
- suggestion = suggestSimilar(unknownName, candidateNames);
2942
- }
2943
- const message2 = `error: unknown command '${unknownName}'${suggestion}`;
2944
- this.error(message2, { code: "commander.unknownCommand" });
2945
- }
2946
- /**
2947
- * Get or set the program version.
2948
- *
2949
- * This method auto-registers the "-V, --version" option which will print the version number.
2950
- *
2951
- * You can optionally supply the flags and description to override the defaults.
2952
- *
2953
- * @param {string} [str]
2954
- * @param {string} [flags]
2955
- * @param {string} [description]
2956
- * @return {(this | string | undefined)} `this` command for chaining, or version string if no arguments
2957
- */
2958
- version(str, flags, description) {
2959
- if (str === void 0) return this._version;
2960
- this._version = str;
2961
- flags = flags || "-V, --version";
2962
- description = description || "output the version number";
2963
- const versionOption = this.createOption(flags, description);
2964
- this._versionOptionName = versionOption.attributeName();
2965
- this._registerOption(versionOption);
2966
- this.on("option:" + versionOption.name(), () => {
2967
- this._outputConfiguration.writeOut(`${str}
2968
- `);
2969
- this._exit(0, "commander.version", str);
2970
- });
2971
- return this;
2972
- }
2973
- /**
2974
- * Set the description.
2975
- *
2976
- * @param {string} [str]
2977
- * @param {object} [argsDescription]
2978
- * @return {(string|Command)}
2979
- */
2980
- description(str, argsDescription) {
2981
- if (str === void 0 && argsDescription === void 0)
2982
- return this._description;
2983
- this._description = str;
2984
- if (argsDescription) {
2985
- this._argsDescription = argsDescription;
2986
- }
2987
- return this;
2988
- }
2989
- /**
2990
- * Set the summary. Used when listed as subcommand of parent.
2991
- *
2992
- * @param {string} [str]
2993
- * @return {(string|Command)}
2994
- */
2995
- summary(str) {
2996
- if (str === void 0) return this._summary;
2997
- this._summary = str;
2998
- return this;
2999
- }
3000
- /**
3001
- * Set an alias for the command.
3002
- *
3003
- * You may call more than once to add multiple aliases. Only the first alias is shown in the auto-generated help.
3004
- *
3005
- * @param {string} [alias]
3006
- * @return {(string|Command)}
3007
- */
3008
- alias(alias) {
3009
- if (alias === void 0) return this._aliases[0];
3010
- let command = this;
3011
- if (this.commands.length !== 0 && this.commands[this.commands.length - 1]._executableHandler) {
3012
- command = this.commands[this.commands.length - 1];
3013
- }
3014
- if (alias === command._name)
3015
- throw new Error("Command alias can't be the same as its name");
3016
- const matchingCommand = this.parent?._findCommand(alias);
3017
- if (matchingCommand) {
3018
- const existingCmd = [matchingCommand.name()].concat(matchingCommand.aliases()).join("|");
3019
- throw new Error(
3020
- `cannot add alias '${alias}' to command '${this.name()}' as already have command '${existingCmd}'`
3021
- );
3022
- }
3023
- command._aliases.push(alias);
3024
- return this;
3025
- }
3026
- /**
3027
- * Set aliases for the command.
3028
- *
3029
- * Only the first alias is shown in the auto-generated help.
3030
- *
3031
- * @param {string[]} [aliases]
3032
- * @return {(string[]|Command)}
3033
- */
3034
- aliases(aliases) {
3035
- if (aliases === void 0) return this._aliases;
3036
- aliases.forEach((alias) => this.alias(alias));
3037
- return this;
3038
- }
3039
- /**
3040
- * Set / get the command usage `str`.
3041
- *
3042
- * @param {string} [str]
3043
- * @return {(string|Command)}
3044
- */
3045
- usage(str) {
3046
- if (str === void 0) {
3047
- if (this._usage) return this._usage;
3048
- const args = this.registeredArguments.map((arg) => {
3049
- return humanReadableArgName(arg);
3050
- });
3051
- return [].concat(
3052
- this.options.length || this._helpOption !== null ? "[options]" : [],
3053
- this.commands.length ? "[command]" : [],
3054
- this.registeredArguments.length ? args : []
3055
- ).join(" ");
3056
- }
3057
- this._usage = str;
3058
- return this;
3059
- }
3060
- /**
3061
- * Get or set the name of the command.
3062
- *
3063
- * @param {string} [str]
3064
- * @return {(string|Command)}
3065
- */
3066
- name(str) {
3067
- if (str === void 0) return this._name;
3068
- this._name = str;
3069
- return this;
3070
- }
3071
- /**
3072
- * Set/get the help group heading for this subcommand in parent command's help.
3073
- *
3074
- * @param {string} [heading]
3075
- * @return {Command | string}
3076
- */
3077
- helpGroup(heading) {
3078
- if (heading === void 0) return this._helpGroupHeading ?? "";
3079
- this._helpGroupHeading = heading;
3080
- return this;
3081
- }
3082
- /**
3083
- * Set/get the default help group heading for subcommands added to this command.
3084
- * (This does not override a group set directly on the subcommand using .helpGroup().)
3085
- *
3086
- * @example
3087
- * program.commandsGroup('Development Commands:);
3088
- * program.command('watch')...
3089
- * program.command('lint')...
3090
- * ...
3091
- *
3092
- * @param {string} [heading]
3093
- * @returns {Command | string}
3094
- */
3095
- commandsGroup(heading) {
3096
- if (heading === void 0) return this._defaultCommandGroup ?? "";
3097
- this._defaultCommandGroup = heading;
3098
- return this;
3099
- }
3100
- /**
3101
- * Set/get the default help group heading for options added to this command.
3102
- * (This does not override a group set directly on the option using .helpGroup().)
3103
- *
3104
- * @example
3105
- * program
3106
- * .optionsGroup('Development Options:')
3107
- * .option('-d, --debug', 'output extra debugging')
3108
- * .option('-p, --profile', 'output profiling information')
3109
- *
3110
- * @param {string} [heading]
3111
- * @returns {Command | string}
3112
- */
3113
- optionsGroup(heading) {
3114
- if (heading === void 0) return this._defaultOptionGroup ?? "";
3115
- this._defaultOptionGroup = heading;
3116
- return this;
3117
- }
3118
- /**
3119
- * @param {Option} option
3120
- * @private
3121
- */
3122
- _initOptionGroup(option) {
3123
- if (this._defaultOptionGroup && !option.helpGroupHeading)
3124
- option.helpGroup(this._defaultOptionGroup);
3125
- }
3126
- /**
3127
- * @param {Command} cmd
3128
- * @private
3129
- */
3130
- _initCommandGroup(cmd) {
3131
- if (this._defaultCommandGroup && !cmd.helpGroup())
3132
- cmd.helpGroup(this._defaultCommandGroup);
3133
- }
3134
- /**
3135
- * Set the name of the command from script filename, such as process.argv[1],
3136
- * or import.meta.filename.
3137
- *
3138
- * (Used internally and public although not documented in README.)
3139
- *
3140
- * @example
3141
- * program.nameFromFilename(import.meta.filename);
3142
- *
3143
- * @param {string} filename
3144
- * @return {Command}
3145
- */
3146
- nameFromFilename(filename) {
3147
- this._name = import_node_path.default.basename(filename, import_node_path.default.extname(filename));
3148
- return this;
3149
- }
3150
- /**
3151
- * Get or set the directory for searching for executable subcommands of this command.
3152
- *
3153
- * @example
3154
- * program.executableDir(import.meta.dirname);
3155
- * // or
3156
- * program.executableDir('subcommands');
3157
- *
3158
- * @param {string} [path]
3159
- * @return {(string|null|Command)}
3160
- */
3161
- executableDir(path2) {
3162
- if (path2 === void 0) return this._executableDir;
3163
- this._executableDir = path2;
3164
- return this;
3165
- }
3166
- /**
3167
- * Return program help documentation.
3168
- *
3169
- * @param {{ error: boolean }} [contextOptions] - pass {error:true} to wrap for stderr instead of stdout
3170
- * @return {string}
3171
- */
3172
- helpInformation(contextOptions) {
3173
- const helper = this.createHelp();
3174
- const context = this._getOutputContext(contextOptions);
3175
- helper.prepareContext({
3176
- error: context.error,
3177
- helpWidth: context.helpWidth,
3178
- outputHasColors: context.hasColors
3179
- });
3180
- const text = helper.formatHelp(this, helper);
3181
- if (context.hasColors) return text;
3182
- return this._outputConfiguration.stripColor(text);
3183
- }
3184
- /**
3185
- * @typedef HelpContext
3186
- * @type {object}
3187
- * @property {boolean} error
3188
- * @property {number} helpWidth
3189
- * @property {boolean} hasColors
3190
- * @property {function} write - includes stripColor if needed
3191
- *
3192
- * @returns {HelpContext}
3193
- * @private
3194
- */
3195
- _getOutputContext(contextOptions) {
3196
- contextOptions = contextOptions || {};
3197
- const error = !!contextOptions.error;
3198
- let baseWrite;
3199
- let hasColors;
3200
- let helpWidth;
3201
- if (error) {
3202
- baseWrite = (str) => this._outputConfiguration.writeErr(str);
3203
- hasColors = this._outputConfiguration.getErrHasColors();
3204
- helpWidth = this._outputConfiguration.getErrHelpWidth();
3205
- } else {
3206
- baseWrite = (str) => this._outputConfiguration.writeOut(str);
3207
- hasColors = this._outputConfiguration.getOutHasColors();
3208
- helpWidth = this._outputConfiguration.getOutHelpWidth();
3209
- }
3210
- const write = (str) => {
3211
- if (!hasColors) str = this._outputConfiguration.stripColor(str);
3212
- return baseWrite(str);
3213
- };
3214
- return { error, write, hasColors, helpWidth };
3215
- }
3216
- /**
3217
- * Output help information for this command.
3218
- *
3219
- * Outputs built-in help, and custom text added using `.addHelpText()`.
3220
- *
3221
- * @param {{ error: boolean } | Function} [contextOptions] - pass {error:true} to write to stderr instead of stdout
3222
- */
3223
- outputHelp(contextOptions) {
3224
- let deprecatedCallback;
3225
- if (typeof contextOptions === "function") {
3226
- deprecatedCallback = contextOptions;
3227
- contextOptions = void 0;
3228
- }
3229
- const outputContext = this._getOutputContext(contextOptions);
3230
- const eventContext = {
3231
- error: outputContext.error,
3232
- write: outputContext.write,
3233
- command: this
3234
- };
3235
- this._getCommandAndAncestors().reverse().forEach((command) => command.emit("beforeAllHelp", eventContext));
3236
- this.emit("beforeHelp", eventContext);
3237
- let helpInformation = this.helpInformation({ error: outputContext.error });
3238
- if (deprecatedCallback) {
3239
- helpInformation = deprecatedCallback(helpInformation);
3240
- if (typeof helpInformation !== "string" && !Buffer.isBuffer(helpInformation)) {
3241
- throw new Error("outputHelp callback must return a string or a Buffer");
3242
- }
3243
- }
3244
- outputContext.write(helpInformation);
3245
- if (this._getHelpOption()?.long) {
3246
- this.emit(this._getHelpOption().long);
3247
- }
3248
- this.emit("afterHelp", eventContext);
3249
- this._getCommandAndAncestors().forEach(
3250
- (command) => command.emit("afterAllHelp", eventContext)
3251
- );
3880
+ const code = config.code || "commander.error";
3881
+ this._exit(exitCode, code, message2);
3252
3882
  }
3253
3883
  /**
3254
- * You can pass in flags and a description to customise the built-in help option.
3255
- * Pass in false to disable the built-in help option.
3256
- *
3257
- * @example
3258
- * program.helpOption('-?, --help' 'show help'); // customise
3259
- * program.helpOption(false); // disable
3884
+ * Apply any option related environment variables, if option does
3885
+ * not have a value from cli or client code.
3260
3886
  *
3261
- * @param {(string | boolean)} flags
3262
- * @param {string} [description]
3263
- * @return {Command} `this` command for chaining
3887
+ * @private
3264
3888
  */
3265
- helpOption(flags, description) {
3266
- if (typeof flags === "boolean") {
3267
- if (flags) {
3268
- if (this._helpOption === null) this._helpOption = void 0;
3269
- if (this._defaultOptionGroup) {
3270
- this._initOptionGroup(this._getHelpOption());
3889
+ _parseOptionsEnv() {
3890
+ this.options.forEach((option) => {
3891
+ if (option.envVar && option.envVar in import_node_process.default.env) {
3892
+ const optionKey = option.attributeName();
3893
+ if (this.getOptionValue(optionKey) === void 0 || ["default", "config", "env"].includes(
3894
+ this.getOptionValueSource(optionKey)
3895
+ )) {
3896
+ if (option.required || option.optional) {
3897
+ this.emit(`optionEnv:${option.name()}`, import_node_process.default.env[option.envVar]);
3898
+ } else {
3899
+ this.emit(`optionEnv:${option.name()}`);
3900
+ }
3271
3901
  }
3272
- } else {
3273
- this._helpOption = null;
3274
3902
  }
3275
- return this;
3276
- }
3277
- this._helpOption = this.createOption(
3278
- flags ?? "-h, --help",
3279
- description ?? "display help for command"
3280
- );
3281
- if (flags || description) this._initOptionGroup(this._helpOption);
3282
- return this;
3903
+ });
3283
3904
  }
3284
3905
  /**
3285
- * Lazy create help option.
3286
- * Returns null if has been disabled with .helpOption(false).
3906
+ * Apply any implied option values, if option is undefined or default value.
3287
3907
  *
3288
- * @returns {(Option | null)} the help option
3289
- * @package
3908
+ * @private
3290
3909
  */
3291
- _getHelpOption() {
3292
- if (this._helpOption === void 0) {
3293
- this.helpOption(void 0, void 0);
3294
- }
3295
- return this._helpOption;
3910
+ _parseOptionsImplied() {
3911
+ const dualHelper = new DualOptions(this.options);
3912
+ const hasCustomOptionValue = (optionKey) => {
3913
+ return this.getOptionValue(optionKey) !== void 0 && !["default", "implied"].includes(this.getOptionValueSource(optionKey));
3914
+ };
3915
+ this.options.filter(
3916
+ (option) => option.implied !== void 0 && hasCustomOptionValue(option.attributeName()) && dualHelper.valueFromOption(
3917
+ this.getOptionValue(option.attributeName()),
3918
+ option
3919
+ )
3920
+ ).forEach((option) => {
3921
+ Object.keys(option.implied).filter((impliedKey) => !hasCustomOptionValue(impliedKey)).forEach((impliedKey) => {
3922
+ this.setOptionValueWithSource(
3923
+ impliedKey,
3924
+ option.implied[impliedKey],
3925
+ "implied"
3926
+ );
3927
+ });
3928
+ });
3296
3929
  }
3297
3930
  /**
3298
- * Supply your own option to use for the built-in help option.
3299
- * This is an alternative to using helpOption() to customise the flags and description etc.
3931
+ * Argument `name` is missing.
3300
3932
  *
3301
- * @param {Option} option
3302
- * @return {Command} `this` command for chaining
3933
+ * @param {string} name
3934
+ * @private
3303
3935
  */
3304
- addHelpOption(option) {
3305
- this._helpOption = option;
3306
- this._initOptionGroup(option);
3307
- return this;
3936
+ missingArgument(name) {
3937
+ const message2 = `error: missing required argument '${name}'`;
3938
+ this.error(message2, { code: "commander.missingArgument" });
3308
3939
  }
3309
3940
  /**
3310
- * Output help information and exit.
3311
- *
3312
- * Outputs built-in help, and custom text added using `.addHelpText()`.
3941
+ * `Option` is missing an argument.
3313
3942
  *
3314
- * @param {{ error: boolean }} [contextOptions] - pass {error:true} to write to stderr instead of stdout
3943
+ * @param {Option} option
3944
+ * @private
3315
3945
  */
3316
- help(contextOptions) {
3317
- this.outputHelp(contextOptions);
3318
- let exitCode = Number(import_node_process.default.exitCode ?? 0);
3319
- if (exitCode === 0 && contextOptions && typeof contextOptions !== "function" && contextOptions.error) {
3320
- exitCode = 1;
3321
- }
3322
- this._exit(exitCode, "commander.help", "(outputHelp)");
3946
+ optionMissingArgument(option) {
3947
+ const message2 = `error: option '${option.flags}' argument missing`;
3948
+ this.error(message2, { code: "commander.optionMissingArgument" });
3323
3949
  }
3324
3950
  /**
3325
- * // Do a little typing to coordinate emit and listener for the help text events.
3326
- * @typedef HelpTextEventContext
3327
- * @type {object}
3328
- * @property {boolean} error
3329
- * @property {Command} command
3330
- * @property {function} write
3331
- */
3332
- /**
3333
- * Add additional text to be displayed with the built-in help.
3334
- *
3335
- * Position is 'before' or 'after' to affect just this command,
3336
- * and 'beforeAll' or 'afterAll' to affect this command and all its subcommands.
3951
+ * `Option` does not have a value, and is a mandatory option.
3337
3952
  *
3338
- * @param {string} position - before or after built-in help
3339
- * @param {(string | Function)} text - string to add, or a function returning a string
3340
- * @return {Command} `this` command for chaining
3953
+ * @param {Option} option
3954
+ * @private
3341
3955
  */
3342
- addHelpText(position, text) {
3343
- const allowedValues = ["beforeAll", "before", "after", "afterAll"];
3344
- if (!allowedValues.includes(position)) {
3345
- throw new Error(`Unexpected value for position to addHelpText.
3346
- Expecting one of '${allowedValues.join("', '")}'`);
3347
- }
3348
- const helpEvent = `${position}Help`;
3349
- this.on(helpEvent, (context) => {
3350
- let helpStr;
3351
- if (typeof text === "function") {
3352
- helpStr = text({ error: context.error, command: context.command });
3353
- } else {
3354
- helpStr = text;
3355
- }
3356
- if (helpStr) {
3357
- context.write(`${helpStr}
3358
- `);
3359
- }
3360
- });
3361
- return this;
3956
+ missingMandatoryOptionValue(option) {
3957
+ const message2 = `error: required option '${option.flags}' not specified`;
3958
+ this.error(message2, { code: "commander.missingMandatoryOptionValue" });
3362
3959
  }
3363
3960
  /**
3364
- * Output help information if help flags specified
3961
+ * `Option` conflicts with another option.
3365
3962
  *
3366
- * @param {Array} args - array of options to search for help flags
3963
+ * @param {Option} option
3964
+ * @param {Option} conflictingOption
3367
3965
  * @private
3368
3966
  */
3369
- _outputHelpIfRequested(args) {
3370
- const helpOption = this._getHelpOption();
3371
- const helpRequested = helpOption && args.find((arg) => helpOption.is(arg));
3372
- if (helpRequested) {
3373
- this.outputHelp();
3374
- this._exit(0, "commander.helpDisplayed", "(outputHelp)");
3375
- }
3376
- }
3377
- };
3378
- function incrementNodeInspectorPort(args) {
3379
- return args.map((arg) => {
3380
- if (!arg.startsWith("--inspect")) {
3381
- return arg;
3382
- }
3383
- let debugOption;
3384
- let debugHost = "127.0.0.1";
3385
- let debugPort = "9229";
3386
- let match;
3387
- if ((match = arg.match(/^(--inspect(-brk)?)$/)) !== null) {
3388
- debugOption = match[1];
3389
- } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+)$/)) !== null) {
3390
- debugOption = match[1];
3391
- if (/^\d+$/.test(match[3])) {
3392
- debugPort = match[3];
3393
- } else {
3394
- debugHost = match[3];
3967
+ _conflictingOption(option, conflictingOption) {
3968
+ const findBestOptionFromValue = (option2) => {
3969
+ const optionKey = option2.attributeName();
3970
+ const optionValue = this.getOptionValue(optionKey);
3971
+ const negativeOption = this.options.find(
3972
+ (target) => target.negate && optionKey === target.attributeName()
3973
+ );
3974
+ const positiveOption = this.options.find(
3975
+ (target) => !target.negate && optionKey === target.attributeName()
3976
+ );
3977
+ if (negativeOption && (negativeOption.presetArg === void 0 && optionValue === false || negativeOption.presetArg !== void 0 && optionValue === negativeOption.presetArg)) {
3978
+ return negativeOption;
3395
3979
  }
3396
- } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/)) !== null) {
3397
- debugOption = match[1];
3398
- debugHost = match[3];
3399
- debugPort = match[4];
3400
- }
3401
- if (debugOption && debugPort !== "0") {
3402
- return `${debugOption}=${debugHost}:${parseInt(debugPort) + 1}`;
3403
- }
3404
- return arg;
3405
- });
3406
- }
3407
- function useColor() {
3408
- if (import_node_process.default.env.NO_COLOR || import_node_process.default.env.FORCE_COLOR === "0" || import_node_process.default.env.FORCE_COLOR === "false")
3409
- return false;
3410
- if (import_node_process.default.env.FORCE_COLOR || import_node_process.default.env.CLICOLOR_FORCE !== void 0)
3411
- return true;
3412
- return void 0;
3413
- }
3414
-
3415
- // node_modules/commander/index.js
3416
- var program = new Command();
3417
-
3418
- // src/command-composition.ts
3419
- var import_node_fs48 = require("node:fs");
3420
-
3421
- // src/clean-exit.ts
3422
- var UNDICI_GLOBAL_DISPATCHER_SYMBOL = Object.getOwnPropertySymbols(globalThis).find(
3423
- (s) => s.description === "undici.globalDispatcher.1" || s.description?.startsWith("undici.globalDispatcher.")
3424
- ) ?? /* @__PURE__ */ Symbol.for("undici.globalDispatcher.1");
3425
- function globalDispatcher() {
3426
- return globalThis[UNDICI_GLOBAL_DISPATCHER_SYMBOL];
3427
- }
3428
- function destroyHttpPool() {
3429
- try {
3430
- const dispatcher = globalDispatcher();
3431
- if (dispatcher?.destroy) {
3432
- void dispatcher.destroy();
3433
- return true;
3434
- }
3435
- } catch {
3436
- }
3437
- return false;
3438
- }
3439
- var closingDispatcher;
3440
- var closingDispatcherPromise;
3441
- async function closeHttpPool() {
3442
- const dispatcher = globalDispatcher();
3443
- if (!dispatcher) return;
3444
- if (dispatcher === closingDispatcher && closingDispatcherPromise) {
3445
- await closingDispatcherPromise;
3446
- return;
3447
- }
3448
- closingDispatcher = dispatcher;
3449
- closingDispatcherPromise = (async () => {
3450
- try {
3451
- if (dispatcher.close) await dispatcher.close();
3452
- else if (dispatcher.destroy) await dispatcher.destroy();
3453
- } catch {
3454
- try {
3455
- if (dispatcher.destroy) await dispatcher.destroy();
3456
- } catch {
3980
+ return positiveOption || option2;
3981
+ };
3982
+ const getErrorMessage = (option2) => {
3983
+ const bestOption = findBestOptionFromValue(option2);
3984
+ const optionKey = bestOption.attributeName();
3985
+ const source = this.getOptionValueSource(optionKey);
3986
+ if (source === "env") {
3987
+ return `environment variable '${bestOption.envVar}'`;
3457
3988
  }
3458
- }
3459
- })();
3460
- await closingDispatcherPromise;
3461
- }
3462
- function hardExit(code) {
3463
- destroyHttpPool();
3464
- process.exit(code);
3465
- }
3466
- var STDIO_FLUSH_TIMEOUT_MS = 2e3;
3467
- function flushStream(stream) {
3468
- return new Promise((resolve7) => {
3469
- try {
3470
- stream.write("", () => resolve7());
3471
- } catch {
3472
- resolve7();
3473
- }
3474
- });
3475
- }
3476
- async function flushStdio(timeoutMs = STDIO_FLUSH_TIMEOUT_MS) {
3477
- await Promise.race([
3478
- Promise.all([flushStream(process.stdout), flushStream(process.stderr)]),
3479
- new Promise((resolve7) => {
3480
- setTimeout(resolve7, timeoutMs).unref?.();
3481
- })
3482
- ]);
3483
- }
3484
- async function cleanExit(code) {
3485
- process.exitCode = code;
3486
- await closeHttpPool();
3487
- await flushStdio();
3488
- await new Promise((resolve7) => setImmediate(resolve7));
3489
- return void 0;
3490
- }
3491
- var CLI_EXIT_WATCHDOG_MS = (() => {
3492
- const testOverride = Number(process.env.MMI_CLI_TEST_EXIT_WATCHDOG_MS);
3493
- return process.env.NODE_ENV === "test" && Number.isFinite(testOverride) && testOverride >= 25 ? testOverride : 1e4;
3494
- })();
3495
- async function finishCliRun(watchdogMs = CLI_EXIT_WATCHDOG_MS) {
3496
- const timer = setTimeout(() => {
3497
- console.error(
3498
- `mmi-cli: command finished but the process did not exit within ${watchdogMs}ms \u2014 a handle leaked (${describeActiveHandles()}); forcing exit (#2904). Please report this.`
3499
- );
3500
- void flushStdio().finally(() => process.exit(typeof process.exitCode === "number" ? process.exitCode : 0));
3501
- }, watchdogMs);
3502
- timer.unref?.();
3503
- await closeHttpPool();
3504
- }
3505
- function describeActiveHandles() {
3506
- let summary;
3507
- try {
3508
- const counts = /* @__PURE__ */ new Map();
3509
- for (const type of process.getActiveResourcesInfo()) counts.set(type, (counts.get(type) ?? 0) + 1);
3510
- summary = [...counts.entries()].sort().map(([type, n]) => n > 1 ? `${type}\xD7${n}` : type).join(", ") || "none reported";
3511
- } catch {
3512
- summary = "unavailable";
3513
- }
3514
- try {
3515
- const children = (process._getActiveHandles?.() ?? []).filter((h) => h?.constructor?.name === "ChildProcess").map((h) => `${h.spawnfile ?? "?"}(pid=${h.pid ?? "?"})`);
3516
- if (children.length) summary += `; live children: ${children.join(", ")}`;
3517
- } catch {
3518
- }
3519
- return summary;
3520
- }
3521
- async function failGraceful(msg) {
3522
- console.error(`mmi-cli ${msg}`);
3523
- return cleanExit(1);
3524
- }
3525
-
3526
- // src/cli-shared.ts
3527
- var import_node_child_process2 = require("node:child_process");
3528
- var import_node_fs5 = require("node:fs");
3529
- var import_node_util3 = require("node:util");
3530
-
3531
- // src/hub-url.ts
3532
- var DEFAULT_HUB_URL = "https://tqxxwzftic.execute-api.eu-central-1.amazonaws.com";
3533
- function defaultHubUrl() {
3534
- return process.env.MMI_HUB_URL || DEFAULT_HUB_URL;
3535
- }
3536
-
3537
- // src/client-version.ts
3538
- var import_node_fs2 = require("node:fs");
3539
- var import_node_path2 = require("node:path");
3540
-
3541
- // ../infra/compat.mjs
3542
- var CLIENT_VERSION_HEADER = "x-client-version";
3543
- function parseSemver(s) {
3544
- if (typeof s !== "string") return null;
3545
- const m = /^(\d+)\.(\d+)\.(\d+)(?:[-+]|$)/.exec(s.trim());
3546
- if (!m) return null;
3547
- return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) };
3548
- }
3549
- function versionAtLeast(v, min) {
3550
- const a = parseSemver(v);
3551
- const b = parseSemver(min);
3552
- if (!a || !b) return false;
3553
- if (a.major !== b.major) return a.major > b.major;
3554
- if (a.minor !== b.minor) return a.minor > b.minor;
3555
- return a.patch >= b.patch;
3556
- }
3557
-
3558
- // src/client-version.ts
3559
- function resolveClientVersionManifestCandidates(distDir = __dirname) {
3560
- return [
3561
- (0, import_node_path2.join)(distDir, "..", "..", ".claude-plugin", "plugin.json"),
3562
- (0, import_node_path2.join)(distDir, "..", "package.json")
3563
- ];
3564
- }
3565
- function readVersionFromManifest(path2) {
3566
- try {
3567
- const version = JSON.parse((0, import_node_fs2.readFileSync)(path2, "utf8")).version;
3568
- return typeof version === "string" && version.trim() ? version.trim() : null;
3569
- } catch {
3570
- return null;
3571
- }
3572
- }
3573
- function resolveClientVersion() {
3574
- for (const manifest of resolveClientVersionManifestCandidates()) {
3575
- const version = readVersionFromManifest(manifest);
3576
- if (version) return version;
3577
- }
3578
- return "0.0.0";
3579
- }
3580
- function clientVersionHeaders() {
3581
- return { [CLIENT_VERSION_HEADER]: resolveClientVersion() };
3582
- }
3583
- function upgradeRequiredError(res, body) {
3584
- const minVersion = body && typeof body === "object" && typeof body.minVersion === "string" ? body.minVersion : "a newer version";
3585
- return `Hub requires mmi-cli >= ${minVersion} \u2014 run mmi-cli doctor (installed ${resolveClientVersion()})`;
3586
- }
3587
-
3588
- // src/error-codes.ts
3589
- var ERROR_CODES = {
3590
- /** A required flag was not supplied (e.g. `issue create` without `--priority`). */
3591
- ERR_MISSING_FLAG: "ERR_MISSING_FLAG",
3592
- /** Two mutually-exclusive flags were both supplied (e.g. `--title` with `--title-file`). */
3593
- ERR_CONFLICTING_FLAGS: "ERR_CONFLICTING_FLAGS",
3594
- /** A flag was supplied but its resolved value is empty (e.g. an empty `--title-file` or empty stdin). */
3595
- ERR_EMPTY_INPUT: "ERR_EMPTY_INPUT",
3596
- /** A supplied value has the right source but an invalid shape (e.g. a title with line breaks). */
3597
- ERR_INVALID_INPUT: "ERR_INVALID_INPUT",
3598
- /** A flag's value is outside its allowed set (e.g. `--priority nope`). */
3599
- ERR_BAD_ENUM: "ERR_BAD_ENUM",
3600
- /** An unknown flag or subcommand — usually a typo; carries a `did_you_mean`. */
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",
3606
- /** A referenced resource (issue, repo, board item) does not exist. */
3607
- ERR_NOT_FOUND: "ERR_NOT_FOUND",
3608
- /** Missing / rejected credentials on a path that needs auth. */
3609
- ERR_NO_AUTH: "ERR_NO_AUTH",
3610
- /** The operation partially succeeded (some units done, some failed). */
3611
- ERR_PARTIAL: "ERR_PARTIAL",
3612
- /** The request was well-formed and every referent resolved, but the target's CURRENT STATE forbids the
3613
- * mutation (HTTP 409 semantics) — e.g. reparenting an issue that already has a parent. Deliberately not
3614
- * one code per API constraint: retry is futile until the named state is changed, and that is the fact a
3615
- * caller has to act on, whichever rule produced it. */
3616
- ERR_STATE_CONFLICT: "ERR_STATE_CONFLICT"
3617
- };
3618
- var ERROR_CODE_REFERENCE = [
3619
- {
3620
- code: ERROR_CODES.ERR_MISSING_FLAG,
3621
- meaning: "A required flag was not supplied.",
3622
- typical_fix: "Run `mmi-cli explain <command>` and retry with the required flag."
3623
- },
3624
- {
3625
- code: ERROR_CODES.ERR_CONFLICTING_FLAGS,
3626
- meaning: "Two mutually-exclusive flags were supplied together.",
3627
- typical_fix: "Pass only one of the conflicting flags (see `offending_flag`) and retry."
3628
- },
3629
- {
3630
- code: ERROR_CODES.ERR_EMPTY_INPUT,
3631
- meaning: "A flag was supplied but resolved to an empty value (e.g. an empty file or empty stdin).",
3632
- typical_fix: "Provide non-empty content for the flag in `offending_flag` (a file with text, or a real pipe/heredoc for stdin)."
3633
- },
3634
- {
3635
- code: ERROR_CODES.ERR_INVALID_INPUT,
3636
- meaning: "A supplied value has an invalid shape for the flag.",
3637
- typical_fix: "Correct the value named by `offending_flag` and retry."
3638
- },
3639
- {
3640
- code: ERROR_CODES.ERR_BAD_ENUM,
3641
- meaning: "A flag value is outside the allowed enum.",
3642
- typical_fix: "Use one of the values in `expected`; casing and separators are often normalized by the CLI when supported."
3643
- },
3644
- {
3645
- code: ERROR_CODES.ERR_UNKNOWN_FLAG,
3646
- meaning: "A flag or subcommand is not known to this CLI version.",
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."
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
- },
3654
- {
3655
- code: ERROR_CODES.ERR_NOT_FOUND,
3656
- meaning: "The referenced issue, PR, repo, board item, or other resource was not found.",
3657
- typical_fix: "Verify the identifier and repo, then rerun with an explicit `--repo <owner/repo>` when local repo detection is ambiguous."
3658
- },
3659
- {
3660
- code: ERROR_CODES.ERR_NO_AUTH,
3661
- meaning: "The command needs credentials that are missing, expired, or rejected.",
3662
- typical_fix: "Run `mmi-cli doctor --self` or refresh the relevant GitHub/Hub session before retrying."
3663
- },
3664
- {
3665
- code: ERROR_CODES.ERR_PARTIAL,
3666
- meaning: "A batch operation completed some units and failed others.",
3667
- typical_fix: "Read the per-unit results, fix the failed inputs, and rerun only the failed units."
3668
- },
3669
- {
3670
- code: ERROR_CODES.ERR_STATE_CONFLICT,
3671
- meaning: "The request was valid and every referent resolved, but the target resource's current state forbids it.",
3672
- typical_fix: "A plain retry fails identically. Read the state field the envelope names (e.g. `current_parent`), change that state deliberately with the command that owns it, then retry."
3673
- }
3674
- ];
3675
- function buildErrorEnvelope(message2, payload) {
3676
- const env = { error_code: payload.code, message: message2 };
3677
- if (payload.offending_flag !== void 0) env.offending_flag = payload.offending_flag;
3678
- if (payload.expected !== void 0) env.expected = payload.expected;
3679
- if (payload.did_you_mean !== void 0) env.did_you_mean = payload.did_you_mean;
3680
- if (payload.corrected_command !== void 0) env.corrected_command = payload.corrected_command;
3681
- if (payload.current_parent !== void 0) env.current_parent = payload.current_parent;
3682
- if (payload.issue_ref !== void 0) env.issue_ref = payload.issue_ref;
3683
- if (payload.board_status !== void 0) env.board_status = payload.board_status;
3684
- return env;
3685
- }
3686
- function formatErrorEnvelope(message2, payload) {
3687
- return JSON.stringify(buildErrorEnvelope(message2, payload));
3688
- }
3689
- function levenshtein(a, b) {
3690
- if (a === b) return 0;
3691
- if (a.length === 0) return b.length;
3692
- if (b.length === 0) return a.length;
3693
- let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
3694
- let curr = new Array(b.length + 1);
3695
- for (let i = 1; i <= a.length; i++) {
3696
- curr[0] = i;
3697
- for (let j = 1; j <= b.length; j++) {
3698
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
3699
- curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
3700
- }
3701
- [prev, curr] = [curr, prev];
3702
- }
3703
- return prev[b.length];
3704
- }
3705
- function didYouMean(input, candidates) {
3706
- const strip = (s) => s.replace(/^-+/, "");
3707
- const target = strip(input);
3708
- if (!target) return void 0;
3709
- let best;
3710
- for (const candidate of candidates) {
3711
- const cand = strip(candidate);
3712
- if (!cand) continue;
3713
- if (cand === target) continue;
3714
- if (cand === `${target}-file` || target === `${cand}-file`) continue;
3715
- const distance = levenshtein(target, cand);
3716
- const prefix = cand.startsWith(target) || target.startsWith(cand);
3717
- const threshold = Math.max(2, Math.ceil(cand.length * 0.4));
3718
- if (!prefix && distance > threshold) continue;
3719
- const better = !best || distance < best.distance || distance === best.distance && prefix && !best.prefix;
3720
- if (better) best = { flag: candidate, distance, prefix };
3721
- }
3722
- return best?.flag;
3723
- }
3724
-
3725
- // src/github-client.ts
3726
- function classifyGhTokenExecFailure(e) {
3727
- const err = e;
3728
- const stderr = typeof err?.stderr === "string" ? err.stderr.trim() : "";
3729
- const detail = (stderr || err?.message || String(e)).replace(/\s+/g, " ").slice(0, 200);
3730
- if (err?.code === "ENOENT") return { state: "absent", detail: "gh is not installed on PATH" };
3731
- if (err?.killed === true || (err?.signal ?? null) !== null) return { state: "failed", detail: `gh auth token did not complete (${detail})` };
3732
- if (/not logged (in|into)|no oauth token|authentication token not found|gh auth login/i.test(`${stderr} ${err?.message ?? ""}`)) {
3733
- return { state: "absent", detail: "gh is installed but not logged in" };
3734
- }
3735
- return { state: "failed", detail };
3736
- }
3737
- var cachedGhCliTokenRead;
3738
- async function githubTokenRead() {
3739
- if (process.env.GH_TOKEN) return { state: "ok", token: process.env.GH_TOKEN };
3740
- if (process.env.GITHUB_TOKEN) return { state: "ok", token: process.env.GITHUB_TOKEN };
3741
- cachedGhCliTokenRead ??= execFileP("gh", ["auth", "token"]).then(({ stdout }) => {
3742
- const token = stdout.trim();
3743
- return token ? { state: "ok", token } : { state: "absent", detail: "`gh auth token` exited 0 without printing a token" };
3744
- }).catch((e) => classifyGhTokenExecFailure(e));
3745
- return cachedGhCliTokenRead;
3746
- }
3747
- async function githubToken() {
3748
- const read = await githubTokenRead();
3749
- if (read.state === "failed") {
3750
- throw new GitHubApiError(
3751
- `GitHub identity read FAILED: \`gh auth token\` ${read.detail} \u2014 this is NOT "no token", so the call is refused rather than sent anonymously (an anonymous 401/404 would read as a permission or missing-object answer). Retry, or set GH_TOKEN/GITHUB_TOKEN for this process.`,
3752
- { status: 0 }
3753
- );
3754
- }
3755
- return read.state === "ok" ? read.token : void 0;
3756
- }
3757
- var GitHubApiError = class extends Error {
3758
- status;
3759
- stderr;
3760
- graphqlErrors;
3761
- /** #4588: true when GitHub refused THIS call for budget (primary or secondary rate limit),
3762
- * read from the refusing response itself — never the rate_limit endpoint. */
3763
- rateLimited;
3764
- /** Epoch seconds when the exhausted pool resets (`X-Ratelimit-Reset` of the refusing response). */
3765
- rateLimitReset;
3766
- constructor(message2, opts = {}) {
3767
- super(message2);
3768
- this.name = "GitHubApiError";
3769
- this.status = opts.status ?? 0;
3770
- this.stderr = message2;
3771
- this.graphqlErrors = opts.graphqlErrors;
3772
- this.rateLimited = opts.rateLimited;
3773
- this.rateLimitReset = opts.rateLimitReset;
3774
- }
3775
- };
3776
- function rateLimitResetNote(resetEpochSeconds, now = Date.now()) {
3777
- if (!resetEpochSeconds) return "rate limit exhausted; reset time unknown";
3778
- const resetMs = resetEpochSeconds * 1e3;
3779
- const minutes = Math.max(0, Math.ceil((resetMs - now) / 6e4));
3780
- return `rate limit exhausted; resets at ${new Date(resetMs).toISOString()} (~${minutes}m)`;
3781
- }
3782
- function rateLimitFromResponse(res, detail) {
3783
- const remaining = res.headers.get("x-ratelimit-remaining");
3784
- const resetHeader = res.headers.get("x-ratelimit-reset");
3785
- const reset = resetHeader && /^\d+$/.test(resetHeader) ? Number(resetHeader) : void 0;
3786
- const rateLimited = (res.status === 403 || res.status === 429) && remaining === "0" || /rate limit|secondary rate|abuse detection/i.test(detail);
3787
- return { rateLimited, ...rateLimited && reset !== void 0 ? { rateLimitReset: reset } : {} };
3788
- }
3789
- var DEFAULT_TIMEOUT_MS = 2e4;
3790
- function joinUrl(base, path2) {
3791
- if (path2.startsWith("http://") || path2.startsWith("https://")) return path2;
3792
- return `${base.replace(/\/+$/, "")}/${path2.replace(/^\/+/, "")}`;
3793
- }
3794
- function withPerPage(url) {
3795
- if (/[?&]per_page=/.test(url)) return url;
3796
- return url.includes("?") ? `${url}&per_page=100` : `${url}?per_page=100`;
3797
- }
3798
- function apiOrigin(baseUrl) {
3799
- return new URL(baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).origin;
3800
- }
3801
- function nextLink(linkHeader) {
3802
- if (!linkHeader) return void 0;
3803
- for (const part of linkHeader.split(",")) {
3804
- const match = part.match(/<([^>]+)>\s*;\s*rel="next"/);
3805
- if (match) return match[1];
3989
+ return `option '${bestOption.flags}'`;
3990
+ };
3991
+ const message2 = `error: ${getErrorMessage(option)} cannot be used with ${getErrorMessage(conflictingOption)}`;
3992
+ this.error(message2, { code: "commander.conflictingOption" });
3806
3993
  }
3807
- return void 0;
3808
- }
3809
- function safeNextLink(linkHeader, allowedOrigin) {
3810
- const next = nextLink(linkHeader);
3811
- if (!next) return void 0;
3812
- let origin;
3813
- try {
3814
- origin = new URL(next).origin;
3815
- } catch {
3816
- throw new GitHubApiError(`pagination link rejected: invalid URL (${next})`, { status: 0 });
3994
+ /**
3995
+ * Unknown option `flag`.
3996
+ *
3997
+ * @param {string} flag
3998
+ * @private
3999
+ */
4000
+ unknownOption(flag) {
4001
+ if (this._allowUnknownOption) return;
4002
+ let suggestion = "";
4003
+ if (flag.startsWith("--") && this._showSuggestionAfterError) {
4004
+ let candidateFlags = [];
4005
+ let command = this;
4006
+ do {
4007
+ const moreFlags = command.createHelp().visibleOptions(command).filter((option) => option.long).map((option) => option.long);
4008
+ candidateFlags = candidateFlags.concat(moreFlags);
4009
+ command = command.parent;
4010
+ } while (command && !command._enablePositionalOptions);
4011
+ suggestion = suggestSimilar(flag, candidateFlags);
4012
+ }
4013
+ const message2 = `error: unknown option '${flag}'${suggestion}`;
4014
+ this.error(message2, { code: "commander.unknownOption" });
3817
4015
  }
3818
- if (origin !== allowedOrigin) {
3819
- throw new GitHubApiError(`pagination link rejected: cross-origin ${origin} (expected ${allowedOrigin})`, { status: 0 });
4016
+ /**
4017
+ * Excess arguments, more than expected.
4018
+ *
4019
+ * @param {string[]} receivedArgs
4020
+ * @private
4021
+ */
4022
+ _excessArguments(receivedArgs) {
4023
+ if (this._allowExcessArguments) return;
4024
+ const expected = this.registeredArguments.length;
4025
+ const s = expected === 1 ? "" : "s";
4026
+ const received = receivedArgs.length;
4027
+ const forSubcommand = this.parent ? ` for '${this.name()}'` : "";
4028
+ const details = receivedArgs.join(", ");
4029
+ const message2 = `error: too many arguments${forSubcommand}. Expected ${expected} argument${s} but got ${received}: ${details}.`;
4030
+ this.error(message2, { code: "commander.excessArguments" });
3820
4031
  }
3821
- return next;
3822
- }
3823
- async function errorFromResponse(res) {
3824
- let detail = "";
3825
- try {
3826
- const text = await res.text();
3827
- try {
3828
- const parsed = JSON.parse(text);
3829
- detail = parsed.message ?? text;
3830
- } catch {
3831
- detail = text;
4032
+ /**
4033
+ * Unknown command.
4034
+ *
4035
+ * @private
4036
+ */
4037
+ unknownCommand() {
4038
+ const unknownName = this.args[0];
4039
+ let suggestion = "";
4040
+ if (this._showSuggestionAfterError) {
4041
+ const candidateNames = [];
4042
+ this.createHelp().visibleCommands(this).forEach((command) => {
4043
+ candidateNames.push(command.name());
4044
+ if (command.alias()) candidateNames.push(command.alias());
4045
+ });
4046
+ suggestion = suggestSimilar(unknownName, candidateNames);
3832
4047
  }
3833
- } catch {
3834
- detail = "";
4048
+ const message2 = `error: unknown command '${unknownName}'${suggestion}`;
4049
+ this.error(message2, { code: "commander.unknownCommand" });
3835
4050
  }
3836
- const suffix = detail ? `: ${detail.trim()}` : "";
3837
- const limit = rateLimitFromResponse(res, detail);
3838
- const budgetNote = limit.rateLimited ? ` \u2014 ${rateLimitResetNote(limit.rateLimitReset)}` : "";
3839
- return new GitHubApiError(`HTTP ${res.status}${suffix}${budgetNote} (${res.url})`, {
3840
- status: res.status,
3841
- ...limit.rateLimited ? { rateLimited: true, rateLimitReset: limit.rateLimitReset } : {}
3842
- });
3843
- }
3844
- function createGitHubClient(options = {}) {
3845
- const baseUrl = options.baseUrl ?? process.env.GITHUB_API_URL ?? "https://api.github.com";
3846
- const allowedOrigin = apiOrigin(baseUrl);
3847
- const token = options.token ?? githubToken;
3848
- const defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
3849
- const fetchImpl = options.fetchImpl ?? fetch;
3850
- const externalSignal = options.signal;
3851
- async function request(method, url, init = {}) {
3852
- const t = await token();
3853
- const headers = {
3854
- Accept: "application/vnd.github+json",
3855
- "X-GitHub-Api-Version": "2022-11-28",
3856
- "User-Agent": "mmi-cli",
3857
- ...t ? { Authorization: `Bearer ${t}` } : {},
3858
- ...init.body !== void 0 ? { "Content-Type": "application/json" } : {},
3859
- ...init.headers
3860
- };
3861
- const timeoutSignal = AbortSignal.timeout(init.timeoutMs ?? defaultTimeoutMs);
3862
- const res = await fetchImpl(url, {
3863
- method,
3864
- headers,
3865
- body: init.body !== void 0 ? JSON.stringify(init.body) : void 0,
3866
- signal: externalSignal ? AbortSignal.any([externalSignal, timeoutSignal]) : timeoutSignal
4051
+ /**
4052
+ * Get or set the program version.
4053
+ *
4054
+ * This method auto-registers the "-V, --version" option which will print the version number.
4055
+ *
4056
+ * You can optionally supply the flags and description to override the defaults.
4057
+ *
4058
+ * @param {string} [str]
4059
+ * @param {string} [flags]
4060
+ * @param {string} [description]
4061
+ * @return {(this | string | undefined)} `this` command for chaining, or version string if no arguments
4062
+ */
4063
+ version(str, flags, description) {
4064
+ if (str === void 0) return this._version;
4065
+ this._version = str;
4066
+ flags = flags || "-V, --version";
4067
+ description = description || "output the version number";
4068
+ const versionOption = this.createOption(flags, description);
4069
+ this._versionOptionName = versionOption.attributeName();
4070
+ this._registerOption(versionOption);
4071
+ this.on("option:" + versionOption.name(), () => {
4072
+ this._outputConfiguration.writeOut(`${str}
4073
+ `);
4074
+ this._exit(0, "commander.version", str);
3867
4075
  });
3868
- if (!res.ok) throw await errorFromResponse(res);
3869
- return res;
4076
+ return this;
3870
4077
  }
3871
- async function parseJson2(res) {
3872
- if (res.status === 204) return void 0;
3873
- const text = await res.text();
3874
- if (!text) return void 0;
3875
- return JSON.parse(text);
4078
+ /**
4079
+ * Set the description.
4080
+ *
4081
+ * @param {string} [str]
4082
+ * @param {object} [argsDescription]
4083
+ * @return {(string|Command)}
4084
+ */
4085
+ description(str, argsDescription) {
4086
+ if (str === void 0 && argsDescription === void 0)
4087
+ return this._description;
4088
+ this._description = str;
4089
+ if (argsDescription) {
4090
+ this._argsDescription = argsDescription;
4091
+ }
4092
+ return this;
3876
4093
  }
3877
- return {
3878
- async rest(method, path2, init) {
3879
- const res = await request(method, joinUrl(baseUrl, path2), init);
3880
- return parseJson2(res);
3881
- },
3882
- async restPaginate(path2, init) {
3883
- const items = [];
3884
- let url = withPerPage(joinUrl(baseUrl, path2));
3885
- while (url) {
3886
- const res = await request("GET", url, init);
3887
- const page = await parseJson2(res);
3888
- if (!Array.isArray(page)) {
3889
- throw new GitHubApiError(
3890
- `pagination page was not a JSON array (got ${page === void 0 ? "an empty body" : typeof page}) \u2014 the list read is PARTIAL and must not be treated as complete: ${url}. Retry the call.`,
3891
- { status: res.status }
3892
- );
3893
- }
3894
- items.push(...page);
3895
- url = safeNextLink(res.headers.get("link"), allowedOrigin);
3896
- }
3897
- return items;
3898
- },
3899
- async graphql(query, variables, init) {
3900
- const res = await request("POST", joinUrl(baseUrl, "graphql"), {
3901
- ...init,
3902
- body: { query, ...variables ? { variables } : {} }
3903
- });
3904
- const parsed = await parseJson2(res);
3905
- if (parsed?.errors?.length) {
3906
- const message2 = parsed.errors.map((e) => e.message ?? e.type ?? "unknown GraphQL error").join("; ");
3907
- const rateLimited = parsed.errors.some((e) => e.type === "RATE_LIMITED" || /rate limit/i.test(e.message ?? ""));
3908
- if (rateLimited) {
3909
- const resetHeader = res.headers.get("x-ratelimit-reset");
3910
- const reset = resetHeader && /^\d+$/.test(resetHeader) ? Number(resetHeader) : void 0;
3911
- throw new GitHubApiError(`GraphQL: ${message2} \u2014 GraphQL ${rateLimitResetNote(reset)}; REST may still have headroom`, {
3912
- status: 200,
3913
- graphqlErrors: parsed.errors,
3914
- rateLimited: true,
3915
- rateLimitReset: reset
3916
- });
3917
- }
3918
- throw new GitHubApiError(`GraphQL: ${message2}`, { status: 200, graphqlErrors: parsed.errors });
3919
- }
3920
- if (!parsed || parsed.data === void 0 || parsed.data === null) {
3921
- throw new GitHubApiError("GraphQL response did not include data", { status: 200 });
3922
- }
3923
- return parsed.data;
4094
+ /**
4095
+ * Set the summary. Used when listed as subcommand of parent.
4096
+ *
4097
+ * @param {string} [str]
4098
+ * @return {(string|Command)}
4099
+ */
4100
+ summary(str) {
4101
+ if (str === void 0) return this._summary;
4102
+ this._summary = str;
4103
+ return this;
4104
+ }
4105
+ /**
4106
+ * Set an alias for the command.
4107
+ *
4108
+ * You may call more than once to add multiple aliases. Only the first alias is shown in the auto-generated help.
4109
+ *
4110
+ * @param {string} [alias]
4111
+ * @return {(string|Command)}
4112
+ */
4113
+ alias(alias) {
4114
+ if (alias === void 0) return this._aliases[0];
4115
+ let command = this;
4116
+ if (this.commands.length !== 0 && this.commands[this.commands.length - 1]._executableHandler) {
4117
+ command = this.commands[this.commands.length - 1];
3924
4118
  }
3925
- };
3926
- }
3927
- var cachedDefaultClient;
3928
- function defaultGitHubClient() {
3929
- cachedDefaultClient ??= createGitHubClient();
3930
- return cachedDefaultClient;
3931
- }
3932
-
3933
- // src/hub-auth.ts
3934
- var import_node_crypto = require("node:crypto");
3935
- var import_node_fs3 = require("node:fs");
3936
- var import_node_path3 = require("node:path");
3937
- var import_node_os = require("node:os");
3938
-
3939
- // src/fetch-retry.ts
3940
- async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
3941
- const attempts = opts.attempts ?? 3;
3942
- const baseDelayMs = opts.baseDelayMs ?? 250;
3943
- const retryOn = opts.retryOn ?? ((res) => res.status >= 500);
3944
- const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
3945
- let lastErr;
3946
- for (let i = 0; i < attempts; i++) {
3947
- const isLast = i === attempts - 1;
3948
- const attemptInit = opts.timeoutMs ? { ...init, signal: AbortSignal.timeout(opts.timeoutMs) } : init;
3949
- try {
3950
- const res = await fetchImpl(url, attemptInit);
3951
- if (!isLast && retryOn(res)) {
3952
- await sleep2(baseDelayMs * 2 ** i);
3953
- continue;
3954
- }
3955
- return res;
3956
- } catch (e) {
3957
- lastErr = e;
3958
- if (isLast) throw e;
3959
- await sleep2(baseDelayMs * 2 ** i);
4119
+ if (alias === command._name)
4120
+ throw new Error("Command alias can't be the same as its name");
4121
+ const matchingCommand = this.parent?._findCommand(alias);
4122
+ if (matchingCommand) {
4123
+ const existingCmd = [matchingCommand.name()].concat(matchingCommand.aliases()).join("|");
4124
+ throw new Error(
4125
+ `cannot add alias '${alias}' to command '${this.name()}' as already have command '${existingCmd}'`
4126
+ );
4127
+ }
4128
+ command._aliases.push(alias);
4129
+ return this;
4130
+ }
4131
+ /**
4132
+ * Set aliases for the command.
4133
+ *
4134
+ * Only the first alias is shown in the auto-generated help.
4135
+ *
4136
+ * @param {string[]} [aliases]
4137
+ * @return {(string[]|Command)}
4138
+ */
4139
+ aliases(aliases) {
4140
+ if (aliases === void 0) return this._aliases;
4141
+ aliases.forEach((alias) => this.alias(alias));
4142
+ return this;
4143
+ }
4144
+ /**
4145
+ * Set / get the command usage `str`.
4146
+ *
4147
+ * @param {string} [str]
4148
+ * @return {(string|Command)}
4149
+ */
4150
+ usage(str) {
4151
+ if (str === void 0) {
4152
+ if (this._usage) return this._usage;
4153
+ const args = this.registeredArguments.map((arg) => {
4154
+ return humanReadableArgName(arg);
4155
+ });
4156
+ return [].concat(
4157
+ this.options.length || this._helpOption !== null ? "[options]" : [],
4158
+ this.commands.length ? "[command]" : [],
4159
+ this.registeredArguments.length ? args : []
4160
+ ).join(" ");
3960
4161
  }
4162
+ this._usage = str;
4163
+ return this;
3961
4164
  }
3962
- throw lastErr;
3963
- }
3964
-
3965
- // src/hub-auth.ts
3966
- var REFRESH_WINDOW_MS = 10 * 60 * 1e3;
3967
- var EXCHANGE_TIMEOUT_MS = 8e3;
3968
- var EXCHANGE_ATTEMPTS = 2;
3969
- function normalizeBaseUrl(baseUrl) {
3970
- return baseUrl.replace(/\/$/, "");
3971
- }
3972
- function tokenFingerprint(token) {
3973
- return (0, import_node_crypto.createHash)("sha256").update(token).digest("hex");
3974
- }
3975
- function defaultHubSessionCachePath(env = process.env) {
3976
- if (env.MMI_HUB_SESSION_CACHE) return env.MMI_HUB_SESSION_CACHE;
3977
- if (process.platform === "win32") {
3978
- const base2 = env.LOCALAPPDATA || (0, import_node_path3.join)((0, import_node_os.homedir)(), "AppData", "Local");
3979
- return (0, import_node_path3.join)(base2, "MMI Future", "mmi-cli", "hub-session.json");
4165
+ /**
4166
+ * Get or set the name of the command.
4167
+ *
4168
+ * @param {string} [str]
4169
+ * @return {(string|Command)}
4170
+ */
4171
+ name(str) {
4172
+ if (str === void 0) return this._name;
4173
+ this._name = str;
4174
+ return this;
3980
4175
  }
3981
- const base = env.XDG_STATE_HOME || (0, import_node_path3.join)((0, import_node_os.homedir)(), ".local", "state");
3982
- return (0, import_node_path3.join)(base, "mmi-cli", "hub-session.json");
3983
- }
3984
- function roleFromToken(token) {
3985
- const part = token?.split(".")[1];
3986
- if (!part) return void 0;
3987
- try {
3988
- const claims = JSON.parse(Buffer.from(part, "base64url").toString("utf8"));
3989
- return claims.role === "owner" ? "owner" : void 0;
3990
- } catch {
3991
- return void 0;
4176
+ /**
4177
+ * Set/get the help group heading for this subcommand in parent command's help.
4178
+ *
4179
+ * @param {string} [heading]
4180
+ * @return {Command | string}
4181
+ */
4182
+ helpGroup(heading) {
4183
+ if (heading === void 0) return this._helpGroupHeading ?? "";
4184
+ this._helpGroupHeading = heading;
4185
+ return this;
3992
4186
  }
3993
- }
3994
- function readCache(path2, apiUrl, now, githubTokenFingerprint) {
3995
- try {
3996
- const session = JSON.parse((0, import_node_fs3.readFileSync)(path2, "utf8"));
3997
- if (!session.token || !session.expiresAt || session.apiUrl !== apiUrl) return null;
3998
- if (session.githubTokenFingerprint !== githubTokenFingerprint) return null;
3999
- if (new Date(session.expiresAt).getTime() <= now.getTime() + REFRESH_WINDOW_MS) return null;
4000
- return { ...session, role: roleFromToken(session.token) };
4001
- } catch {
4002
- return null;
4187
+ /**
4188
+ * Set/get the default help group heading for subcommands added to this command.
4189
+ * (This does not override a group set directly on the subcommand using .helpGroup().)
4190
+ *
4191
+ * @example
4192
+ * program.commandsGroup('Development Commands:);
4193
+ * program.command('watch')...
4194
+ * program.command('lint')...
4195
+ * ...
4196
+ *
4197
+ * @param {string} [heading]
4198
+ * @returns {Command | string}
4199
+ */
4200
+ commandsGroup(heading) {
4201
+ if (heading === void 0) return this._defaultCommandGroup ?? "";
4202
+ this._defaultCommandGroup = heading;
4203
+ return this;
4003
4204
  }
4004
- }
4005
- function writeCache(path2, session) {
4006
- (0, import_node_fs3.mkdirSync)((0, import_node_path3.dirname)(path2), { recursive: true });
4007
- const tmp = `${path2}.${process.pid}.${Date.now()}.tmp`;
4008
- (0, import_node_fs3.writeFileSync)(tmp, JSON.stringify(session, null, 2) + "\n", { encoding: "utf8", mode: 384 });
4009
- try {
4010
- (0, import_node_fs3.chmodSync)(tmp, 384);
4011
- } catch {
4205
+ /**
4206
+ * Set/get the default help group heading for options added to this command.
4207
+ * (This does not override a group set directly on the option using .helpGroup().)
4208
+ *
4209
+ * @example
4210
+ * program
4211
+ * .optionsGroup('Development Options:')
4212
+ * .option('-d, --debug', 'output extra debugging')
4213
+ * .option('-p, --profile', 'output profiling information')
4214
+ *
4215
+ * @param {string} [heading]
4216
+ * @returns {Command | string}
4217
+ */
4218
+ optionsGroup(heading) {
4219
+ if (heading === void 0) return this._defaultOptionGroup ?? "";
4220
+ this._defaultOptionGroup = heading;
4221
+ return this;
4012
4222
  }
4013
- (0, import_node_fs3.renameSync)(tmp, path2);
4014
- try {
4015
- (0, import_node_fs3.chmodSync)(path2, 384);
4016
- } catch {
4223
+ /**
4224
+ * @param {Option} option
4225
+ * @private
4226
+ */
4227
+ _initOptionGroup(option) {
4228
+ if (this._defaultOptionGroup && !option.helpGroupHeading)
4229
+ option.helpGroup(this._defaultOptionGroup);
4017
4230
  }
4018
- }
4019
- async function hubAuthSession(deps) {
4020
- if (!deps.baseUrl) return void 0;
4021
- const apiUrl = normalizeBaseUrl(deps.baseUrl);
4022
- const envToken = process.env.MMI_HUB_TOKEN;
4023
- if (envToken) {
4024
- return { ...{ token: envToken }, expiresAt: new Date(Date.now() + 30 * 60 * 1e3).toISOString(), apiUrl, githubTokenFingerprint: "env-override" };
4231
+ /**
4232
+ * @param {Command} cmd
4233
+ * @private
4234
+ */
4235
+ _initCommandGroup(cmd) {
4236
+ if (this._defaultCommandGroup && !cmd.helpGroup())
4237
+ cmd.helpGroup(this._defaultCommandGroup);
4025
4238
  }
4026
- const now = deps.now?.() ?? /* @__PURE__ */ new Date();
4027
- const cachePath = deps.cachePath ?? defaultHubSessionCachePath();
4028
- const ghToken = await deps.githubToken();
4029
- if (!ghToken) return void 0;
4030
- const githubTokenFingerprint = tokenFingerprint(ghToken);
4031
- const cached = readCache(cachePath, apiUrl, now, githubTokenFingerprint);
4032
- if (cached) return cached;
4033
- try {
4034
- const res = await fetchWithRetry(
4035
- deps.fetch ?? fetch,
4036
- `${apiUrl}/auth/session`,
4037
- { method: "POST", headers: { ...clientVersionHeaders(), Authorization: `Bearer ${ghToken}` } },
4038
- { attempts: EXCHANGE_ATTEMPTS, timeoutMs: EXCHANGE_TIMEOUT_MS }
4039
- );
4040
- if (!res.ok) return void 0;
4041
- const body = await res.json();
4042
- if (!body.token || !body.expiresAt) return void 0;
4043
- const session = {
4044
- token: body.token,
4045
- expiresAt: body.expiresAt,
4046
- login: typeof body.login === "string" ? body.login : void 0,
4047
- // #3513: from the SIGNED token, not the response body — one rule at every hop, so the cache
4048
- // read and the network read cannot disagree about what counts as an assertion.
4049
- role: roleFromToken(body.token),
4050
- apiUrl,
4051
- githubTokenFingerprint
4052
- };
4053
- try {
4054
- writeCache(cachePath, session);
4055
- } catch {
4056
- }
4057
- return session;
4058
- } catch {
4059
- return void 0;
4239
+ /**
4240
+ * Set the name of the command from script filename, such as process.argv[1],
4241
+ * or import.meta.filename.
4242
+ *
4243
+ * (Used internally and public although not documented in README.)
4244
+ *
4245
+ * @example
4246
+ * program.nameFromFilename(import.meta.filename);
4247
+ *
4248
+ * @param {string} filename
4249
+ * @return {Command}
4250
+ */
4251
+ nameFromFilename(filename) {
4252
+ this._name = import_node_path.default.basename(filename, import_node_path.default.extname(filename));
4253
+ return this;
4060
4254
  }
4061
- }
4062
- async function hubAuthToken(deps) {
4063
- return (await hubAuthSession(deps))?.token;
4064
- }
4065
-
4066
- // src/house-map.ts
4067
- var HOUSE_ROOTS = ["oracle", "harbour", "devops", "vault", "learning"];
4068
- function isHouseRoot(token) {
4069
- return HOUSE_ROOTS.includes(token);
4070
- }
4071
- var COMPAT_SHIM_DEATH_WAVE = "Wave 3";
4072
- var FLAT_ALIAS_CUT_NOTE = `flat Wave 0 alias \u2014 removed in ${COMPAT_SHIM_DEATH_WAVE} (#4316, #4438); only the canonical house-prefixed path parses`;
4073
- var HOUSE_QUESTIONS = {
4074
- oracle: "Is it live?",
4075
- harbour: "Is it bounded?",
4076
- devops: "Is it legal?",
4077
- vault: "Is it guarded?",
4078
- learning: "Did it learn?",
4079
- core: "Is it one door?"
4080
- };
4081
- var HOUSE_MAP = {
4082
- // --- oracle — live truth ------------------------------------------------------------------------
4083
- board: "oracle",
4084
- // board management (read + the guarded mutations that keep it live)
4085
- issue: "oracle",
4086
- // issues are live org truth
4087
- wave: "oracle",
4088
- // read-side multi-worktree board visibility (`wave status`)
4089
- next: "oracle",
4090
- // read-side board: the next actionable item
4091
- docs: "oracle",
4092
- // generated docs surfaces — the live org knowledge routing index
4093
- org: "oracle",
4094
- // org projects/registry/access reads (subgroup overrides below)
4095
- "org project": "oracle",
4096
- // registry projections (declared + projected live truth)
4097
- "org access": "oracle",
4098
- // org access role/audit reads
4099
- "org config": "oracle",
4100
- // live org configuration read
4101
- // --- harbour — declared lanes -------------------------------------------------------------------
4102
- "org schedules": "harbour",
4103
- // register/run/park schedule rows under the one lane contract
4104
- // --- devops — shipping --------------------------------------------------------------------------
4105
- pr: "devops",
4106
- ci: "devops",
4107
- // the CI/gate audit
4108
- rcand: "devops",
4109
- release: "devops",
4110
- hotfix: "devops",
4111
- train: "devops",
4112
- "wave land": "devops",
4113
- // the write-side serial merge train is shipping, not board observation
4114
- bootstrap: "devops",
4115
- // repo provisioning + propagate
4116
- runtime: "devops",
4117
- // tenant/deploy/box/edge — shipping and central deploy
4118
- "org rules": "devops",
4119
- // org-managed repository rule delivery (.gitignore)
4120
- // --- vault — secrets ----------------------------------------------------------------------------
4121
- secrets: "vault",
4122
- "org oauth": "vault",
4123
- // OAuth credential planning/set/verify
4124
- // --- learning — self-improvement ----------------------------------------------------------------
4125
- report: "learning",
4126
- // friction reports
4127
- "skill-lesson": "learning",
4128
- "closure-rate": "learning",
4129
- // closure rate per loop kind, computed at read (#4440)
4130
- pickup: "learning",
4131
- // cloud-agent adoption gate (#5708)
4132
- // --- core — the front door itself ---------------------------------------------------------------
4133
- commands: "core",
4134
- whoami: "core",
4135
- spawn: "core",
4136
- // this repo's process-spawn contract
4137
- tests: "core",
4138
- // this repo's test-policy contract
4139
- dist: "core",
4140
- // this repo's committed dist/BOM drift receipt (#5576)
4141
- doctor: "core",
4142
- stage: "core",
4143
- plugin: "core",
4144
- // plugin lifecycle + guards (CLI house owns plugins)
4145
- explain: "core",
4146
- // command-surface help
4147
- status: "core",
4148
- // repo-orientation snapshot (front door — torn toward oracle, kept core)
4149
- onboard: "core"
4150
- // repo-readiness orientation (front door — torn toward oracle, kept core)
4151
- };
4152
- var HOUSE_ALIASES = {
4153
- issue: ["devops"],
4154
- // filing/managing issues is oracle, but reads as shipping work
4155
- "issue create": ["devops"]
4156
- };
4157
- function isDeclaredHouseAlias(houseToken, lookupPath) {
4158
- const segments = lookupPath.split(" ");
4159
- for (let end = segments.length; end > 0; end -= 1) {
4160
- const key = segments.slice(0, end).join(" ");
4161
- const aliases = HOUSE_ALIASES[key];
4162
- if (aliases) return aliases.includes(houseToken);
4255
+ /**
4256
+ * Get or set the directory for searching for executable subcommands of this command.
4257
+ *
4258
+ * @example
4259
+ * program.executableDir(import.meta.dirname);
4260
+ * // or
4261
+ * program.executableDir('subcommands');
4262
+ *
4263
+ * @param {string} [path]
4264
+ * @return {(string|null|Command)}
4265
+ */
4266
+ executableDir(path2) {
4267
+ if (path2 === void 0) return this._executableDir;
4268
+ this._executableDir = path2;
4269
+ return this;
4163
4270
  }
4164
- return false;
4165
- }
4166
- function houseForPath(path2) {
4167
- if (path2 === "") return "core";
4168
- const segments = path2.split(" ");
4169
- for (let end = segments.length; end > 0; end -= 1) {
4170
- const key = segments.slice(0, end).join(" ");
4171
- const house = HOUSE_MAP[key];
4172
- if (house) return house;
4271
+ /**
4272
+ * Return program help documentation.
4273
+ *
4274
+ * @param {{ error: boolean }} [contextOptions] - pass {error:true} to wrap for stderr instead of stdout
4275
+ * @return {string}
4276
+ */
4277
+ helpInformation(contextOptions) {
4278
+ const helper = this.createHelp();
4279
+ const context = this._getOutputContext(contextOptions);
4280
+ helper.prepareContext({
4281
+ error: context.error,
4282
+ helpWidth: context.helpWidth,
4283
+ outputHasColors: context.hasColors
4284
+ });
4285
+ const text = helper.formatHelp(this, helper);
4286
+ if (context.hasColors) return text;
4287
+ return this._outputConfiguration.stripColor(text);
4173
4288
  }
4174
- return void 0;
4175
- }
4176
- function canonicalPathFor(path2) {
4177
- const house = houseForPath(path2);
4178
- if (!house) return void 0;
4179
- return house === "core" ? path2 : `${house} ${path2}`;
4180
- }
4181
- function canonicalArgvFor(args) {
4182
- const lookup = args.filter((tok) => !tok.startsWith("-")).slice(0, 2).join(" ");
4183
- const house = houseForPath(lookup);
4184
- return house && house !== "core" ? [house, ...args] : args;
4185
- }
4186
-
4187
- // src/stdin-inject.ts
4188
- var import_node_fs4 = require("node:fs");
4189
- var injectedStdin;
4190
- function stdinHasPipedInput(statFd = () => (0, import_node_fs4.fstatSync)(0), getIsTTY = () => process.stdin.isTTY) {
4191
- try {
4192
- const stat4 = statFd();
4193
- if (stat4.isFIFO() || stat4.isFile()) return true;
4194
- if (stat4.isCharacterDevice()) return false;
4195
- if (stat4.isSocket()) return false;
4196
- if (getIsTTY() === true) return false;
4197
- return true;
4198
- } catch {
4199
- return false;
4289
+ /**
4290
+ * @typedef HelpContext
4291
+ * @type {object}
4292
+ * @property {boolean} error
4293
+ * @property {number} helpWidth
4294
+ * @property {boolean} hasColors
4295
+ * @property {function} write - includes stripColor if needed
4296
+ *
4297
+ * @returns {HelpContext}
4298
+ * @private
4299
+ */
4300
+ _getOutputContext(contextOptions) {
4301
+ contextOptions = contextOptions || {};
4302
+ const error = !!contextOptions.error;
4303
+ let baseWrite;
4304
+ let hasColors;
4305
+ let helpWidth;
4306
+ if (error) {
4307
+ baseWrite = (str) => this._outputConfiguration.writeErr(str);
4308
+ hasColors = this._outputConfiguration.getErrHasColors();
4309
+ helpWidth = this._outputConfiguration.getErrHelpWidth();
4310
+ } else {
4311
+ baseWrite = (str) => this._outputConfiguration.writeOut(str);
4312
+ hasColors = this._outputConfiguration.getOutHasColors();
4313
+ helpWidth = this._outputConfiguration.getOutHelpWidth();
4314
+ }
4315
+ const write = (str) => {
4316
+ if (!hasColors) str = this._outputConfiguration.stripColor(str);
4317
+ return baseWrite(str);
4318
+ };
4319
+ return { error, write, hasColors, helpWidth };
4200
4320
  }
4201
- }
4202
- var STDIN_MAX_BYTES = 8 * 1024 * 1024;
4203
- var STDIN_DRAIN_TIMEOUT_MS = 5e3;
4204
- async function readStdin(opts = {}) {
4205
- if (injectedStdin !== void 0) return injectedStdin;
4206
- if (!stdinHasPipedInput()) return "";
4207
- const maxBytes = opts.maxBytes ?? STDIN_MAX_BYTES;
4208
- const timeoutMs = opts.timeoutMs ?? STDIN_DRAIN_TIMEOUT_MS;
4209
- const chunks = [];
4210
- let total = 0;
4211
- const drain = (async () => {
4212
- for await (const chunk of process.stdin) {
4213
- const buf = chunk;
4214
- const room = maxBytes - total;
4215
- if (buf.length >= room) {
4216
- chunks.push(buf.subarray(0, room));
4217
- total = maxBytes;
4218
- break;
4321
+ /**
4322
+ * Output help information for this command.
4323
+ *
4324
+ * Outputs built-in help, and custom text added using `.addHelpText()`.
4325
+ *
4326
+ * @param {{ error: boolean } | Function} [contextOptions] - pass {error:true} to write to stderr instead of stdout
4327
+ */
4328
+ outputHelp(contextOptions) {
4329
+ let deprecatedCallback;
4330
+ if (typeof contextOptions === "function") {
4331
+ deprecatedCallback = contextOptions;
4332
+ contextOptions = void 0;
4333
+ }
4334
+ const outputContext = this._getOutputContext(contextOptions);
4335
+ const eventContext = {
4336
+ error: outputContext.error,
4337
+ write: outputContext.write,
4338
+ command: this
4339
+ };
4340
+ this._getCommandAndAncestors().reverse().forEach((command) => command.emit("beforeAllHelp", eventContext));
4341
+ this.emit("beforeHelp", eventContext);
4342
+ let helpInformation = this.helpInformation({ error: outputContext.error });
4343
+ if (deprecatedCallback) {
4344
+ helpInformation = deprecatedCallback(helpInformation);
4345
+ if (typeof helpInformation !== "string" && !Buffer.isBuffer(helpInformation)) {
4346
+ throw new Error("outputHelp callback must return a string or a Buffer");
4219
4347
  }
4220
- chunks.push(buf);
4221
- total += buf.length;
4222
4348
  }
4223
- })().catch(() => {
4224
- });
4225
- let timer;
4226
- const timeout = new Promise((resolve7) => {
4227
- timer = setTimeout(resolve7, timeoutMs);
4228
- });
4229
- try {
4230
- await Promise.race([drain, timeout]);
4231
- } finally {
4232
- if (timer) clearTimeout(timer);
4233
- try {
4234
- process.stdin.unref();
4235
- } catch {
4349
+ outputContext.write(helpInformation);
4350
+ if (this._getHelpOption()?.long) {
4351
+ this.emit(this._getHelpOption().long);
4236
4352
  }
4237
- }
4238
- return Buffer.concat(chunks).toString("utf8");
4239
- }
4240
-
4241
- // src/cli-shared.ts
4242
- var rawExecFileP = (0, import_node_util3.promisify)(import_node_child_process2.execFile);
4243
- var DEFAULT_EXEC_TIMEOUT_MS = 1e4;
4244
- var execFileP = (file, args, options = {}) => (
4245
- // encoding 'utf8' guarantees string stdout/stderr at runtime; the cast pins the type because
4246
- // promisify(execFile)'s overloads widen to string|Buffer when options is spread in.
4247
- rawExecFileP(file, args, { encoding: "utf8", windowsHide: true, timeout: DEFAULT_EXEC_TIMEOUT_MS, killSignal: "SIGTERM", ...options })
4248
- );
4249
- var GIT_TIMEOUT_MS = DEFAULT_EXEC_TIMEOUT_MS;
4250
- var ExecDeadlineError = class extends Error {
4251
- constructor(step, timeoutMs, elapsedMs, killed) {
4252
- super(
4253
- `\`${step}\` did not finish within ${Math.round(timeoutMs / 1e3)}s (gave up after ${Math.round(elapsedMs / 1e3)}s). It stopped responding and did not exit when asked to, so the timeout alone could not end it; ${killed ? "its process tree was force-terminated" : "its process tree could NOT be terminated and may still be running"}.`
4353
+ this.emit("afterHelp", eventContext);
4354
+ this._getCommandAndAncestors().forEach(
4355
+ (command) => command.emit("afterAllHelp", eventContext)
4254
4356
  );
4255
- this.step = step;
4256
- this.timeoutMs = timeoutMs;
4257
- this.elapsedMs = elapsedMs;
4258
- this.killed = killed;
4259
- this.name = "ExecDeadlineError";
4260
- }
4261
- step;
4262
- timeoutMs;
4263
- elapsedMs;
4264
- killed;
4265
- };
4266
- function killProcessTree(pid) {
4267
- try {
4268
- if (process.platform === "win32") {
4269
- (0, import_node_child_process2.execFileSync)("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
4270
- } else {
4271
- process.kill(pid, "SIGKILL");
4357
+ }
4358
+ /**
4359
+ * You can pass in flags and a description to customise the built-in help option.
4360
+ * Pass in false to disable the built-in help option.
4361
+ *
4362
+ * @example
4363
+ * program.helpOption('-?, --help' 'show help'); // customise
4364
+ * program.helpOption(false); // disable
4365
+ *
4366
+ * @param {(string | boolean)} flags
4367
+ * @param {string} [description]
4368
+ * @return {Command} `this` command for chaining
4369
+ */
4370
+ helpOption(flags, description) {
4371
+ if (typeof flags === "boolean") {
4372
+ if (flags) {
4373
+ if (this._helpOption === null) this._helpOption = void 0;
4374
+ if (this._defaultOptionGroup) {
4375
+ this._initOptionGroup(this._getHelpOption());
4376
+ }
4377
+ } else {
4378
+ this._helpOption = null;
4379
+ }
4380
+ return this;
4272
4381
  }
4273
- return true;
4274
- } catch {
4275
- return false;
4382
+ this._helpOption = this.createOption(
4383
+ flags ?? "-h, --help",
4384
+ description ?? "display help for command"
4385
+ );
4386
+ if (flags || description) this._initOptionGroup(this._helpOption);
4387
+ return this;
4276
4388
  }
4277
- }
4278
- function execFileHard(file, args, options) {
4279
- const { timeout, step, ...rest } = options;
4280
- const started = Date.now();
4281
- return new Promise((resolve7, reject) => {
4282
- const child2 = (0, import_node_child_process2.execFile)(file, args, { encoding: "utf8", windowsHide: true, ...rest, timeout: 0 }, (error, stdout, stderr) => {
4283
- clearTimeout(timer);
4284
- if (expired) return;
4285
- if (error) reject(error);
4286
- else resolve7({ stdout: String(stdout), stderr: String(stderr) });
4287
- });
4288
- let expired = false;
4289
- const timer = setTimeout(() => {
4290
- expired = true;
4291
- const killed = child2.pid ? killProcessTree(child2.pid) : false;
4292
- child2.stdout?.destroy();
4293
- child2.stderr?.destroy();
4294
- child2.unref();
4295
- reject(new ExecDeadlineError(step, timeout, Date.now() - started, killed));
4296
- }, timeout);
4297
- timer.unref?.();
4298
- });
4299
- }
4300
- var cachedGithubLogin;
4301
- async function githubLogin() {
4302
- cachedGithubLogin ??= execFileP("gh", ["api", "user", "--jq", ".login"]).then(({ stdout }) => stdout.trim() || void 0).catch(() => void 0);
4303
- return cachedGithubLogin;
4304
- }
4305
- async function hubHeaders(extra = {}) {
4306
- const cfg = await loadConfig();
4307
- const t = await hubAuthToken({ baseUrl: cfg.sagaApiUrl ?? defaultHubUrl(), githubToken });
4308
- const base = { ...clientVersionHeaders(), ...extra };
4309
- return t ? { ...base, Authorization: `Bearer ${t}` } : base;
4310
- }
4311
- async function loadConfig() {
4312
- return { sagaApiUrl: defaultHubUrl() };
4313
- }
4314
- async function originRemoteUrl() {
4315
- return gitOut(["config", "--get", "remote.origin.url"]);
4316
- }
4317
- async function isOrgRepoRoot(readOrigin = originRemoteUrl) {
4318
- return /[:/]mutmutco\//i.test(await readOrigin());
4319
- }
4320
- var gitOut = async (args) => {
4321
- try {
4322
- return (await execFileP("git", [...args])).stdout.trim();
4323
- } catch {
4324
- return "";
4389
+ /**
4390
+ * Lazy create help option.
4391
+ * Returns null if has been disabled with .helpOption(false).
4392
+ *
4393
+ * @returns {(Option | null)} the help option
4394
+ * @package
4395
+ */
4396
+ _getHelpOption() {
4397
+ if (this._helpOption === void 0) {
4398
+ this.helpOption(void 0, void 0);
4399
+ }
4400
+ return this._helpOption;
4325
4401
  }
4326
- };
4327
- async function repoProject(cfg) {
4328
- const remote = await gitOut(["remote", "get-url", "origin"]);
4329
- const repo = remote.replace(/\.git$/, "").split("/").pop() || "-";
4330
- return cfg.project || repo;
4331
- }
4332
- var consoleIo = { log: (m) => console.log(m), err: (m) => console.error(m) };
4333
- function argvWantsJson() {
4334
- return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
4335
- }
4336
- function argvWantsMachineFailure() {
4337
- return argvWantsJson() || process.argv.some((a) => a === "--validate-only" || a === "--dry-run");
4338
- }
4339
- function commandFromFailMessage(msg) {
4340
- const head = msg.split(":", 1)[0].trim();
4341
- if (!head || head.startsWith("-")) return "mmi-cli";
4342
- if (!/^[a-z][a-z0-9-]*(?: [a-z][a-z0-9-]*){0,5}$/.test(head)) return "mmi-cli";
4343
- return canonicalPathFor(head) ?? head;
4344
- }
4345
- function canonicalFailMessage(msg) {
4346
- const colon = msg.indexOf(":");
4347
- if (colon < 0) return msg;
4348
- const command = commandFromFailMessage(msg);
4349
- return command === "mmi-cli" ? msg : `${command}${msg.slice(colon)}`;
4350
- }
4351
- function fail(msg, payload) {
4352
- const json = argvWantsMachineFailure();
4353
- const canonicalMsg = canonicalFailMessage(msg);
4354
- if (payload && json) {
4355
- console.error(formatErrorEnvelope(canonicalMsg, payload));
4356
- } else {
4357
- if (!json) console.error(`run: mmi-cli explain ${commandFromFailMessage(msg)}`);
4358
- console.error(`mmi-cli ${canonicalMsg}`);
4402
+ /**
4403
+ * Supply your own option to use for the built-in help option.
4404
+ * This is an alternative to using helpOption() to customise the flags and description etc.
4405
+ *
4406
+ * @param {Option} option
4407
+ * @return {Command} `this` command for chaining
4408
+ */
4409
+ addHelpOption(option) {
4410
+ this._helpOption = option;
4411
+ this._initOptionGroup(option);
4412
+ return this;
4359
4413
  }
4360
- hardExit(1);
4361
- }
4362
- async function failGracefulEnvelope(msg, payload) {
4363
- if (payload && argvWantsMachineFailure()) {
4364
- console.error(formatErrorEnvelope(msg, payload));
4365
- return cleanExit(1);
4414
+ /**
4415
+ * Output help information and exit.
4416
+ *
4417
+ * Outputs built-in help, and custom text added using `.addHelpText()`.
4418
+ *
4419
+ * @param {{ error: boolean }} [contextOptions] - pass {error:true} to write to stderr instead of stdout
4420
+ */
4421
+ help(contextOptions) {
4422
+ this.outputHelp(contextOptions);
4423
+ let exitCode = Number(import_node_process.default.exitCode ?? 0);
4424
+ if (exitCode === 0 && contextOptions && typeof contextOptions !== "function" && contextOptions.error) {
4425
+ exitCode = 1;
4426
+ }
4427
+ this._exit(exitCode, "commander.help", "(outputHelp)");
4366
4428
  }
4367
- return failGraceful(msg);
4368
- }
4369
- async function planMutation(opts, args, commandName, planFn) {
4370
- const validateOnly = opts.validateOnly === true;
4371
- const dryRun = opts.dryRun === true;
4372
- if (!validateOnly && !dryRun) return null;
4373
- const planned = planFn ? await planFn(opts, args) : { command: commandName, args };
4374
- return validateOnly ? { ok: true, planned } : { dry_run: true, planned };
4375
- }
4376
- function commandPath(cmd) {
4377
- const parts = [];
4378
- let node = cmd;
4379
- while (node && node.parent) {
4380
- parts.unshift(node.name());
4381
- node = node.parent;
4429
+ /**
4430
+ * // Do a little typing to coordinate emit and listener for the help text events.
4431
+ * @typedef HelpTextEventContext
4432
+ * @type {object}
4433
+ * @property {boolean} error
4434
+ * @property {Command} command
4435
+ * @property {function} write
4436
+ */
4437
+ /**
4438
+ * Add additional text to be displayed with the built-in help.
4439
+ *
4440
+ * Position is 'before' or 'after' to affect just this command,
4441
+ * and 'beforeAll' or 'afterAll' to affect this command and all its subcommands.
4442
+ *
4443
+ * @param {string} position - before or after built-in help
4444
+ * @param {(string | Function)} text - string to add, or a function returning a string
4445
+ * @return {Command} `this` command for chaining
4446
+ */
4447
+ addHelpText(position, text) {
4448
+ const allowedValues = ["beforeAll", "before", "after", "afterAll"];
4449
+ if (!allowedValues.includes(position)) {
4450
+ throw new Error(`Unexpected value for position to addHelpText.
4451
+ Expecting one of '${allowedValues.join("', '")}'`);
4452
+ }
4453
+ const helpEvent = `${position}Help`;
4454
+ this.on(helpEvent, (context) => {
4455
+ let helpStr;
4456
+ if (typeof text === "function") {
4457
+ helpStr = text({ error: context.error, command: context.command });
4458
+ } else {
4459
+ helpStr = text;
4460
+ }
4461
+ if (helpStr) {
4462
+ context.write(`${helpStr}
4463
+ `);
4464
+ }
4465
+ });
4466
+ return this;
4382
4467
  }
4383
- return parts.join(" ") || cmd.name();
4384
- }
4385
- function jsonParity(cmd) {
4386
- if (!cmd.options.some((o) => o.long === "--json")) {
4387
- cmd.option("--json", "machine-readable output (default; accepted for parity)");
4468
+ /**
4469
+ * Output help information if help flags specified
4470
+ *
4471
+ * @param {Array} args - array of options to search for help flags
4472
+ * @private
4473
+ */
4474
+ _outputHelpIfRequested(args) {
4475
+ const helpOption = this._getHelpOption();
4476
+ const helpRequested = helpOption && args.find((arg) => helpOption.is(arg));
4477
+ if (helpRequested) {
4478
+ this.outputHelp();
4479
+ this._exit(0, "commander.helpDisplayed", "(outputHelp)");
4480
+ }
4388
4481
  }
4389
- return cmd;
4390
- }
4391
- function mutating(cmd, planFn) {
4392
- const marker = cmd;
4393
- if (marker.__mmiMutating) return cmd;
4394
- marker.__mmiMutating = true;
4395
- if (!cmd.options.some((o) => o.long === "--json")) cmd.option("--json", "machine-readable output");
4396
- cmd.option("--dry-run", "resolve + validate, print the planned action as JSON, and exit without writing");
4397
- cmd.option("--validate-only", "validate flags/enums/refs, print {ok,planned} or the C2 error envelope, and exit without writing");
4398
- const registerAction = cmd.action.bind(cmd);
4399
- cmd.action = ((handler) => registerAction(async (...actionArgs) => {
4400
- const command = actionArgs[actionArgs.length - 1];
4401
- const opts = command.opts();
4402
- const positionals = actionArgs.slice(0, -2);
4403
- const out = await planMutation(opts, positionals, commandPath(command), planFn);
4404
- if (out) {
4405
- console.log(JSON.stringify(out));
4406
- return;
4482
+ };
4483
+ function incrementNodeInspectorPort(args) {
4484
+ return args.map((arg) => {
4485
+ if (!arg.startsWith("--inspect")) {
4486
+ return arg;
4407
4487
  }
4408
- return handler(...actionArgs);
4409
- }));
4410
- return cmd;
4411
- }
4412
- function installProcessBackstop() {
4413
- process.on("unhandledRejection", (reason) => void failGraceful(reason instanceof Error ? reason.message : String(reason)));
4414
- process.on("uncaughtException", (err) => void failGraceful(err instanceof Error ? err.message : String(err)));
4415
- }
4416
- function rawFlag(flag) {
4417
- return process.argv.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
4418
- }
4419
- function rawValue(flag, fallback) {
4420
- const index = process.argv.indexOf(flag);
4421
- if (index >= 0 && process.argv[index + 1]) return process.argv[index + 1];
4422
- const inline = process.argv.find((arg) => arg.startsWith(`${flag}=`));
4423
- return inline ? inline.slice(flag.length + 1) : fallback;
4488
+ let debugOption;
4489
+ let debugHost = "127.0.0.1";
4490
+ let debugPort = "9229";
4491
+ let match;
4492
+ if ((match = arg.match(/^(--inspect(-brk)?)$/)) !== null) {
4493
+ debugOption = match[1];
4494
+ } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+)$/)) !== null) {
4495
+ debugOption = match[1];
4496
+ if (/^\d+$/.test(match[3])) {
4497
+ debugPort = match[3];
4498
+ } else {
4499
+ debugHost = match[3];
4500
+ }
4501
+ } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/)) !== null) {
4502
+ debugOption = match[1];
4503
+ debugHost = match[3];
4504
+ debugPort = match[4];
4505
+ }
4506
+ if (debugOption && debugPort !== "0") {
4507
+ return `${debugOption}=${debugHost}:${parseInt(debugPort) + 1}`;
4508
+ }
4509
+ return arg;
4510
+ });
4424
4511
  }
4425
- function printLine(value) {
4426
- (0, import_node_fs5.writeSync)(1, `${value}
4427
- `);
4512
+ function useColor() {
4513
+ if (import_node_process.default.env.NO_COLOR || import_node_process.default.env.FORCE_COLOR === "0" || import_node_process.default.env.FORCE_COLOR === "false")
4514
+ return false;
4515
+ if (import_node_process.default.env.FORCE_COLOR || import_node_process.default.env.CLICOLOR_FORCE !== void 0)
4516
+ return true;
4517
+ return void 0;
4428
4518
  }
4429
4519
 
4520
+ // node_modules/commander/index.js
4521
+ var program = new Command();
4522
+
4523
+ // src/command-composition.ts
4524
+ var import_node_fs48 = require("node:fs");
4525
+ init_clean_exit();
4526
+ init_cli_shared();
4527
+
4430
4528
  // src/doctor-fleet-drift.ts
4431
4529
  var import_node_fs6 = require("node:fs");
4432
4530
  var import_node_os2 = require("node:os");
@@ -4599,6 +4697,9 @@ function readFleetDrift(env, activeSurfaces) {
4599
4697
  }
4600
4698
  }
4601
4699
 
4700
+ // src/command-composition.ts
4701
+ init_error_codes();
4702
+
4602
4703
  // src/git-clean-tree.ts
4603
4704
  function isAgentScratchPath(path2) {
4604
4705
  const normalized = path2.replace(/\\/g, "/").trim();
@@ -4804,6 +4905,7 @@ function excessArgumentMessage(extra, corrected) {
4804
4905
  // src/released-version-cache.ts
4805
4906
  var import_node_fs7 = require("node:fs");
4806
4907
  var import_node_path5 = require("node:path");
4908
+ init_client_version();
4807
4909
  var RELEASED_VERSION_CACHE_MS = 24 * 36e5;
4808
4910
  function releasedVersionCachePath(runtimeRoot) {
4809
4911
  return (0, import_node_path5.join)(runtimeRoot, "head-ts", ".released-version");
@@ -5309,8 +5411,12 @@ var import_node_path46 = require("node:path");
5309
5411
  var import_node_fs13 = require("node:fs");
5310
5412
  var import_node_os9 = require("node:os");
5311
5413
  var import_node_path11 = require("node:path");
5414
+ init_cli_shared();
5415
+ init_github_client();
5312
5416
 
5313
5417
  // src/board-snapshot.ts
5418
+ init_fetch_retry();
5419
+ init_client_version();
5314
5420
  var BOARD_SNAPSHOT_TIMEOUT_MS = 25e3;
5315
5421
  function isSnapshotShape(body) {
5316
5422
  const b = body;
@@ -5358,6 +5464,7 @@ async function fetchHubBoardSnapshot(request, deps) {
5358
5464
  }
5359
5465
 
5360
5466
  // src/github-quota.ts
5467
+ init_github_client();
5361
5468
  var RATE_LIMIT_WAIT_CAP_MS = 12e4;
5362
5469
  var RateLimitedError = class extends Error {
5363
5470
  receipt;
@@ -5899,6 +6006,9 @@ async function withAlreadyClosedCommitTargets(input, fetchIssueState) {
5899
6006
  return alreadyClosed.length ? { ...input, alreadyClosed: [.../* @__PURE__ */ new Set([...input.alreadyClosed ?? [], ...alreadyClosed])] } : input;
5900
6007
  }
5901
6008
 
6009
+ // src/repo-resolve.ts
6010
+ init_cli_shared();
6011
+
5902
6012
  // src/registry-degrade.ts
5903
6013
  function registryDegradeError(error) {
5904
6014
  return new Error(
@@ -5913,6 +6023,10 @@ var ORG_CONFIG_PATH = "/org/config";
5913
6023
  var PROJECTS_ENVELOPE_KEY = "projects";
5914
6024
  var REGISTRY_FETCH_TIMEOUT_MS = 8e3;
5915
6025
 
6026
+ // src/registry-client.ts
6027
+ init_fetch_retry();
6028
+ init_client_version();
6029
+
5916
6030
  // src/workflow-context.ts
5917
6031
  function parseWorkflowJobs(yaml) {
5918
6032
  const lines2 = yaml.split(/\r?\n/);
@@ -6969,6 +7083,11 @@ function boardConfigFromProject(meta, floor = {}) {
6969
7083
  };
6970
7084
  }
6971
7085
 
7086
+ // src/cli-doctor-shared.ts
7087
+ init_cli_shared();
7088
+ init_hub_auth();
7089
+ init_github_client();
7090
+
6972
7091
  // src/project-model.ts
6973
7092
  var PROJECT_TYPES = ["web-app", "hub-service", "content", "desktop-app", "desktop-game", "mobile-app", "non-deployable", "cli-tool", "worker"];
6974
7093
  var DEPLOY_MODELS = ["hub-serverless", "serverless", "tenant-container", "solo-container", "static-cdn", "registry-publish", "content", "none"];
@@ -7204,6 +7323,8 @@ ${lines2.join("\n")}`;
7204
7323
  }
7205
7324
 
7206
7325
  // src/issue-surface.ts
7326
+ init_cli_shared();
7327
+ init_error_codes();
7207
7328
  var SURFACE_PREFIX = "surface:";
7208
7329
  var SURFACE_READ_TIMEOUT_MS = 1e4;
7209
7330
  function labelsCarrySurface(labels) {
@@ -7342,6 +7463,7 @@ function shouldWithdrawSurfaceFlag(result) {
7342
7463
  }
7343
7464
 
7344
7465
  // src/secrets-transport.ts
7466
+ init_client_version();
7345
7467
  var OWNER = "mutmutco";
7346
7468
  var SSM_ROOT = "/mmi-future";
7347
7469
  var PROJECT_TIER_SEGMENT = "dev";
@@ -7959,6 +8081,7 @@ async function secretsPreflight(deps, opts) {
7959
8081
  // src/secrets-execution.ts
7960
8082
  var import_node_child_process4 = require("node:child_process");
7961
8083
  var import_node_os5 = require("node:os");
8084
+ init_clean_exit();
7962
8085
 
7963
8086
  // src/gh-create.ts
7964
8087
  var import_promises = require("node:fs/promises");
@@ -7994,6 +8117,8 @@ function isPriorityFieldConfigured(cfg) {
7994
8117
  }
7995
8118
 
7996
8119
  // src/gh-create.ts
8120
+ init_cli_shared();
8121
+ init_github_client();
7997
8122
  var ISSUE_TYPES = ["bug", "feature", "task"];
7998
8123
  var GH_MUTATION_TIMEOUT_MS = 12e4;
7999
8124
  function timeoutKillNote(err, timeoutMs) {
@@ -9790,6 +9915,9 @@ function readSettingsAutoUpdate(raw, name) {
9790
9915
  var CATALOG_CONTENT_REF = "main";
9791
9916
  var CATALOG_REF_PIN_STEPS = `add \`"ref": "${CATALOG_CONTENT_REF}"\` to this marketplace's \`source\` in ~/.claude/plugins/known_marketplaces.json, then restart Claude`;
9792
9917
 
9918
+ // src/plugin-guard-io.ts
9919
+ init_cli_shared();
9920
+
9793
9921
  // src/plugin-guard.ts
9794
9922
  function buildPluginGuardDecision(i) {
9795
9923
  if (!i.isOrgRepo) return { state: "not-org" };
@@ -10663,6 +10791,9 @@ function describeSessionIdentity(env = process.env) {
10663
10791
  };
10664
10792
  }
10665
10793
 
10794
+ // src/board-doctor-config.ts
10795
+ init_github_client();
10796
+
10666
10797
  // ../infra/board-vocab.mjs
10667
10798
  var BOARD_STATUSES = ["Todo", "In Progress", "In Review", "Done"];
10668
10799
 
@@ -10993,7 +11124,14 @@ mutation($projectId: ID!, $itemId: ID!) {
10993
11124
  }
10994
11125
  }`;
10995
11126
  async function updateItemSingleSelect(client, projectId, itemId, fieldId, optionId) {
10996
- await client.graphql(UPDATE_ITEM_FIELD_MUTATION, { projectId, itemId, fieldId, optionId });
11127
+ try {
11128
+ await client.graphql(UPDATE_ITEM_FIELD_MUTATION, { projectId, itemId, fieldId, optionId });
11129
+ } catch (error) {
11130
+ const errors = error.graphqlErrors;
11131
+ if (!errors?.length || !errors.every((entry) => entry.type === "INSUFFICIENT_SCOPES")) throw error;
11132
+ const { writeHubBoardField: writeHubBoardField2 } = await Promise.resolve().then(() => (init_board_write(), board_write_exports));
11133
+ await writeHubBoardField2({ projectId, itemId, fieldId, optionId });
11134
+ }
10997
11135
  }
10998
11136
  function parseIssueSelector(selector, defaultRepo, expectedRepo) {
10999
11137
  const parsed = parseIssueRef(selector, expectedRepo);
@@ -12014,7 +12152,11 @@ function ghError(e) {
12014
12152
  return (err.stderr || err.message || String(e)).trim();
12015
12153
  }
12016
12154
 
12155
+ // src/board-claim-move.ts
12156
+ init_github_client();
12157
+
12017
12158
  // src/sub-issue.ts
12159
+ init_error_codes();
12018
12160
  function buildResolveIdArgs(ref) {
12019
12161
  const args = ["issue", "view", String(ref.number), "--json", "id", "--jq", ".id"];
12020
12162
  if (ref.repo) args.push("--repo", ref.repo);
@@ -12651,6 +12793,7 @@ async function postClaimMarkerComment(client, item, actor = describeSessionIdent
12651
12793
  }
12652
12794
 
12653
12795
  // src/board-cleanup.ts
12796
+ init_github_client();
12654
12797
  async function removeRepoBoardItems(options, deps = {}) {
12655
12798
  const cfg = resolveBoardConfig(options.config);
12656
12799
  const client = deps.client ?? defaultGitHubClient();
@@ -13041,6 +13184,7 @@ async function waitForPrChecks(deps) {
13041
13184
  }
13042
13185
 
13043
13186
  // src/bootstrap-ruleset.ts
13187
+ init_github_client();
13044
13188
  var PRODUCT_RULESET_NAME = "mmi-product-required-checks";
13045
13189
  var PRODUCT_GATE_CONTEXT = "gate";
13046
13190
  var PRODUCT_RULESET_PATH = ".github/rulesets/mmi-product-required-checks.json";
@@ -14951,6 +15095,9 @@ function isJervHubRepo(repo) {
14951
15095
  var import_node_fs18 = require("node:fs");
14952
15096
  var import_node_path16 = require("node:path");
14953
15097
 
15098
+ // src/fleet-lockstep-enforce.ts
15099
+ init_github_client();
15100
+
14954
15101
  // src/bootstrap-org-ruleset.ts
14955
15102
  var ORG_NO_AGENT_FILES_RULESET_NAME = "mmi-no-agent-files-org";
14956
15103
  var ORG_LOGIN = "mutmutco";
@@ -15106,32 +15253,33 @@ function collaboratorRole(c) {
15106
15253
  async function auditRepoCollaborators(repo, owners, deps, projectAdmins = /* @__PURE__ */ new Set(), sanctionedAdmins = /* @__PURE__ */ new Set()) {
15107
15254
  const collabs = await restPagedJson(deps, `repos/${repo}/collaborators?affiliation=direct`, []);
15108
15255
  const findings = [];
15109
- const writeOrBetter = /* @__PURE__ */ new Set();
15256
+ const adminLogins = /* @__PURE__ */ new Set();
15257
+ const listed = (logins, login) => [...logins].some((entry) => entry.toLowerCase() === login.toLowerCase());
15110
15258
  for (const c of collabs) {
15111
- if (owners.has(c.login)) continue;
15259
+ if (listed(owners, c.login)) continue;
15112
15260
  const role = collaboratorRole(c);
15113
- writeOrBetter.add(c.login);
15114
- if (OVERGRANT_ROLES.has(role) && sanctionedAdmins.has(c.login)) continue;
15261
+ if (role === "admin") adminLogins.add(c.login.toLowerCase());
15262
+ if (OVERGRANT_ROLES.has(role) && (listed(projectAdmins, c.login) || listed(sanctionedAdmins, c.login))) continue;
15115
15263
  if (OVERGRANT_ROLES.has(role)) {
15116
15264
  findings.push({
15117
15265
  repo,
15118
15266
  kind: "collaborator-overgrant",
15119
15267
  severity: "high",
15120
15268
  actor: c.login,
15121
- detail: `direct collaborator @${c.login} holds role '${role}'; a developer must be 'write' (admin/maintain is master-only)`,
15269
+ detail: `direct collaborator @${c.login} holds role '${role}' without a project-admin registration or owner sanction`,
15122
15270
  remediation: `gh api -X PUT repos/${repo}/collaborators/${c.login} -f permission=push`
15123
15271
  });
15124
15272
  }
15125
15273
  }
15126
15274
  for (const login of projectAdmins) {
15127
- if (owners.has(login) || writeOrBetter.has(login)) continue;
15275
+ if (listed(owners, login) || adminLogins.has(login.toLowerCase())) continue;
15128
15276
  findings.push({
15129
15277
  repo,
15130
15278
  kind: "collaborator-undergrant",
15131
15279
  severity: "medium",
15132
15280
  actor: login,
15133
- detail: `@${login} is a declared project-admin of ${repo} in the registry but holds no direct collaborator role; secrets self-serve and train authority fail closed for them`,
15134
- remediation: `gh api -X PUT repos/${repo}/collaborators/${login} -f permission=push`
15281
+ detail: `@${login} is a declared project-admin of ${repo} but lacks the required repository admin role`,
15282
+ remediation: `gh api -X PUT repos/${repo}/collaborators/${login} -f permission=admin`
15135
15283
  });
15136
15284
  }
15137
15285
  return findings;
@@ -15414,6 +15562,13 @@ function renderAccessReport(report) {
15414
15562
  return lines2.join("\n");
15415
15563
  }
15416
15564
 
15565
+ // src/fleet-lockstep-enforce.ts
15566
+ init_clean_exit();
15567
+ init_cli_shared();
15568
+
15569
+ // ../infra/src/version-gate.ts
15570
+ init_compat();
15571
+
15417
15572
  // ../infra/src/infra-constants.ts
15418
15573
  var HUB_REPO3 = "mutmutco/MMI-Hub";
15419
15574
  var STAGES = ["dev", "rc", "main"];
@@ -15703,10 +15858,10 @@ var rollout_plan_default = {
15703
15858
  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)."
15704
15859
  },
15705
15860
  baseline: {
15706
- version: "4.3.45",
15707
- tag: "v4.3.45",
15708
- commit: "519cee3af091",
15709
- npm: "@mutmutco/cli@4.3.45"
15861
+ version: "4.3.47",
15862
+ tag: "v4.3.47",
15863
+ commit: "9c81f5030532",
15864
+ npm: "@mutmutco/cli@4.3.47"
15710
15865
  },
15711
15866
  exitCriterion: "fleet-n-of-n",
15712
15867
  hubOnlyShortcut: "forbidden",
@@ -15723,14 +15878,14 @@ var rollout_plan_default = {
15723
15878
  repo: "mutmutco/mmi-hub",
15724
15879
  role: "canary",
15725
15880
  schedule: "train",
15726
- v3Target: "v4.3.45"
15881
+ v3Target: "v4.3.47"
15727
15882
  }
15728
15883
  ],
15729
15884
  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.",
15730
15885
  rollback: {
15731
15886
  independent: true,
15732
- mechanism: "npm dist-tag latest -> 4.3.45 and redeploy the Hub Lambda from tag v4.3.45 (519cee3af091); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15733
- v3Target: "v4.3.45 (@mutmutco/cli@4.3.45, tag commit 519cee3af091 \u2014 last known-good release carrying the repo-index v4-only contract)"
15887
+ mechanism: "npm dist-tag latest -> 4.3.47 and redeploy the Hub Lambda from tag v4.3.47 (9c81f5030532); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15888
+ v3Target: "v4.3.47 (@mutmutco/cli@4.3.47, tag commit 9c81f5030532 \u2014 last known-good release carrying the repo-index v4-only contract)"
15734
15889
  }
15735
15890
  },
15736
15891
  {
@@ -15888,6 +16043,13 @@ var rollout_plan_default = {
15888
16043
  ]
15889
16044
  };
15890
16045
 
16046
+ // src/fleet-lockstep-gate.ts
16047
+ init_clean_exit();
16048
+
16049
+ // src/fleet-track-inventory.ts
16050
+ init_cli_shared();
16051
+ init_clean_exit();
16052
+
15891
16053
  // src/train-status.ts
15892
16054
  function buildTrainStatusReport(input) {
15893
16055
  const bumpIntent = input.bumpIntent ?? process.env.MMI_BUMP_INTENT;
@@ -16036,6 +16198,10 @@ function formatOrgVersions(report) {
16036
16198
  return lines2.join("\n");
16037
16199
  }
16038
16200
 
16201
+ // src/query-commands.ts
16202
+ init_cli_shared();
16203
+ init_error_codes();
16204
+
16039
16205
  // src/issue-view-json.ts
16040
16206
  var DEFAULT_ISSUE_VIEW_FIELDS = "number,title,state,url,labels,author,assignees,milestone,body";
16041
16207
  function normalizeIssueViewJsonFields(tokens2) {
@@ -19592,6 +19758,7 @@ var import_node_path19 = require("node:path");
19592
19758
  // src/jerv-cli-spawn.ts
19593
19759
  var import_node_os11 = require("node:os");
19594
19760
  var import_node_path18 = require("node:path");
19761
+ init_cli_shared();
19595
19762
  var JERV_CLI_ENTRY = (0, import_node_path18.join)("node_modules", "@jervaise", "jerv-cli", "dist", "index.cjs");
19596
19763
  function pathEnvEntries(pathEnv, platform2 = process.platform) {
19597
19764
  if (platform2 !== "win32") {
@@ -21335,8 +21502,12 @@ async function beginReleaseLedger(deps, cwd, targetTag) {
21335
21502
  });
21336
21503
  }
21337
21504
 
21505
+ // src/train-doctor.ts
21506
+ init_client_version();
21507
+
21338
21508
  // src/train-self-converge.ts
21339
21509
  var import_node_child_process8 = require("node:child_process");
21510
+ init_cli_shared();
21340
21511
  var SELF_CONVERGE_INSTALL_TIMEOUT_MS = 12e4;
21341
21512
  var SELF_CONVERGE_VERSION_TIMEOUT_MS = 3e4;
21342
21513
  var isWin2 = process.platform === "win32";
@@ -21698,7 +21869,7 @@ async function runTrainDoctor(input) {
21698
21869
  const tip = await git3(["rev-parse", "--verify", "--quiet", `refs/remotes/origin/${startBranch}`]);
21699
21870
  const checkRuns = JSON.parse(await train.run("gh", ["api", `repos/${repo}/commits/${tip}/check-runs`, "--jq", TRAIN_CHECK_RUNS_JQ]));
21700
21871
  const statuses = JSON.parse(await train.run("gh", ["api", `repos/${repo}/commits/${tip}/status`, "--jq", TRAIN_COMMIT_STATUS_JQ]));
21701
- const observed = required.filter((c) => checkRuns.some((r) => r.name === c) || statuses.some((s) => s.context === c));
21872
+ const observed = required.filter((c) => checkRuns.some((r) => r.name === c && !(r.status === "completed" && (r.conclusion === "skipped" || r.conclusion === "neutral"))) || statuses.some((s) => s.context === c));
21702
21873
  const red = observed.filter((c) => resolveContextState(c, checkRuns, statuses) === "failed");
21703
21874
  const inFlight = observed.filter((c) => resolveContextState(c, checkRuns, statuses) === "pending");
21704
21875
  const at = `${startBranch}@${tip.slice(0, 12)}`;
@@ -24804,6 +24975,9 @@ async function applyCiReconcileRepo(repo, deps) {
24804
24975
  return finalizeCiReconcile(repo, deps, result, report);
24805
24976
  }
24806
24977
 
24978
+ // src/command-composition.ts
24979
+ init_client_version();
24980
+
24807
24981
  // src/command-consolidation.ts
24808
24982
  function child(parent, name) {
24809
24983
  const found = parent.commands.find((command) => command.name() === name);
@@ -24860,7 +25034,12 @@ function consolidateCommandNamespaces(program3) {
24860
25034
  move(program3, stage, "port-range");
24861
25035
  }
24862
25036
 
25037
+ // src/command-manifest.ts
25038
+ init_error_codes();
25039
+ init_house_map();
25040
+
24863
25041
  // src/command-taxonomy.ts
25042
+ init_house_map();
24864
25043
  var COMMAND_METADATA = /* @__PURE__ */ Symbol.for("mmi.commandTaxonomy.metadata");
24865
25044
  var PRIMARY_GROUPS = [
24866
25045
  ["Orient", ["onboard", "status", "next", "doctor", "whoami", "commands", "explain"]],
@@ -25401,6 +25580,9 @@ function formatManifestHuman(manifest, options = {}) {
25401
25580
  return lines2.join("\n");
25402
25581
  }
25403
25582
 
25583
+ // src/command-composition.ts
25584
+ init_house_map();
25585
+
25404
25586
  // src/plugin-cache-prune.ts
25405
25587
  var PLUGIN_CACHE_KEEP = 2;
25406
25588
  var VERSION_DIR = /^\d+\.\d+\.\d+(?:[-+].*)?$/;
@@ -25761,7 +25943,13 @@ async function activateAppActor(commandPath3, env, mint) {
25761
25943
  return "app";
25762
25944
  }
25763
25945
 
25946
+ // src/board-commands.ts
25947
+ init_cli_shared();
25948
+ init_clean_exit();
25949
+ init_error_codes();
25950
+
25764
25951
  // src/config-load.ts
25952
+ init_cli_shared();
25765
25953
  var discoveredConfig = null;
25766
25954
  var lastDiscoverMiss = null;
25767
25955
  function lastBoardDiscoverMiss() {
@@ -26105,6 +26293,8 @@ function registerBoardCommands(program3) {
26105
26293
  var import_node_fs25 = require("node:fs");
26106
26294
  var import_node_os14 = require("node:os");
26107
26295
  var import_node_path25 = require("node:path");
26296
+ init_cli_shared();
26297
+ init_clean_exit();
26108
26298
 
26109
26299
  // src/command-plans.ts
26110
26300
  function parseTrainTag(tag) {
@@ -26268,6 +26458,13 @@ function renderSteps(title, steps) {
26268
26458
  ].join("\n");
26269
26459
  }
26270
26460
 
26461
+ // src/bootstrap-commands.ts
26462
+ init_house_map();
26463
+ init_github_client();
26464
+
26465
+ // src/port-range-assign.ts
26466
+ init_cli_shared();
26467
+
26271
26468
  // src/port-registry.ts
26272
26469
  var import_node_fs24 = require("node:fs");
26273
26470
  var import_node_path24 = require("node:path");
@@ -27350,10 +27547,10 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
27350
27547
  try {
27351
27548
  const owners = new Set(await resolveOwners(deps));
27352
27549
  const sanctioned = new Set(deps.sanctionedAdmins ?? []);
27353
- const overgrants = (await auditRepoCollaborators(repo, owners, deps, /* @__PURE__ */ new Set(), sanctioned)).filter((f) => f.kind === "collaborator-overgrant");
27550
+ const overgrants = (await auditRepoCollaborators(repo, owners, deps, new Set(deps.projectAdmins ?? []), sanctioned)).filter((f) => f.kind === "collaborator-overgrant");
27354
27551
  checks.push({
27355
27552
  ok: overgrants.length === 0,
27356
- label: "collaborator roles are master-only (no admin/maintain over-grant)",
27553
+ label: "repository admin roles match project admins and owner sanctions",
27357
27554
  detail: overgrants.length ? `over-granted: ${overgrants.map((f) => f.actor).join(", ")}` : sanctioned.size ? `sanctioned admin: ${[...sanctioned].join(", ")} (access-matrix.json, an owner decision)` : void 0
27358
27555
  });
27359
27556
  } catch {
@@ -27671,6 +27868,7 @@ function renderBootstrapVerifyReport(report) {
27671
27868
 
27672
27869
  // src/bootstrap-seed-delivery.ts
27673
27870
  var import_node_crypto9 = require("node:crypto");
27871
+ init_github_client();
27674
27872
  var SHA = /^[a-f0-9]{40}$/;
27675
27873
  async function readSeedFile(repo, target, ref, client) {
27676
27874
  try {
@@ -27802,6 +28000,7 @@ function registerBootstrapCommands(program3) {
27802
28000
  let report = await verifyBootstrap(repo, o.class, {
27803
28001
  client: defaultGitHubClient(),
27804
28002
  projectMeta: meta,
28003
+ projectAdmins: meta?.projectAdmins,
27805
28004
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
27806
28005
  deployFacts,
27807
28006
  deployFactsRead: deployFacts !== null,
@@ -28926,6 +29125,8 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
28926
29125
  }
28927
29126
 
28928
29127
  // src/call-telemetry.ts
29128
+ init_client_version();
29129
+ init_hub_url();
28929
29130
  var TELEMETRY_TIMEOUT_MS = 2e3;
28930
29131
  var CI_MARKERS = [
28931
29132
  "CI",
@@ -28966,6 +29167,10 @@ function emitCliCallTelemetry(command) {
28966
29167
  }
28967
29168
  }
28968
29169
 
29170
+ // src/deploy-commands.ts
29171
+ init_cli_shared();
29172
+ init_clean_exit();
29173
+
28969
29174
  // src/deploy-status.ts
28970
29175
  function buildDeployStatusReport(input) {
28971
29176
  const deploy = input.deployFacts?.stages[input.stage] ?? null;
@@ -29065,6 +29270,9 @@ function registerDeployCommands(program3) {
29065
29270
  });
29066
29271
  }
29067
29272
 
29273
+ // src/deploy-guards.ts
29274
+ init_cli_shared();
29275
+
29068
29276
  // src/project-set.ts
29069
29277
  var UNSET_KEYS = ["oauth", "requiredRuntimeSecrets", "requiredBuildSecrets", "tenantTasks", "secrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "releaseChannel", "releaseLanguage", "dsManifestPath", "fofuEnabled", "consumesDesignSystem", "ci", "requiredChecks", "requiredCheckBranches", "ciExemptReason", "gate", "seedCanary"];
29070
29278
  var UNSET_KEY_SET = new Set(UNSET_KEYS);
@@ -29844,6 +30052,10 @@ Rollback only after independent verification: set-deploy --no-env-file false; pr
29844
30052
  return { ok: true };
29845
30053
  }
29846
30054
 
30055
+ // src/deploy-port-doctor.ts
30056
+ init_fetch_retry();
30057
+ init_client_version();
30058
+
29847
30059
  // ../infra/src/deploy-port-collision.ts
29848
30060
  function formatDeployPortCollisionReportLine(collision) {
29849
30061
  const owners = collision.owners.map((o) => `${o.slug}/${o.stage}`).join(", ");
@@ -29914,6 +30126,9 @@ function renderDeployPortDoctor(report) {
29914
30126
  return lines2;
29915
30127
  }
29916
30128
 
30129
+ // src/discovery-commands.ts
30130
+ init_cli_shared();
30131
+
29917
30132
  // src/wave-status.ts
29918
30133
  var import_node_fs28 = require("node:fs");
29919
30134
 
@@ -30790,6 +31005,8 @@ async function collectWaveStatus(deps) {
30790
31005
  }
30791
31006
 
30792
31007
  // src/discovery-commands.ts
31008
+ init_github_client();
31009
+ init_cli_shared();
30793
31010
  var import_node_fs29 = require("node:fs");
30794
31011
  var import_node_os15 = require("node:os");
30795
31012
  var import_node_path28 = require("node:path");
@@ -32620,6 +32837,9 @@ async function runDoctorClean(opts, io, deps) {
32620
32837
  }
32621
32838
 
32622
32839
  // src/explain-command.ts
32840
+ init_cli_shared();
32841
+ init_error_codes();
32842
+ init_house_map();
32623
32843
  var LOOP_PLAYBOOKS = {
32624
32844
  agent: {
32625
32845
  title: "Agent",
@@ -32935,12 +33155,20 @@ function parseOriginRepo(remoteUrl) {
32935
33155
  return `${match[1]}/${match[2]}`;
32936
33156
  }
32937
33157
 
33158
+ // src/command-composition.ts
33159
+ init_github_client();
33160
+
32938
33161
  // src/issue-commands.ts
32939
33162
  var import_node_fs30 = require("node:fs");
32940
33163
  var import_node_crypto10 = require("node:crypto");
33164
+ init_cli_shared();
33165
+ init_clean_exit();
33166
+ init_error_codes();
33167
+ init_github_client();
32941
33168
 
32942
33169
  // src/issue-body.ts
32943
33170
  var import_node_os16 = require("node:os");
33171
+ init_error_codes();
32944
33172
  var TextArgError = class extends Error {
32945
33173
  constructor(message2, code, offendingFlag) {
32946
33174
  super(message2);
@@ -33003,6 +33231,8 @@ async function resolveIssueTitle(input, deps) {
33003
33231
  }
33004
33232
 
33005
33233
  // src/learning-closure-rate.ts
33234
+ init_cli_shared();
33235
+ init_error_codes();
33006
33236
  var CLOSURE_RATE_TIMEOUT_MS = 2e4;
33007
33237
  var LOOP_ITEM_CAP = 100;
33008
33238
  var EVIDENCE_CONCURRENCY = 4;
@@ -33919,6 +34149,8 @@ ${lines2}`, {
33919
34149
  }
33920
34150
 
33921
34151
  // src/learning-pickup.ts
34152
+ init_cli_shared();
34153
+ init_github_client();
33922
34154
  var LEARNING_PICKUP_CMD = ["mmi-cli", "learning", "pickup"].join(" ");
33923
34155
  var LEARNING_CLAIM_MARKER_LIVE_MS = 24 * 60 * 6e4;
33924
34156
  function isLearningChannelItem(labels) {
@@ -34179,6 +34411,7 @@ function registerOrgHealthQuery(program3, deps = defaultOrgHealthQueryDeps()) {
34179
34411
  }
34180
34412
 
34181
34413
  // src/pr-checks-rest.ts
34414
+ init_cli_shared();
34182
34415
  var REST_GH_TIMEOUT_MS = 2e4;
34183
34416
  var pollTokenProvider;
34184
34417
  var pollTokenOnce;
@@ -34609,6 +34842,8 @@ async function fetchRestCorePool(gh = defaultGhApi) {
34609
34842
 
34610
34843
  // src/pr-commands.ts
34611
34844
  var import_promises4 = require("node:fs/promises");
34845
+ init_cli_shared();
34846
+ init_error_codes();
34612
34847
  var GC_GH_TIMEOUT_MS2 = 2e4;
34613
34848
  var CHECKS_WATCH_POLL_MS = 15e3;
34614
34849
  var CHECKS_WATCH_TIMEOUT_MS = 10 * 6e4;
@@ -35179,6 +35414,9 @@ function writeError(res) {
35179
35414
  var import_promises5 = require("node:fs/promises");
35180
35415
  var import_node_child_process11 = require("node:child_process");
35181
35416
  var import_node_util6 = require("node:util");
35417
+ init_clean_exit();
35418
+ init_github_client();
35419
+ init_cli_shared();
35182
35420
  var execFileP4 = (0, import_node_util6.promisify)(import_node_child_process11.execFile);
35183
35421
  var AWS_REGION = "eu-central-1";
35184
35422
  var AWS_TIMEOUT_MS = 3e4;
@@ -35497,6 +35735,9 @@ function registerSchedulesCommands(program3) {
35497
35735
  var import_node_fs32 = require("node:fs");
35498
35736
  var import_node_path30 = require("node:path");
35499
35737
  var import_node_os17 = require("node:os");
35738
+ init_cli_shared();
35739
+ init_hub_auth();
35740
+ init_github_client();
35500
35741
 
35501
35742
  // src/secrets-diff.ts
35502
35743
  var TIMEOUT_MS2 = 8e3;
@@ -35876,6 +36117,7 @@ function registerSecretsCommands(program3) {
35876
36117
  }
35877
36118
 
35878
36119
  // src/session-report.ts
36120
+ init_cli_shared();
35879
36121
  var GC_GH_TIMEOUT_MS3 = 2e4;
35880
36122
  function shapeSessionIssues(report) {
35881
36123
  if (!report) return [];
@@ -35995,6 +36237,8 @@ function registerSessionReport(program3) {
35995
36237
  // src/stage-commands.ts
35996
36238
  var import_node_fs33 = require("node:fs");
35997
36239
  var import_node_path31 = require("node:path");
36240
+ init_cli_shared();
36241
+ init_clean_exit();
35998
36242
 
35999
36243
  // src/stage-default.ts
36000
36244
  function shellFor() {
@@ -36063,39 +36307,7 @@ function decideStage(inputs) {
36063
36307
  }
36064
36308
 
36065
36309
  // src/stage-live.ts
36066
- var import_node_net2 = require("node:net");
36067
36310
  var STAGE_LIVE_HUB_REPO = "mutmutco/MMI-Hub";
36068
- var IP_ECHO_URL = "https://api.ipify.org";
36069
- var IP6_ECHO_URL = "https://api6.ipify.org";
36070
- var IP_DETECT_TIMEOUT_MS = 1e4;
36071
- function validStageLiveIp(ip) {
36072
- return (0, import_node_net2.isIP)(ip.trim()) !== 0;
36073
- }
36074
- async function detectPublicIp(fetchImpl = fetch) {
36075
- return detectPublicIpFrom(IP_ECHO_URL, fetchImpl);
36076
- }
36077
- async function detectCallerIps(fetchImpl = fetch) {
36078
- const [v4, v6] = await Promise.allSettled([
36079
- detectPublicIp(fetchImpl),
36080
- detectPublicIpFrom(IP6_ECHO_URL, fetchImpl)
36081
- ]);
36082
- if (v4.status === "rejected") throw v4.reason;
36083
- const ip = v4.value;
36084
- const candidate = v6.status === "fulfilled" ? v6.value : void 0;
36085
- return { ip, ip6: candidate && (0, import_node_net2.isIP)(candidate) === 6 ? candidate : void 0 };
36086
- }
36087
- async function detectPublicIpFrom(url, fetchImpl) {
36088
- let res;
36089
- try {
36090
- res = await fetchImpl(url, { signal: AbortSignal.timeout(IP_DETECT_TIMEOUT_MS) });
36091
- } catch (e) {
36092
- throw new Error(`public IP detection failed (${url}): ${e.message}`);
36093
- }
36094
- if (!res.ok) throw new Error(`public IP detection failed: HTTP ${res.status} from ${url}`);
36095
- const ip = (await res.text()).trim();
36096
- if (!validStageLiveIp(ip)) throw new Error(`public IP detection returned a non-IP body from ${url}: "${ip.slice(0, 80)}"`);
36097
- return ip;
36098
- }
36099
36311
  function isStageLiveDeployModel(deployModel, projectType) {
36100
36312
  if (deployModel) return deployModel === "tenant-container";
36101
36313
  return projectType === "web-app";
@@ -36107,16 +36319,14 @@ function stageLiveUnsupportedReason(deployModel, projectType) {
36107
36319
  }
36108
36320
  function stageLiveUpSteps(t) {
36109
36321
  return [
36110
- { label: `detect your public IPv4 and IPv6 (${IP_ECHO_URL} + ${IP6_ECHO_URL}, bounded, in parallel)` },
36322
+ { label: `clear legacy IP gates for ${t.host} via the Hub backend (tenant-control cf-gate-clear; no new rules)` },
36111
36323
  { label: `deploy ${t.ref ?? "<current branch>"} to the ${t.slug} dev stage via the Hub backend (tenant-deploy)` },
36112
- { label: `gate ${t.host} to your IPv4 and your IPv6 /64 at the Cloudflare edge via the Hub backend (tenant-control cf-gate-allow)` },
36113
36324
  { label: "tear down when done", command: "mmi-cli stage --live --down --apply" }
36114
36325
  ];
36115
36326
  }
36116
36327
  function stageLiveDownSteps(t) {
36117
36328
  return [
36118
- { label: `stop the ${t.slug} dev runtime via the Hub backend (tenant-control stop)` },
36119
- { label: `remove the Cloudflare edge gate for ${t.host} via the Hub backend (tenant-control cf-gate-clear)` }
36329
+ { label: `stop the ${t.slug} dev runtime via the Hub backend (tenant-control stop)` }
36120
36330
  ];
36121
36331
  }
36122
36332
  function stageLiveDownNoop(t, reason) {
@@ -36131,39 +36341,28 @@ function stageLiveDownNoop(t, reason) {
36131
36341
  }
36132
36342
  async function runStageLiveUp(deps, t) {
36133
36343
  if (!t.ref?.trim()) throw new Error("stage --live: cannot resolve the current branch to deploy");
36134
- const detected = await deps.detectIp();
36135
- const ip = detected.ip.trim();
36136
- if (!validStageLiveIp(ip)) throw new Error(`stage --live: detected public IP is not a literal IPv4/IPv6 address: "${ip.slice(0, 80)}"`);
36137
- const ip6 = detected.ip6?.trim() || void 0;
36138
36344
  if (!t.host?.trim()) throw new Error("stage --live: cannot resolve the dev edge host (registry edgeDomains.dev)");
36345
+ await deps.control({ repo: t.repo, action: "cf-gate-clear", host: t.host });
36139
36346
  await deps.deployDev({ repo: t.repo, ref: t.ref });
36140
- await deps.control({ repo: t.repo, action: "cf-gate-allow", host: t.host, ip, ip6 });
36141
- const allowed = ip6 ? `${ip} and your IPv6 /64 prefix (from ${ip6})` : `${ip} (IPv4 only \u2014 no public IPv6 detected)`;
36142
36347
  return {
36143
36348
  command: "stage --live",
36144
36349
  mode: "up",
36145
36350
  slug: t.slug,
36146
36351
  repo: t.repo,
36147
36352
  ref: t.ref,
36148
- ip,
36149
- ip6,
36150
- dispatched: ["tenant-deploy.yml", "tenant-control.yml"],
36151
- // #2656: the gate now writes a skip+block pair and self-verifies from the runner (a non-allowed vantage),
36152
- // so a gate that failed to close fails the run RED. The stage is private only once that gate run is green.
36153
- message: `dispatched the dev deploy of ${t.ref} and the Cloudflare edge gate for ${t.host} \u2192 ${allowed}; watch the runs in ${STAGE_LIVE_HUB_REPO} Actions and treat the stage as private ONLY once the cf-gate-allow run is green (it self-verifies the host is blocked) \u2014 tear down with: mmi-cli stage --live --down --apply`
36353
+ dispatched: ["tenant-control.yml", "tenant-deploy.yml"],
36354
+ message: `dispatched legacy gate cleanup and the dev deploy of ${t.ref}; ${t.host} has no stage IP restriction once both runs succeed in ${STAGE_LIVE_HUB_REPO} Actions. The app's own sign-in still applies. Tear down with: mmi-cli stage --live --down --apply`
36154
36355
  };
36155
36356
  }
36156
36357
  async function runStageLiveDown(deps, t) {
36157
- if (!t.host?.trim()) throw new Error("stage --live: cannot resolve the dev edge host (registry edgeDomains.dev)");
36158
36358
  await deps.control({ repo: t.repo, action: "stop" });
36159
- await deps.control({ repo: t.repo, action: "cf-gate-clear", host: t.host });
36160
36359
  return {
36161
36360
  command: "stage --live",
36162
36361
  mode: "down",
36163
36362
  slug: t.slug,
36164
36363
  repo: t.repo,
36165
- dispatched: ["tenant-control.yml", "tenant-control.yml"],
36166
- message: `dispatched the dev stop and the Cloudflare edge gate clear for ${t.host}; the dev stage is dark until the next mmi-cli stage --live --apply`
36364
+ dispatched: ["tenant-control.yml"],
36365
+ message: "dispatched the dev stop; verify the workflow succeeds before treating the stage as stopped"
36167
36366
  };
36168
36367
  }
36169
36368
 
@@ -36405,13 +36604,12 @@ function registerStageCommands(program3) {
36405
36604
  }
36406
36605
  const rcDeps = registryClientDeps(await loadConfig());
36407
36606
  const deps = {
36408
- detectIp: () => detectCallerIps(),
36409
36607
  deployDev: async ({ repo, ref }) => {
36410
36608
  const res = await tenantDeploy({ repo, stage: "dev", ref }, rcDeps);
36411
36609
  if (!res.ok) throw new Error(`dev deploy dispatch failed: ${res.body?.error ?? res.error ?? `HTTP ${res.status}`}`);
36412
36610
  },
36413
- control: async ({ repo, action, host, ip, ip6 }) => {
36414
- const res = await tenantControl({ repo, stage: "dev", action, host, ip, ip6 }, rcDeps);
36611
+ control: async ({ repo, action, host }) => {
36612
+ const res = await tenantControl({ repo, stage: "dev", action, host }, rcDeps);
36415
36613
  if (!res.ok) throw new Error(`runtime tenant control ${action} dispatch failed: ${res.body?.error ?? res.error ?? `HTTP ${res.status}`}`);
36416
36614
  }
36417
36615
  };
@@ -36422,7 +36620,7 @@ function registerStageCommands(program3) {
36422
36620
  return failGraceful(`stage --live: ${e.message}`);
36423
36621
  }
36424
36622
  }
36425
- const stage = program3.command("stage").description("plan or run the repo local stage environment; --live = personal IP-gated cloud dev stage; remote rc/live move only via the gated rcand/release/hotfix train").option("--json", "machine-readable output").option("--apply", "run the full local stage: stop previous in this worktree, build, start, health-check").option("--port <port>", "loopback port for this worktree stage (1024..65535; default picks a free port in the registry range)").option("--live", "personal cloud dev stage: deploy the current branch to the dev runtime, served only to your public IP").option("--down", "with --live: stop the dev runtime and clear the IP allowlist").option("--timeout-ms <ms>", "bounded build/health timeout", "60000").action(async (o) => {
36623
+ const stage = program3.command("stage").description("plan or run the repo local stage environment; --live = on-demand cloud dev stage; remote rc/live move only via the gated rcand/release/hotfix train").option("--json", "machine-readable output").option("--apply", "run the full local stage: stop previous in this worktree, build, start, health-check").option("--port <port>", "loopback port for this worktree stage (1024..65535; default picks a free port in the registry range)").option("--live", "personal cloud dev stage: deploy the current branch to the dev runtime, without a stage IP restriction").option("--down", "with --live: stop the dev runtime").option("--timeout-ms <ms>", "bounded build/health timeout", "60000").action(async (o) => {
36426
36624
  if (o.down && !o.live) return fail("stage: --down applies to --live only (local teardown is `mmi-cli stage stop --apply`)");
36427
36625
  if (o.live) return runStageLiveCommand(o);
36428
36626
  const { resolution: res, project: project2, cfg: stageCfg } = await resolveStage();
@@ -36712,8 +36910,12 @@ function renderVerifySecrets(body) {
36712
36910
  var import_node_child_process16 = require("node:child_process");
36713
36911
  var import_node_fs41 = require("node:fs");
36714
36912
  var import_promises7 = require("node:fs/promises");
36913
+ init_clean_exit();
36914
+ init_cli_shared();
36915
+ init_error_codes();
36715
36916
 
36716
36917
  // src/session-runtime.ts
36918
+ init_house_map();
36717
36919
  var WIN32_TRAMPOLINE = "const{spawn}=require('node:child_process');const a=JSON.parse(process.argv[1]);const c=spawn(a.cmd,a.args,{stdio:'ignore',windowsHide:true,cwd:a.cwd});c.on('exit',x=>process.exit(x??0));c.on('error',()=>process.exit(1));";
36718
36920
  function spawnDetachedSelf(args, deps, opts = {}) {
36719
36921
  try {
@@ -36768,6 +36970,9 @@ async function resolveAutoAddBoardAttach(client, cfg, selector, priority, warn =
36768
36970
  return { projectItemId };
36769
36971
  }
36770
36972
 
36973
+ // src/command-register-collaboration.ts
36974
+ init_house_map();
36975
+
36771
36976
  // src/issue-check.ts
36772
36977
  var CHECKLIST_RE = /^([ \t]*[-*+] \[)([ xX])(\] )(.*)$/gm;
36773
36978
  function findChecklistItems(body) {
@@ -36975,10 +37180,15 @@ async function runPrLand(prNumber, options, deps) {
36975
37180
  };
36976
37181
  }
36977
37182
 
37183
+ // src/command-register-collaboration.ts
37184
+ init_github_client();
37185
+
36978
37186
  // src/merge-cleanup.ts
36979
37187
  var import_node_fs36 = require("node:fs");
36980
37188
  var import_node_path34 = require("node:path");
36981
37189
  var import_node_os18 = require("node:os");
37190
+ init_cli_shared();
37191
+ init_github_client();
36982
37192
 
36983
37193
  // src/board-advance.ts
36984
37194
  function repoOf2(ref) {
@@ -38263,6 +38473,7 @@ var import_node_fs37 = require("node:fs");
38263
38473
  var import_node_path35 = require("node:path");
38264
38474
 
38265
38475
  // src/cross-repo-filing-issue.ts
38476
+ init_github_client();
38266
38477
  function crossRepoFilingRetryCommand(prRepo, prNumber) {
38267
38478
  return `mmi-cli devops pr merge ${prNumber} --repo ${prRepo}`;
38268
38479
  }
@@ -38470,6 +38681,7 @@ var import_node_child_process14 = require("node:child_process");
38470
38681
  var import_node_fs38 = require("node:fs");
38471
38682
  var import_node_os19 = require("node:os");
38472
38683
  var import_node_path36 = require("node:path");
38684
+ init_cli_shared();
38473
38685
  var REVIEW_VERDICT_MARKER = "<!-- zeroci-review v1 -->";
38474
38686
  var REVIEW_VERDICTS = ["PROCEED", "CORRECT", "ESCALATE"];
38475
38687
  function isReviewVerdict(value) {
@@ -38649,6 +38861,7 @@ async function postPrCommentFromFile(number, repo, body) {
38649
38861
 
38650
38862
  // src/pr-create-docs-check.ts
38651
38863
  var import_node_child_process15 = require("node:child_process");
38864
+ init_cli_shared();
38652
38865
  var GIT_TIMEOUT_MS2 = 15e3;
38653
38866
  function catFileBatch(root, ref, paths) {
38654
38867
  if (paths.length === 0) return Promise.resolve([]);
@@ -38769,6 +38982,7 @@ async function checkDocsIndexAtHead(opts, deps) {
38769
38982
  }
38770
38983
 
38771
38984
  // src/pr-create-claim-guard.ts
38985
+ init_github_client();
38772
38986
  var CLAIM_GUARD_RATE_LIMIT_WAIT_CAP_MS = 3e4;
38773
38987
  function withRateLimitRetry(client, seams = {}) {
38774
38988
  const throttledPaths = /* @__PURE__ */ new Set();
@@ -38827,6 +39041,7 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
38827
39041
  // src/worktree-merge-cleanup.ts
38828
39042
  var import_node_fs40 = require("node:fs");
38829
39043
  var import_node_path38 = require("node:path");
39044
+ init_cli_shared();
38830
39045
 
38831
39046
  // src/worktree-evidence-archive.ts
38832
39047
  var import_node_fs39 = require("node:fs");
@@ -41208,6 +41423,9 @@ ${list}`);
41208
41423
 
41209
41424
  // src/command-register-developer.ts
41210
41425
  var import_node_fs46 = require("node:fs");
41426
+ init_clean_exit();
41427
+ init_cli_shared();
41428
+ init_error_codes();
41211
41429
 
41212
41430
  // src/whoami.ts
41213
41431
  async function resolveWhoami(deps) {
@@ -41238,6 +41456,8 @@ async function resolveWhoami(deps) {
41238
41456
 
41239
41457
  // src/command-register-developer.ts
41240
41458
  var import_node_path44 = require("node:path");
41459
+ init_house_map();
41460
+ init_hub_url();
41241
41461
 
41242
41462
  // src/wave-land.ts
41243
41463
  function planWaveLand(prs) {
@@ -41269,6 +41489,7 @@ async function executeWaveLand(plan, deps) {
41269
41489
 
41270
41490
  // src/box-commands.ts
41271
41491
  var import_node_fs42 = require("node:fs");
41492
+ init_clean_exit();
41272
41493
 
41273
41494
  // src/box.ts
41274
41495
  var BOX_KEYS = {
@@ -41676,6 +41897,9 @@ function runDistStatus(root) {
41676
41897
  }
41677
41898
  }
41678
41899
 
41900
+ // src/edge-commands.ts
41901
+ init_cli_shared();
41902
+
41679
41903
  // src/edge-tunnel.ts
41680
41904
  var HOSTNAME_RE = /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/;
41681
41905
  var UPSTREAM_RE = /^https?:\/\/[^/\s]+(?::\d+)?(?:\/.*)?$/;
@@ -41742,9 +41966,15 @@ function registerEdgeCommands(program3) {
41742
41966
  });
41743
41967
  }
41744
41968
 
41969
+ // src/command-register-developer.ts
41970
+ init_github_client();
41971
+ init_hub_auth();
41972
+
41745
41973
  // src/schedules-lift-command.ts
41746
41974
  var import_promises8 = require("node:fs/promises");
41747
41975
  var import_node_path42 = require("node:path");
41976
+ init_clean_exit();
41977
+ init_cli_shared();
41748
41978
  var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
41749
41979
  var SCHEDULE_REPO_RE = /^[A-Za-z0-9_.-]+$/;
41750
41980
  var SchedulesLiftUsageError = class extends Error {
@@ -42222,6 +42452,10 @@ function registerDeveloperCommands(program3) {
42222
42452
 
42223
42453
  // src/command-register-train-operations.ts
42224
42454
  var import_promises10 = require("node:fs/promises");
42455
+ init_clean_exit();
42456
+ init_cli_shared();
42457
+ init_client_version();
42458
+ init_house_map();
42225
42459
 
42226
42460
  // src/hotfix-apply.ts
42227
42461
  var import_promises9 = require("node:fs/promises");
@@ -43633,6 +43867,9 @@ function checkHotfixCarries(options) {
43633
43867
  // src/train-commands.ts
43634
43868
  var import_node_fs47 = require("node:fs");
43635
43869
  var import_node_path45 = require("node:path");
43870
+ init_cli_shared();
43871
+ init_clean_exit();
43872
+ init_client_version();
43636
43873
  var INVOKED_ARGV = process.argv.slice(2);
43637
43874
  var RELEASE_BUMP_INTENTS = ["major", "minor", "patch"];
43638
43875
  function resolveReleaseBumpIntent(raw) {