@reliverse/rempts 1.7.0 → 1.7.2

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.
@@ -44,10 +44,10 @@ type CommandMeta = {
44
44
  * 2) A lazy import function returning a Promise that resolves to
45
45
  * { default: Command<any> } or directly to a Command instance.
46
46
  */
47
- type SubCommandSpec = string | (() => Promise<{
47
+ type CommandSpec = string | (() => Promise<{
48
48
  default: Command<any>;
49
49
  } | Command<any>>);
50
- export type SubCommandsMap = Record<string, SubCommandSpec>;
50
+ export type CommandsMap = Record<string, CommandSpec>;
51
51
  type CommandContext<ARGS> = {
52
52
  args: ARGS;
53
53
  raw: string[];
@@ -57,17 +57,75 @@ type DefineCommandOptions<A extends ArgDefinitions = EmptyArgs> = {
57
57
  meta?: CommandMeta;
58
58
  args?: A;
59
59
  run?: CommandRun<InferArgTypes<A>>;
60
- subCommands?: SubCommandsMap;
60
+ /**
61
+ * Object subcommands for this command.
62
+ */
63
+ commands?: CommandsMap;
64
+ /**
65
+ * @deprecated Use `commands` instead. Will be removed in a future major version.
66
+ */
67
+ subCommands?: CommandsMap;
68
+ /**
69
+ * Called before the command runs
70
+ */
71
+ onCmdStart?: () => void | Promise<void>;
72
+ /**
73
+ * Called after the command finishes
74
+ */
75
+ onCmdEnd?: () => void | Promise<void>;
76
+ /**
77
+ * @deprecated Use onCmdStart instead
78
+ */
61
79
  setup?: () => void | Promise<void>;
80
+ /**
81
+ * @deprecated Use onCmdEnd instead
82
+ */
62
83
  cleanup?: () => void | Promise<void>;
84
+ /**
85
+ * Called once per CLI process, before any command/run() is executed
86
+ */
87
+ onLauncherStart?: () => void | Promise<void>;
88
+ /**
89
+ * Called once per CLI process, after all command/run() logic is finished
90
+ */
91
+ onLauncherEnd?: () => void | Promise<void>;
63
92
  };
64
93
  export type Command<A extends ArgDefinitions = EmptyArgs> = {
65
94
  meta?: CommandMeta;
66
95
  args: A;
67
96
  run?: CommandRun<InferArgTypes<A>>;
68
- subCommands?: SubCommandsMap;
97
+ /**
98
+ * Object subcommands for this command.
99
+ */
100
+ commands?: CommandsMap;
101
+ /**
102
+ * @deprecated Use `commands` instead. Will be removed in a future major version.
103
+ */
104
+ subCommands?: CommandsMap;
105
+ /**
106
+ * Called before the command runs
107
+ */
108
+ onCmdStart?: () => void | Promise<void>;
109
+ /**
110
+ * Called after the command finishes
111
+ */
112
+ onCmdEnd?: () => void | Promise<void>;
113
+ /**
114
+ * @deprecated Use onCmdStart instead
115
+ */
69
116
  setup?: () => void | Promise<void>;
117
+ /**
118
+ * @deprecated Use onCmdEnd instead
119
+ */
70
120
  cleanup?: () => void | Promise<void>;
121
+ /**
122
+ * Called once per CLI process, before any command/run() is executed
123
+ */
124
+ onLauncherStart?: () => void | Promise<void>;
125
+ /**
126
+ * Called once per CLI process, after all command/run() logic is finished
127
+ */
128
+ onLauncherEnd?: () => void | Promise<void>;
71
129
  };
72
130
  export type InferArgTypes<A extends ArgDefinitions> = {
73
131
  [K in keyof A]: A[K] extends PositionalArgDefinition ? string : A[K] extends BooleanArgDefinition ? boolean : A[K] extends StringArgDefinition ? string : A[K] extends NumberArgDefinition ? number : A[K] extends {
@@ -108,11 +166,11 @@ export declare function showUsage<A extends ArgDefinitions>(command: Command<A>,
108
166
  /**
109
167
  * Primary entry point to run a command. This function supports:
110
168
  *
111
- * - File-based Subcommands: scanning for subcommands within a given commands root.
112
- * - SubCommands defined within the command object.
169
+ * - File-based Commands: scanning for commands within a given commands root.
170
+ * - Commands defined within the command object.
113
171
  * - Standard flags like --help, --version, and --debug.
114
172
  *
115
- * This function passes along remaining arguments to subcommand runners to ensure
173
+ * This function passes along remaining arguments to command runners to ensure
116
174
  * consistent parsing.
117
175
  */
118
176
  export declare function runMain<A extends ArgDefinitions = EmptyArgs>(command: Command<A>, parserOptions?: ReliArgParserOptions & {
@@ -130,4 +188,14 @@ export declare function runMain<A extends ArgDefinitions = EmptyArgs>(command: C
130
188
  * precise default value validation (e.g., `options: ["a", "b"] as const`).
131
189
  */
132
190
  export declare function defineArgs<A extends ArgDefinitions>(args: A & ValidateArrayDefaults<A>): A;
191
+ /**
192
+ * Programmatically run a command's run() handler with parsed arguments.
193
+ * Does not handle subcommands, file-based commands, or global hooks.
194
+ * Suitable for use in demos, tests, or programmatic invocation.
195
+ *
196
+ * @param command The command definition (from defineCommand)
197
+ * @param argv The argv array to parse (default: [])
198
+ * @param parserOptions Optional reliArgParser options
199
+ */
200
+ export declare function runCmd<A extends ArgDefinitions = EmptyArgs>(command: Command<A>, argv?: string[], parserOptions?: ReliArgParserOptions): Promise<void>;
133
201
  export {};
@@ -50,21 +50,42 @@ function buildExampleArgs(args) {
50
50
  const isDebugMode = process.argv.includes("--debug");
51
51
  function debugLog(...args) {
52
52
  if (isDebugMode) {
53
- relinka("info", "[DEBUG]", ...args);
53
+ relinka("log", "[DEBUG]", ...args);
54
54
  }
55
55
  }
56
56
  function isFlag(str) {
57
57
  return str.startsWith("-");
58
58
  }
59
59
  export function defineCommand(options) {
60
- return {
60
+ const onCmdStart = options.onCmdStart || options.setup;
61
+ const onCmdEnd = options.onCmdEnd || options.cleanup;
62
+ const onLauncherStart = options.onLauncherStart;
63
+ const onLauncherEnd = options.onLauncherEnd;
64
+ let commands = options.commands;
65
+ if (!commands) {
66
+ commands = options.subCommands;
67
+ }
68
+ const cmdObj = {
61
69
  meta: options.meta,
62
70
  args: options.args || {},
63
71
  run: options.run,
64
- subCommands: options.subCommands,
65
- setup: options.setup,
66
- cleanup: options.cleanup
72
+ commands,
73
+ onCmdStart,
74
+ onCmdEnd,
75
+ onLauncherStart,
76
+ onLauncherEnd,
77
+ // Backward-compatible aliases
78
+ setup: onCmdStart,
79
+ cleanup: onCmdEnd
67
80
  };
81
+ Object.defineProperty(cmdObj, "subCommands", {
82
+ get() {
83
+ return this.commands;
84
+ },
85
+ enumerable: false,
86
+ configurable: true
87
+ });
88
+ return cmdObj;
68
89
  }
69
90
  let _cachedDefaultCliName;
70
91
  let _cachedDefaultCliVersion;
@@ -88,7 +109,7 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
88
109
  const { name: fallbackName, version: fallbackVersion } = await getDefaultCliNameAndVersion();
89
110
  const cliName = command.meta?.name || fallbackName;
90
111
  const cliVersion = command.meta?.version || fallbackVersion;
91
- relinka("info", `${cliName}${cliVersion ? ` v${cliVersion}` : ""}`);
112
+ relinka("log", `${cliName}${cliVersion ? ` v${cliVersion}` : ""}`);
92
113
  if (parserOptions.metaSettings?.showDescriptionOnMain) {
93
114
  let description = command.meta?.description;
94
115
  if (!description) {
@@ -146,7 +167,7 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
146
167
  );
147
168
  }
148
169
  if (subCommandNames.length > 0) {
149
- relinka("info", "Available commands (run with `help` to see more):");
170
+ relinka("log", "Available commands (run with `help` to see more):");
150
171
  subCommandDefs.forEach(({ name, def }) => {
151
172
  const desc = def?.meta?.description ?? "";
152
173
  relinka("log", `\u2022 ${name}${desc ? ` | ${desc}` : ""}`);
@@ -164,8 +185,9 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
164
185
  } else {
165
186
  const subCommandNames = [];
166
187
  const subCommandDefs = [];
167
- if (command.subCommands) {
168
- for (const [name, spec] of Object.entries(command.subCommands)) {
188
+ const objectCommands = command.commands;
189
+ if (objectCommands) {
190
+ for (const [name, spec] of Object.entries(objectCommands)) {
169
191
  try {
170
192
  const cmd = await loadSubCommand(spec);
171
193
  if (!cmd?.meta?.hidden) {
@@ -174,7 +196,7 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
174
196
  subCommandDefs.push({ name, def: cmd });
175
197
  }
176
198
  } catch (err) {
177
- debugLog(`Error loading subcommand ${name}:`, err);
199
+ debugLog(`Error loading command ${name}:`, err);
178
200
  }
179
201
  }
180
202
  }
@@ -195,14 +217,14 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
195
217
  );
196
218
  }
197
219
  if (subCommandNames.length > 0) {
198
- relinka("info", "Available commands (run with `help` to see more):");
220
+ relinka("log", "Available commands (run with `help` to see more):");
199
221
  subCommandDefs.forEach(({ name, def }) => {
200
222
  const desc = def?.meta?.description ?? "";
201
223
  relinka("log", `\u2022 ${name}${desc ? ` | ${desc}` : ""}`);
202
224
  });
203
225
  }
204
226
  }
205
- relinka("info", "Available options:");
227
+ relinka("log", "Available options:");
206
228
  relinka("log", "\u2022 -h, --help | Show help");
207
229
  relinka("log", "\u2022 -v, --version | Show version");
208
230
  relinka("log", "\u2022 --debug | Enable debug mode");
@@ -228,146 +250,153 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
228
250
  }
229
251
  }
230
252
  export async function runMain(command, parserOptions = {}) {
231
- if (!parserOptions.fileBasedCmds && !command.subCommands) {
232
- let callerDir = process.cwd();
233
- let callerFile;
234
- try {
235
- const err = new Error();
236
- const stack = err.stack?.split("\n");
237
- if (stack) {
238
- for (const line of stack) {
239
- const match = /\((.*):(\d+):(\d+)\)/.exec(line) || /at (.*):(\d+):(\d+)/.exec(line);
240
- if (match?.[1] && !match[1].includes("launcher-mod")) {
241
- callerFile = match[1];
242
- break;
253
+ if (typeof command.onLauncherStart === "function")
254
+ await command.onLauncherStart();
255
+ try {
256
+ if (!parserOptions.fileBasedCmds && !command.commands) {
257
+ let callerDir = process.cwd();
258
+ let callerFile;
259
+ try {
260
+ const err = new Error();
261
+ const stack = err.stack?.split("\n");
262
+ if (stack) {
263
+ for (const line of stack) {
264
+ const match = /\((.*):(\d+):(\d+)\)/.exec(line) || /at (.*):(\d+):(\d+)/.exec(line);
265
+ if (match?.[1] && !match[1].includes("launcher-mod")) {
266
+ callerFile = match[1];
267
+ break;
268
+ }
243
269
  }
244
270
  }
245
- }
246
- if (callerFile) {
247
- callerDir = path.dirname(callerFile);
248
- const rel = path.relative(process.cwd(), callerFile);
249
- if (/app[/][^/]+[/]cmd\.(ts|js)$/.test(rel)) {
250
- relinka(
251
- "error",
252
- `runMain() should not be called from a file-based subcommand: ${rel}
271
+ if (callerFile) {
272
+ callerDir = path.dirname(callerFile);
273
+ const rel = path.relative(process.cwd(), callerFile);
274
+ if (/app[/][^/]+[/]cmd\.(ts|js)$/.test(rel)) {
275
+ relinka(
276
+ "error",
277
+ `runMain() should not be called from a file-based subcommand: ${rel}
253
278
  This can cause recursion or unexpected behavior.
254
279
  Move your runMain() call to your main CLI entry file.`
255
- );
256
- process.exit(1);
257
- }
258
- const mainEntry = process.argv[1] ? path.resolve(process.argv[1]) : void 0;
259
- if (mainEntry && path.resolve(callerFile) !== mainEntry) {
260
- relinka(
261
- "error",
262
- `runMain() should only be called from your main CLI entry file.
280
+ );
281
+ process.exit(1);
282
+ }
283
+ const mainEntry = process.argv[1] ? path.resolve(process.argv[1]) : void 0;
284
+ if (mainEntry && path.resolve(callerFile) !== mainEntry) {
285
+ relinka(
286
+ "error",
287
+ `runMain() should only be called from your main CLI entry file.
263
288
  Detected: ${callerFile}
264
289
  Main entry: ${mainEntry}
265
290
  This can cause recursion or unexpected behavior.`
266
- );
267
- process.exit(1);
291
+ );
292
+ process.exit(1);
293
+ }
268
294
  }
295
+ } catch (_e) {
269
296
  }
270
- } catch (_e) {
297
+ const defaultCmdsRoot = path.resolve(callerDir, "app");
298
+ parserOptions.fileBasedCmds = {
299
+ enable: true,
300
+ cmdsRootPath: defaultCmdsRoot
301
+ };
271
302
  }
272
- const defaultCmdsRoot = path.resolve(callerDir, "app");
273
- parserOptions.fileBasedCmds = {
274
- enable: true,
275
- cmdsRootPath: defaultCmdsRoot
276
- };
277
- }
278
- const rawArgv = process.argv.slice(2);
279
- const autoExit = parserOptions.autoExit !== false;
280
- if (!(parserOptions.fileBasedCmds?.enable || command.subCommands && Object.keys(command.subCommands).length > 0 || command.run)) {
281
- relinka(
282
- "error",
283
- "Invalid CLI configuration: No file-based commands, subCommands, or run() handler are defined. This CLI will not do anything.\n\u2502 To fix: add file-based commands (./app), or provide at least one subCommand or a run() handler."
284
- );
285
- process.exit(1);
286
- }
287
- if (rawArgv[0] === "help") {
288
- await showUsage(command, parserOptions);
289
- if (autoExit) process.exit(0);
290
- return;
291
- }
292
- await relinkaConfig;
293
- if (checkHelp(rawArgv)) {
294
- await showUsage(command, parserOptions);
295
- if (autoExit) process.exit(0);
296
- return;
297
- }
298
- if (checkVersion(rawArgv)) {
299
- if (command.meta?.name) {
303
+ const rawArgv = process.argv.slice(2);
304
+ const autoExit = parserOptions.autoExit !== false;
305
+ if (!(parserOptions.fileBasedCmds?.enable || command.commands && Object.keys(command.commands).length > 0 || command.run)) {
300
306
  relinka(
301
- "info",
302
- `${command.meta?.name} ${command.meta?.version ? `v${command.meta?.version}` : ""}`
307
+ "error",
308
+ "Invalid CLI configuration: No file-based commands, subCommands, or run() handler are defined. This CLI will not do anything.\n\u2502 To fix: add file-based commands (./app), or provide at least one subCommand or a run() handler."
303
309
  );
310
+ process.exit(1);
304
311
  }
305
- if (autoExit) process.exit(0);
306
- return;
307
- }
308
- const fileBasedEnabled = parserOptions.fileBasedCmds?.enable;
309
- if (fileBasedEnabled && rawArgv.length > 0 && !isFlag(rawArgv[0])) {
310
- const [subName, ...subCmdArgv] = rawArgv;
311
- try {
312
- if (typeof command.setup === "function") await command.setup();
313
- await runFileBasedSubCmd(
314
- subName,
315
- subCmdArgv,
316
- parserOptions.fileBasedCmds,
317
- parserOptions,
318
- command.cleanup
319
- );
312
+ if (rawArgv[0] === "help") {
313
+ await showUsage(command, parserOptions);
320
314
  if (autoExit) process.exit(0);
321
315
  return;
322
- } catch (err) {
323
- relinka("error", "Error loading file-based subcommand:", err.message);
324
- if (autoExit) process.exit(1);
325
- throw err;
326
316
  }
327
- }
328
- if (!fileBasedEnabled && command.subCommands && rawArgv.length > 0 && !isFlag(rawArgv[0])) {
329
- const [maybeSub, ...subCmdArgv] = rawArgv;
330
- let subSpec;
331
- for (const [key, spec] of Object.entries(command.subCommands)) {
332
- if (key === maybeSub) {
333
- subSpec = spec;
334
- break;
335
- }
336
- try {
337
- const cmd = await loadSubCommand(spec);
338
- if (cmd.meta.aliases?.includes(maybeSub)) {
339
- subSpec = spec;
340
- break;
341
- }
342
- } catch (err) {
343
- debugLog(`Error checking alias for subcommand ${key}:`, err);
317
+ await relinkaConfig;
318
+ if (checkHelp(rawArgv)) {
319
+ await showUsage(command, parserOptions);
320
+ if (autoExit) process.exit(0);
321
+ return;
322
+ }
323
+ if (checkVersion(rawArgv)) {
324
+ if (command.meta?.name) {
325
+ relinka(
326
+ "log",
327
+ `${command.meta?.name} ${command.meta?.version ? `v${command.meta?.version}` : ""}`
328
+ );
344
329
  }
330
+ if (autoExit) process.exit(0);
331
+ return;
345
332
  }
346
- if (subSpec) {
333
+ const fileBasedEnabled = parserOptions.fileBasedCmds?.enable;
334
+ if (fileBasedEnabled && rawArgv.length > 0 && !isFlag(rawArgv[0])) {
335
+ const [subName, ...subCmdArgv] = rawArgv;
347
336
  try {
348
- if (typeof command.setup === "function") await command.setup();
349
- await runSubCommand(
350
- subSpec,
337
+ if (typeof command.onCmdStart === "function")
338
+ await command.onCmdStart();
339
+ await runFileBasedSubCmd(
340
+ subName,
351
341
  subCmdArgv,
342
+ parserOptions.fileBasedCmds,
352
343
  parserOptions,
353
- command.cleanup
344
+ command.onCmdEnd
354
345
  );
355
346
  if (autoExit) process.exit(0);
356
347
  return;
357
348
  } catch (err) {
358
- relinka("error", "Error running subcommand:", err.message);
349
+ relinka("error", "Error loading file-based subcommand:", err.message);
359
350
  if (autoExit) process.exit(1);
360
351
  throw err;
361
352
  }
362
353
  }
363
- }
364
- if (typeof command.setup === "function") await command.setup();
365
- try {
366
- await runCommandWithArgs(command, rawArgv, parserOptions);
354
+ if (!fileBasedEnabled && command.commands && rawArgv.length > 0 && !isFlag(rawArgv[0])) {
355
+ const [maybeSub, ...subCmdArgv] = rawArgv;
356
+ let subSpec;
357
+ for (const [key, spec] of Object.entries(command.commands)) {
358
+ if (key === maybeSub) {
359
+ subSpec = spec;
360
+ break;
361
+ }
362
+ try {
363
+ const cmd = await loadSubCommand(spec);
364
+ if (cmd.meta.aliases?.includes(maybeSub)) {
365
+ subSpec = spec;
366
+ break;
367
+ }
368
+ } catch (err) {
369
+ debugLog(`Error checking alias for command ${key}:`, err);
370
+ }
371
+ }
372
+ if (subSpec) {
373
+ try {
374
+ if (typeof command.onCmdStart === "function")
375
+ await command.onCmdStart();
376
+ await runSubCommand(
377
+ subSpec,
378
+ subCmdArgv,
379
+ parserOptions,
380
+ command.onCmdEnd
381
+ );
382
+ if (autoExit) process.exit(0);
383
+ return;
384
+ } catch (err) {
385
+ relinka("error", "Error running subcommand:", err.message);
386
+ if (autoExit) process.exit(1);
387
+ throw err;
388
+ }
389
+ }
390
+ }
391
+ try {
392
+ await runCommandWithArgs(command, rawArgv, parserOptions);
393
+ } finally {
394
+ }
395
+ await relinkaShutdown();
367
396
  } finally {
368
- if (typeof command.cleanup === "function") await command.cleanup();
397
+ if (typeof command.onLauncherEnd === "function")
398
+ await command.onLauncherEnd();
369
399
  }
370
- await relinkaShutdown();
371
400
  }
372
401
  function checkHelp(argv) {
373
402
  return argv.includes("--help") || argv.includes("-h");
@@ -392,7 +421,7 @@ async function loadSubCommand(spec) {
392
421
  }
393
422
  throw new Error("Subcommand import did not return a valid command");
394
423
  }
395
- async function runFileBasedSubCmd(subName, argv, fileCmdOpts, parserOptions, parentCleanup) {
424
+ async function runFileBasedSubCmd(subName, argv, fileCmdOpts, parserOptions, parentFinish) {
396
425
  const subPathDir = path.join(fileCmdOpts.cmdsRootPath, subName);
397
426
  let importPath;
398
427
  const possibleFiles = [
@@ -447,15 +476,15 @@ Info for this CLI's developer: No valid command directory found, expected: ${exp
447
476
  try {
448
477
  await runCommandWithArgs(subCommand, argv, parserOptions);
449
478
  } finally {
450
- if (typeof parentCleanup === "function") await parentCleanup();
479
+ if (typeof parentFinish === "function") await parentFinish();
451
480
  }
452
481
  }
453
- async function runSubCommand(spec, argv, parserOptions, parentCleanup) {
482
+ async function runSubCommand(spec, argv, parserOptions, parentFinish) {
454
483
  const subCommand = await loadSubCommand(spec);
455
484
  try {
456
485
  await runCommandWithArgs(subCommand, argv, parserOptions);
457
486
  } finally {
458
- if (typeof parentCleanup === "function") await parentCleanup();
487
+ if (typeof parentFinish === "function") await parentFinish();
459
488
  }
460
489
  }
461
490
  async function runCommandWithArgs(command, argv, parserOptions) {
@@ -540,7 +569,7 @@ async function runCommandWithArgs(command, argv, parserOptions) {
540
569
  if (command.run) {
541
570
  await command.run(ctx);
542
571
  } else {
543
- const isDispatcher = parserOptions.fileBasedCmds?.enable || command.subCommands && Object.keys(command.subCommands).length > 0;
572
+ const isDispatcher = parserOptions.fileBasedCmds?.enable || command.commands && Object.keys(command.commands).length > 0;
544
573
  const noSubcommandArgInCurrentCall = !argv.some((arg) => !isFlag(arg));
545
574
  if (isDispatcher && noSubcommandArgInCurrentCall) {
546
575
  relinka("warn", "Please specify a command");
@@ -608,3 +637,74 @@ function renderPositional(args) {
608
637
  export function defineArgs(args) {
609
638
  return args;
610
639
  }
640
+ export async function runCmd(command, argv = [], parserOptions = {}) {
641
+ const booleanKeys = Object.keys(command.args || {}).filter(
642
+ (k) => command.args[k].type === "boolean"
643
+ );
644
+ const defaultMap = {};
645
+ for (const [argKey, def] of Object.entries(command.args || {})) {
646
+ if (def.default !== void 0) {
647
+ if (def.type === "array" && typeof def.default === "string") {
648
+ defaultMap[argKey] = [def.default];
649
+ } else {
650
+ defaultMap[argKey] = def.default;
651
+ }
652
+ }
653
+ }
654
+ const mergedParserOptions = {
655
+ ...parserOptions,
656
+ boolean: [...parserOptions.boolean || [], ...booleanKeys],
657
+ defaults: { ...defaultMap, ...parserOptions.defaults || {} }
658
+ };
659
+ const parsed = reliArgParser(argv, mergedParserOptions);
660
+ debugLog("Parsed arguments (runCmd):", parsed);
661
+ const finalArgs = {};
662
+ const positionalKeys = Object.keys(command.args || {}).filter(
663
+ (k) => command.args[k].type === "positional"
664
+ );
665
+ const leftoverPositionals = [...parsed._ || []];
666
+ for (let i = 0; i < positionalKeys.length; i++) {
667
+ const key = positionalKeys[i];
668
+ const def = command.args?.[key];
669
+ const val = leftoverPositionals[i];
670
+ if (val == null && def.required) {
671
+ throw new Error(`Missing required positional argument: <${key}>`);
672
+ }
673
+ finalArgs[key] = val != null ? castArgValue(def, val, key) : def.default;
674
+ }
675
+ const otherKeys = Object.keys(command.args || {}).filter(
676
+ (k) => command.args[k].type !== "positional"
677
+ );
678
+ for (const key of otherKeys) {
679
+ const def = command.args?.[key];
680
+ let rawVal = parsed[key];
681
+ if (def.type === "array" && rawVal !== void 0 && !Array.isArray(rawVal)) {
682
+ rawVal = [rawVal];
683
+ }
684
+ const valueOrDefault = rawVal ?? defaultMap[key];
685
+ if (valueOrDefault == null && def.required) {
686
+ throw new Error(`Missing required argument: --${key}`);
687
+ }
688
+ finalArgs[key] = castArgValue(def, rawVal, key);
689
+ if (def.type === "array" && def.options && finalArgs[key]) {
690
+ const values = finalArgs[key];
691
+ const invalidOptions = values.filter(
692
+ (v) => def.options && !def.options.includes(v)
693
+ );
694
+ if (invalidOptions.length > 0) {
695
+ throw new Error(
696
+ `Invalid choice(s) for --${key}: ${invalidOptions.join(", ")}. Allowed options: ${def.options.join(", ")}`
697
+ );
698
+ }
699
+ }
700
+ }
701
+ const ctx = {
702
+ args: finalArgs,
703
+ raw: argv
704
+ };
705
+ if (typeof command.run === "function") {
706
+ await command.run(ctx);
707
+ } else {
708
+ throw new Error("Command has no run() handler.");
709
+ }
710
+ }
@@ -21,7 +21,10 @@ export const symbols = {
21
21
  step_active: u("\u25C6", "\u2666"),
22
22
  step_error: u("\u{1F5F4}", "x"),
23
23
  info: u("\u2139", "i"),
24
- success: u("\u2705", "\u2713")
24
+ log: u("\u2502", "|"),
25
+ success: u("\u2705", "\u2713"),
26
+ warn: u("\u26A0", "!"),
27
+ error: u("\u274C", "x")
25
28
  };
26
29
  function wrapThenStyle(input, wrap, typographyName, colorName, variantName, borderColor) {
27
30
  if (!input) return "";
@@ -309,8 +312,8 @@ export function msgUndoAll() {
309
312
  }
310
313
  export function printLineBar(text, indent = 2) {
311
314
  if (text === "") {
312
- relinka("info", re.dim("\u2502"));
315
+ relinka("log", re.dim("\u2502"));
313
316
  } else {
314
- relinka("info", `${re.dim("\u2502")}${" ".repeat(indent)}${text}`);
317
+ relinka("log", `${re.dim("\u2502")}${" ".repeat(indent)}${text}`);
315
318
  }
316
319
  }
@@ -86,7 +86,7 @@ function renderPromptUI(params) {
86
86
  uiLineCount++;
87
87
  }
88
88
  if (debug) {
89
- relinka("info", "", { optionsCount: options.length });
89
+ relinka("log", "", { optionsCount: options.length });
90
90
  }
91
91
  return uiLineCount;
92
92
  }
@@ -104,7 +104,7 @@ async function renderPromptUI(params) {
104
104
  uiLineCount++;
105
105
  }
106
106
  if (debug) {
107
- relinka("info", "", { optionsCount: options.length });
107
+ relinka("log", "", { optionsCount: options.length });
108
108
  }
109
109
  return uiLineCount;
110
110
  }
@@ -59,7 +59,7 @@ function renderTogglePrompt(params) {
59
59
  msg({ type: "M_NULL", title: displayString });
60
60
  uiLineCount++;
61
61
  if (debug) {
62
- relinka("info", "", {
62
+ relinka("log", "", {
63
63
  selectedIndex,
64
64
  displayOptions: options
65
65
  });
@@ -50,9 +50,9 @@ export async function startPrompt({
50
50
  }
51
51
  if (clearConsole) {
52
52
  relinka("clear", "");
53
- relinka("info", "");
53
+ relinka("log", "");
54
54
  } else {
55
- relinka("info", "");
55
+ relinka("log", "");
56
56
  }
57
57
  msg({
58
58
  type: "M_START",
@@ -9,5 +9,5 @@ export async function createAsciiArt({
9
9
  relinka("clear", "");
10
10
  }
11
11
  const asciiArt = figlet.textSync(message, { font });
12
- relinka("info", asciiArt);
12
+ relinka("log", asciiArt);
13
13
  }