@reliverse/rempts 1.7.1 β†’ 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.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > @reliverse/rempts is a modern, type-safe toolkit for building delightful cli experiences. it's fast, flexible, and made for developer happiness. file-based commands keep things simpleβ€”no clutter, just clean and easy workflows. this is how cli should feel.
4
4
 
5
- [πŸ’¬ Discord](https://discord.gg/3GawfWfAPe) β€” [πŸ“¦ NPM](https://npmjs.com/package/@reliverse/rempts) β€” [🧠 Docs](https://docs.reliverse.org/reliverse/rempts) β€” [🌐 JSR](https://jsr.io/@reliverse/rempts) β€” [✨ GitHub](https://github.com/reliverse/rempts)
5
+ [sponsor](https://github.com/sponsors/blefnk) β€” [discord](https://discord.gg/Pb8uKbwpsJ) β€” [repo](https://github.com/reliverse/rempts) β€” [npm](https://npmjs.com/@reliverse/rempts)
6
6
 
7
7
  ## Features
8
8
 
@@ -27,6 +27,14 @@
27
27
  bun add @reliverse/rempts
28
28
  ```
29
29
 
30
+ **Coming soon**:
31
+
32
+ ```bash
33
+ bun i -g @reliverse/dler
34
+ dler rempts init --cmd my-cmd-1
35
+ dler rempts init --cmds
36
+ ```
37
+
30
38
  ## Usage Examples
31
39
 
32
40
  - [Prompts](#prompts)
@@ -130,15 +138,13 @@ await main();
130
138
  ### Terminology
131
139
 
132
140
  - **Launcher/Router**: The main entry point for your CLI. Visit [CLI Launcher (Router)](#cli-launcher-router) section to learn more.
133
- - **Command/Subcommand**: A command is a function that defines the behavior of a CLI.
141
+ - **Command**: A command is a function that defines the inner script launched by the main script where runMain() is used or by some other command.
134
142
  - **Argument**: An argument is a value that is passed to a command.
135
143
  - **Flag**: A flag is a boolean argument that is used to enable or disable a feature.
136
144
  - **Option**: An option is a named argument that is used to configure a command.
137
145
 
138
146
  #### Launcher Usage Example
139
147
 
140
- ‼️ Go to [Usage Examples](#usage-examples) section for a more detailed example.
141
-
142
148
  ```ts
143
149
  import { relinka } from "@reliverse/relinka";
144
150
 
@@ -156,7 +162,7 @@ const main = defineCommand({
156
162
  onCmdEnd() {
157
163
  relinka("success", "Cleanup");
158
164
  },
159
- subCommands: {
165
+ commands: {
160
166
  build: () => import("./app/build/cmd.js").then((r) => r.default),
161
167
  deploy: () => import("./app/deploy/cmd.js").then((r) => r.default),
162
168
  debug: () => import("./app/debug/cmd.js").then((r) => r.default),
@@ -177,9 +183,9 @@ await runMain(myCommand, {
177
183
  });
178
184
  ```
179
185
 
180
- This flexibility allows you to easily build a rich, multi-command CLI with minimal boilerplate. The launcher even supports nested subcommands, making it simple to construct complex CLI applications.
186
+ This flexibility allows you to easily build a rich, multi-command CLI with minimal boilerplate. The launcher even supports nested commands, making it simple to construct complex CLI applications.
181
187
 
182
- #### File-Based Subcommands
188
+ #### File-Based Commands
183
189
 
184
190
  Drop a `./src/cli/app/add/index.ts` and it's live.
185
191
 
@@ -199,7 +205,7 @@ export default defineCommand({
199
205
  }),
200
206
  },
201
207
  async run({ args }) {
202
- relinka("info", "Adding:", args.name);
208
+ relinka("log", "Adding:", args.name);
203
209
  },
204
210
  });
205
211
  ```
@@ -227,7 +233,7 @@ defineCommand({
227
233
  animals: { type: "array", options: ["cat","dog"] },
228
234
  },
229
235
  async run({ args, raw }) { // or `async run(ctx)`
230
- relinka("info", args.name, args.verbose, args.animals); // or `relinka("info", ctx.args.name, ...);`
236
+ relinka("log", args.name, args.verbose, args.animals); // or `relinka("log", ctx.args.name, ...);`
231
237
  },
232
238
  });
233
239
  ```
@@ -362,7 +368,7 @@ const main = defineCommand({
362
368
  ],
363
369
  });
364
370
 
365
- relinka("info", "You have selected:", { name, framework });
371
+ relinka("log", "You have selected:", { name, framework });
366
372
  },
367
373
  });
368
374
 
@@ -388,7 +394,7 @@ import {
388
394
  * This command demonstrates the full range of launcher features along with all supported argument types:
389
395
  *
390
396
  * - Global Usage Handling: Automatically processes `--help` and `--version`.
391
- * - File-Based Subcommands: Scans "app" for subcommands (e.g., `init`).
397
+ * - File-Based Commands: Scans "app" for commands (e.g., `init`).
392
398
  * - Comprehensive Argument Parsing: Supports positional, boolean, string, number, and array arguments.
393
399
  * - Interactive Prompts: Uses built-in prompt functions for an engaging CLI experience.
394
400
  */
@@ -397,7 +403,7 @@ const mainCommand = defineCommand({
397
403
  name: "rempts",
398
404
  version: "1.6.0",
399
405
  description:
400
- "An example CLI that supports file-based subcommands and all argument types.",
406
+ "An example CLI that supports file-based commands and all argument types.",
401
407
  },
402
408
  args: {
403
409
  // Positional arguments
@@ -440,10 +446,10 @@ const mainCommand = defineCommand({
440
446
  },
441
447
  async run({ args, raw }) {
442
448
  // Display invocation details and parsed arguments.
443
- relinka("info", "Main command was invoked!");
444
- relinka("info", "Parsed main-command args:", args);
445
- relinka("info", "Raw argv:", raw);
446
- relinka("info", "\nHelp: `rempts --help`, `rempts cmdName --help`");
449
+ relinka("log", "Main command was invoked!");
450
+ relinka("log", "Parsed main-command args:", args);
451
+ relinka("log", "Raw argv:", raw);
452
+ relinka("log", "\nHelp: `rempts --help`, `rempts cmdName --help`");
447
453
 
448
454
  // Begin interactive session with a prompt.
449
455
  await startPrompt({
@@ -467,7 +473,7 @@ const mainCommand = defineCommand({
467
473
  });
468
474
 
469
475
  // Log all gathered input details.
470
- relinka("info", "You have selected:", {
476
+ relinka("log", "You have selected:", {
471
477
  projectName,
472
478
  framework,
473
479
  inputFile: args.inputFile,
@@ -483,7 +489,7 @@ const mainCommand = defineCommand({
483
489
  /**
484
490
  * The `runMain()` function sets up the launcher with several advanced features:
485
491
  *
486
- * - File-Based Subcommands: Enables scanning for subcommands within the "app" directory.
492
+ * - File-Based Commands: Enables scanning for commands within the "app" directory.
487
493
  * - Alias Mapping: Shorthand flags (e.g., `-v`) are mapped to their full names (e.g., `--verbose`).
488
494
  * - Strict Mode & Unknown Flag Warnings: Unknown flags are either warned about or handled via a callback.
489
495
  * - Negated Boolean Support: Allows flags to be negated (e.g., `--no-verbose`).
@@ -491,8 +497,8 @@ const mainCommand = defineCommand({
491
497
  */
492
498
  await runMain(mainCommand, {
493
499
  fileBasedCmds: {
494
- enable: true, // Enables file-based subcommand detection.
495
- cmdsRootPath: "app", // Directory to scan for subcommands.
500
+ enable: true, // Enables file-based command detection.
501
+ cmdsRootPath: "app", // Directory to scan for commands.
496
502
  },
497
503
  alias: {
498
504
  v: "verbose", // Maps shorthand flag -v to --verbose.
@@ -509,10 +515,10 @@ await runMain(mainCommand, {
509
515
 
510
516
  ### CLI Launcher (Router)
511
517
 
512
- Finally, a full-featured CLI launcher without the ceremony. `@reliverse/rempts`'s so called "launcher" is a uniquely powerful and ergonomic CLI toolkitβ€”one that helps you build delightful developer experiences with less code and more confidence. The launcher supports both programmatically defined subcommands and file-based routing, so you can structure your CLI however you like. It automatically detects and loads subcommands from your filesystem and provides robust usage and error handling out-of-the-box. The launcher is more than just a command runnerβ€”it's a robust, developer-friendly engine with several advanced features and thoughtful design choices:
518
+ Finally, a full-featured CLI launcher without the ceremony. `@reliverse/rempts`'s so called "launcher" is a uniquely powerful and ergonomic CLI toolkitβ€”one that helps you build delightful developer experiences with less code and more confidence. The launcher supports both programmatically defined commands and file-based routing, so you can structure your CLI however you like. It automatically detects and loads commands from your filesystem and provides robust usage and error handling out-of-the-box. The launcher is more than just a command runnerβ€”it's a robust, developer-friendly engine with several advanced features and thoughtful design choices:
513
519
 
514
- - **File-Based & Defined Subcommands:**
515
- Use `subCommands` in your command definition or let the launcher automatically load commands from a specified directory.
520
+ - **File-Based & Defined Commands:**
521
+ Use `commands` in your command definition or let the launcher automatically load commands from a specified directory.
516
522
 
517
523
  - **Automatic Command Detection:**
518
524
  The launcher scans your specified `cmdsRootPath` for command files matching common patterns such as:
@@ -539,20 +545,20 @@ Finally, a full-featured CLI launcher without the ceremony. `@reliverse/rempts`'
539
545
  - **Lifecycle Hooks:**
540
546
  You can define optional lifecycle hooks in your main command:
541
547
  - `onLauncherStart` and `onLauncherEnd` (global, called once per CLI process)
542
- - `onCmdStart` and `onCmdEnd` (per-subcommand, called before/after each subcommand, but NOT for the main `run()` handler)
548
+ - `onCmdStart` and `onCmdEnd` (per-command, called before/after each command, but NOT for the main `run()` handler)
543
549
 
544
550
  **Global Hooks:**
545
- - `onLauncherStart`: Called once, before any command/subcommand/run() is executed.
546
- - `onLauncherEnd`: Called once, after all command/subcommand/run() logic is finished (even if an error occurs).
551
+ - `onLauncherStart`: Called once, before any command/run() is executed.
552
+ - `onLauncherEnd`: Called once, after all command/run() logic is finished (even if an error occurs).
547
553
 
548
- **Per-Subcommand Hooks:**
549
- - `onCmdStart`: Called before each subcommand (not for main `run()`).
550
- - `onCmdEnd`: Called after each subcommand (not for main `run()`).
554
+ **Per-Command Hooks:**
555
+ - `onCmdStart`: Called before each command (not for main `run()`).
556
+ - `onCmdEnd`: Called after each command (not for main `run()`).
551
557
 
552
558
  This means:
553
- - If your CLI has multiple subcommands, `onCmdStart` and `onCmdEnd` will be called for each subcommand invocation, not just once for the whole CLI process.
554
- - If your main command has a `run()` handler (and no subcommand is invoked), these hooks are **not** called; use the `run()` handler itself or the global hooks for such logic.
555
- - This allows you to perform setup/teardown logic specific to each subcommand execution.
559
+ - If your CLI has multiple commands, `onCmdStart` and `onCmdEnd` will be called for each command invocation, not just once for the whole CLI process.
560
+ - If your main command has a `run()` handler (and no command is invoked), these hooks are **not** called; use the `run()` handler itself or the global hooks for such logic.
561
+ - This allows you to perform setup/teardown logic specific to each command execution.
556
562
  - If you want logic to run only once for the entire CLI process, use `onLauncherStart` and `onLauncherEnd`.
557
563
 
558
564
  **Example:**
@@ -561,27 +567,29 @@ Finally, a full-featured CLI launcher without the ceremony. `@reliverse/rempts`'
561
567
  const main = defineCommand({
562
568
  onLauncherStart() { relinka('info', 'Global setup (once per process)'); },
563
569
  onLauncherEnd() { relinka('info', 'Global cleanup (once per process)'); },
564
- onCmdStart() { relinka('info', 'Setup for each subcommand'); },
565
- onCmdEnd() { relinka('info', 'Cleanup for each subcommand'); },
566
- subCommands: { ... },
567
- run() { relinka('info', 'Main run handler (no subcommand)'); },
570
+ onCmdStart() { relinka('info', 'Setup for each command'); },
571
+ onCmdEnd() { relinka('info', 'Cleanup for each command'); },
572
+ commands: { ... },
573
+ run() { relinka('info', 'Main run handler (no command)'); },
568
574
  });
569
575
  // onLauncherStart/onLauncherEnd are called once per process
570
- // onCmdStart/onCmdEnd are called for every subcommand (not for main run())
576
+ // onCmdStart/onCmdEnd are called for every command (not for main run())
571
577
  // If you want per-run() logic, use the run() handler or global hooks
572
578
  ```
573
579
 
574
- > **Note:** The legacy `setup` and `cleanup` names are still supported as aliases for per-command hooks, but will be removed in a future major version. Prefer `onCmdStart` and `onCmdEnd` going forward.
580
+ - **Deprecation Notice**
581
+ - The legacy `setup` and `cleanup` names are still supported as aliases for per-command hooks, but will be removed in a future major version. Prefer `onCmdStart` and `onCmdEnd` going forward.
582
+ - The `subCommands` property is deprecated as well. Please use `commands` instead. `subCommands` will be removed in a future major version.
575
583
 
576
584
  - **Dynamic Usage Examples:**
577
- - The launcher inspects your available subcommands and their argument definitions, then prints a plausible example CLI invocation for a random subcommand directly in the help output. This helps users understand real-world usage at a glance.
585
+ - The launcher inspects your available commands and their argument definitions, then prints a plausible example CLI invocation for a random command directly in the help output. This helps users understand real-world usage at a glance.
578
586
 
579
- - **File-Based & Programmatic Subcommands:**
580
- - Both file-based and object subcommands are fully supported. The launcher can introspect their argument definitions and metadata for help, usage, and validation.
581
- - File-based subcommands are auto-discovered from your filesystem, while programmatic subcommands can be defined inline in your main command.
587
+ - **File-Based & Programmatic Commands:**
588
+ - Both file-based and object commands are fully supported. The launcher can introspect their argument definitions and metadata for help, usage, and validation.
589
+ - File-based commands are auto-discovered from your filesystem, while programmatic commands can be defined inline in your main command.
582
590
 
583
591
  - **Context-Aware Help Output:**
584
- - The help/usage output adapts to your CLI's structure, showing available subcommands, their aliases, argument details, and even dynamic usage examples. It also displays global options and context-specific error messages.
592
+ - The help/usage output adapts to your CLI's structure, showing available commands, their aliases, argument details, and even dynamic usage examples. It also displays global options and context-specific error messages.
585
593
 
586
594
  - **Error Handling:**
587
595
  - The launcher provides clear, actionable error messages for missing required arguments, invalid types, unknown commands, and import errors. It always shows relevant usage information to help users recover quickly.
@@ -598,6 +606,92 @@ Finally, a full-featured CLI launcher without the ceremony. `@reliverse/rempts`'
598
606
  - **Prompt-First, Modern UX:**
599
607
  - The launcher integrates tightly with the prompt engine, so you can build interactive, delightful CLIs with minimal effort.
600
608
 
609
+ ### Launcher Programmatic Execution
610
+
611
+ For larger CLIs or when you want to programmatically run commands (e.g.: [prompt demo](./example/prompts/mod.ts), tests, etc), you can organize your commands in a `cmds.ts` file and use the `runCmd` utility.
612
+
613
+ **Pro Tips & Best Practices**:
614
+
615
+ - Install `dler` globally and run `dler rempts init --cmds` to generate a `src/app/cmds.ts` (custom path is supported) file in your project.
616
+ - You can use any name for the `cmds.ts` file and store it anywhere, but `src/app/cmds.ts` is a good convention you can follow.
617
+ - Use the async function pattern for lazy loading if you have many commands or care about startup performance.
618
+ - Use eager loading (const) for small CLIs or demos where simplicity is preferred.
619
+
620
+ **Lazy Loading (Recommended for Most CLIs)**:
621
+
622
+ ```ts
623
+ // example/launcher/app/cmds.ts
624
+
625
+ export async function getCmdHooks() {
626
+ return (await import("./hooks/cmd.js")).default;
627
+ }
628
+
629
+ export async function getCmdFoo() {
630
+ return (await import("./foo/cmd.js")).default;
631
+ }
632
+
633
+ // ...more commands
634
+ ```
635
+
636
+ Usage:
637
+
638
+ ```ts
639
+ // example/prompts/mod.ts
640
+
641
+ import { getCmdHooks } from "@/launcher/app/cmds.js";
642
+ import { runCmd } from "@reliverse/rempts";
643
+
644
+ await runCmd(await getCmdHooks(), ["--flag"]);
645
+ // OR:
646
+ // const hooksCmd = await getCmdHooks();
647
+ // await runCmd(hooksCmd, ["--flag"]);
648
+ ```
649
+
650
+ **Alternative: Eager Loading (All Commands Loaded at Startup)**:
651
+
652
+ ```ts
653
+ // example/launcher/app/cmds.ts
654
+ export const hooksCmd = (await import("./hooks/cmd.js")).default;
655
+ export const fooCmd = (await import("./foo/cmd.js")).default;
656
+ // ...more commands
657
+ ```
658
+
659
+ Usage:
660
+
661
+ ```ts
662
+ import { hooksCmd } from "./cmds.js";
663
+ import { runCmd } from "@reliverse/rempts";
664
+
665
+ await runCmd(hooksCmd, ["--flag"]);
666
+ ```
667
+
668
+ **Programmatic Command Execution with `runCmd`**:
669
+
670
+ The `runCmd` utility lets you run a command's `run()` handler with parsed arguments, outside of the full launcher context. This is useful for demos, tests, or custom flows:
671
+
672
+ ```ts
673
+ import { runCmd } from "@reliverse/rempts";
674
+ import { hooksCmd } from "./cmds.js";
675
+
676
+ await runCmd(hooksCmd, ["--flag"]); // argv as array of strings
677
+ ```
678
+
679
+ Or with lazy loading:
680
+
681
+ ```ts
682
+ const hooksCmd = await getCmdHooks();
683
+ await runCmd(hooksCmd, ["--flag"]);
684
+ ```
685
+
686
+ **Note:** `runCmd` only runs the command's `run()` handler and does not handle subcommands, file-based commands, or global hooks. For full CLI behavior, use `runMain`.
687
+
688
+ **Performance Note:**
689
+
690
+ - Eager loading (`const`) loads all commands at startup, which may impact performance for large CLIs.
691
+ - Lazy loading (`async function`) loads each command only when needed, improving startup time and memory usage.
692
+
693
+ Choose the pattern that best fits your CLI's size and usage!
694
+
601
695
  ## Contributing
602
696
 
603
697
  Bug report? Prompt idea? Want to build the best DX possible?
@@ -27,7 +27,7 @@ export async function runMain<T extends ArgsDef = ArgsDef>(
27
27
  if (!meta?.version) {
28
28
  throw new CLIError("No version specified", "E_NO_VERSION");
29
29
  }
30
- relinka("info", meta.version);
30
+ relinka("log", meta.version);
31
31
  } else {
32
32
  await runCommand(cmd, { rawArgs });
33
33
  }
@@ -12,7 +12,7 @@ export async function showUsage<T extends ArgsDef = ArgsDef>(
12
12
  ) {
13
13
  try {
14
14
  // biome-ignore lint/style/useTemplate: <explanation>
15
- relinka("info", (await renderUsage(cmd, parent)) + "\n");
15
+ relinka("log", (await renderUsage(cmd, parent)) + "\n");
16
16
  } catch (error) {
17
17
  relinka("error", String(error));
18
18
  }
@@ -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,13 +57,20 @@ type DefineCommandOptions<A extends ArgDefinitions = EmptyArgs> = {
57
57
  meta?: CommandMeta;
58
58
  args?: A;
59
59
  run?: CommandRun<InferArgTypes<A>>;
60
- subCommands?: SubCommandsMap;
61
60
  /**
62
- * Called before the command or subcommand runs
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
63
70
  */
64
71
  onCmdStart?: () => void | Promise<void>;
65
72
  /**
66
- * Called after the command or subcommand finishes
73
+ * Called after the command finishes
67
74
  */
68
75
  onCmdEnd?: () => void | Promise<void>;
69
76
  /**
@@ -75,11 +82,11 @@ type DefineCommandOptions<A extends ArgDefinitions = EmptyArgs> = {
75
82
  */
76
83
  cleanup?: () => void | Promise<void>;
77
84
  /**
78
- * Called once per CLI process, before any command/subcommand/run() is executed
85
+ * Called once per CLI process, before any command/run() is executed
79
86
  */
80
87
  onLauncherStart?: () => void | Promise<void>;
81
88
  /**
82
- * Called once per CLI process, after all command/subcommand/run() logic is finished
89
+ * Called once per CLI process, after all command/run() logic is finished
83
90
  */
84
91
  onLauncherEnd?: () => void | Promise<void>;
85
92
  };
@@ -87,13 +94,20 @@ export type Command<A extends ArgDefinitions = EmptyArgs> = {
87
94
  meta?: CommandMeta;
88
95
  args: A;
89
96
  run?: CommandRun<InferArgTypes<A>>;
90
- subCommands?: SubCommandsMap;
91
97
  /**
92
- * Called before the command or subcommand runs
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
93
107
  */
94
108
  onCmdStart?: () => void | Promise<void>;
95
109
  /**
96
- * Called after the command or subcommand finishes
110
+ * Called after the command finishes
97
111
  */
98
112
  onCmdEnd?: () => void | Promise<void>;
99
113
  /**
@@ -105,11 +119,11 @@ export type Command<A extends ArgDefinitions = EmptyArgs> = {
105
119
  */
106
120
  cleanup?: () => void | Promise<void>;
107
121
  /**
108
- * Called once per CLI process, before any command/subcommand/run() is executed
122
+ * Called once per CLI process, before any command/run() is executed
109
123
  */
110
124
  onLauncherStart?: () => void | Promise<void>;
111
125
  /**
112
- * Called once per CLI process, after all command/subcommand/run() logic is finished
126
+ * Called once per CLI process, after all command/run() logic is finished
113
127
  */
114
128
  onLauncherEnd?: () => void | Promise<void>;
115
129
  };
@@ -152,11 +166,11 @@ export declare function showUsage<A extends ArgDefinitions>(command: Command<A>,
152
166
  /**
153
167
  * Primary entry point to run a command. This function supports:
154
168
  *
155
- * - File-based Subcommands: scanning for subcommands within a given commands root.
156
- * - 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.
157
171
  * - Standard flags like --help, --version, and --debug.
158
172
  *
159
- * This function passes along remaining arguments to subcommand runners to ensure
173
+ * This function passes along remaining arguments to command runners to ensure
160
174
  * consistent parsing.
161
175
  */
162
176
  export declare function runMain<A extends ArgDefinitions = EmptyArgs>(command: Command<A>, parserOptions?: ReliArgParserOptions & {
@@ -174,4 +188,14 @@ export declare function runMain<A extends ArgDefinitions = EmptyArgs>(command: C
174
188
  * precise default value validation (e.g., `options: ["a", "b"] as const`).
175
189
  */
176
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>;
177
201
  export {};
@@ -50,7 +50,7 @@ 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) {
@@ -61,11 +61,15 @@ export function defineCommand(options) {
61
61
  const onCmdEnd = options.onCmdEnd || options.cleanup;
62
62
  const onLauncherStart = options.onLauncherStart;
63
63
  const onLauncherEnd = options.onLauncherEnd;
64
- return {
64
+ let commands = options.commands;
65
+ if (!commands) {
66
+ commands = options.subCommands;
67
+ }
68
+ const cmdObj = {
65
69
  meta: options.meta,
66
70
  args: options.args || {},
67
71
  run: options.run,
68
- subCommands: options.subCommands,
72
+ commands,
69
73
  onCmdStart,
70
74
  onCmdEnd,
71
75
  onLauncherStart,
@@ -74,6 +78,14 @@ export function defineCommand(options) {
74
78
  setup: onCmdStart,
75
79
  cleanup: onCmdEnd
76
80
  };
81
+ Object.defineProperty(cmdObj, "subCommands", {
82
+ get() {
83
+ return this.commands;
84
+ },
85
+ enumerable: false,
86
+ configurable: true
87
+ });
88
+ return cmdObj;
77
89
  }
78
90
  let _cachedDefaultCliName;
79
91
  let _cachedDefaultCliVersion;
@@ -97,7 +109,7 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
97
109
  const { name: fallbackName, version: fallbackVersion } = await getDefaultCliNameAndVersion();
98
110
  const cliName = command.meta?.name || fallbackName;
99
111
  const cliVersion = command.meta?.version || fallbackVersion;
100
- relinka("info", `${cliName}${cliVersion ? ` v${cliVersion}` : ""}`);
112
+ relinka("log", `${cliName}${cliVersion ? ` v${cliVersion}` : ""}`);
101
113
  if (parserOptions.metaSettings?.showDescriptionOnMain) {
102
114
  let description = command.meta?.description;
103
115
  if (!description) {
@@ -155,7 +167,7 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
155
167
  );
156
168
  }
157
169
  if (subCommandNames.length > 0) {
158
- relinka("info", "Available commands (run with `help` to see more):");
170
+ relinka("log", "Available commands (run with `help` to see more):");
159
171
  subCommandDefs.forEach(({ name, def }) => {
160
172
  const desc = def?.meta?.description ?? "";
161
173
  relinka("log", `\u2022 ${name}${desc ? ` | ${desc}` : ""}`);
@@ -173,8 +185,9 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
173
185
  } else {
174
186
  const subCommandNames = [];
175
187
  const subCommandDefs = [];
176
- if (command.subCommands) {
177
- 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)) {
178
191
  try {
179
192
  const cmd = await loadSubCommand(spec);
180
193
  if (!cmd?.meta?.hidden) {
@@ -183,7 +196,7 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
183
196
  subCommandDefs.push({ name, def: cmd });
184
197
  }
185
198
  } catch (err) {
186
- debugLog(`Error loading subcommand ${name}:`, err);
199
+ debugLog(`Error loading command ${name}:`, err);
187
200
  }
188
201
  }
189
202
  }
@@ -204,14 +217,14 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
204
217
  );
205
218
  }
206
219
  if (subCommandNames.length > 0) {
207
- relinka("info", "Available commands (run with `help` to see more):");
220
+ relinka("log", "Available commands (run with `help` to see more):");
208
221
  subCommandDefs.forEach(({ name, def }) => {
209
222
  const desc = def?.meta?.description ?? "";
210
223
  relinka("log", `\u2022 ${name}${desc ? ` | ${desc}` : ""}`);
211
224
  });
212
225
  }
213
226
  }
214
- relinka("info", "Available options:");
227
+ relinka("log", "Available options:");
215
228
  relinka("log", "\u2022 -h, --help | Show help");
216
229
  relinka("log", "\u2022 -v, --version | Show version");
217
230
  relinka("log", "\u2022 --debug | Enable debug mode");
@@ -240,7 +253,7 @@ export async function runMain(command, parserOptions = {}) {
240
253
  if (typeof command.onLauncherStart === "function")
241
254
  await command.onLauncherStart();
242
255
  try {
243
- if (!parserOptions.fileBasedCmds && !command.subCommands) {
256
+ if (!parserOptions.fileBasedCmds && !command.commands) {
244
257
  let callerDir = process.cwd();
245
258
  let callerFile;
246
259
  try {
@@ -289,7 +302,7 @@ This can cause recursion or unexpected behavior.`
289
302
  }
290
303
  const rawArgv = process.argv.slice(2);
291
304
  const autoExit = parserOptions.autoExit !== false;
292
- if (!(parserOptions.fileBasedCmds?.enable || command.subCommands && Object.keys(command.subCommands).length > 0 || command.run)) {
305
+ if (!(parserOptions.fileBasedCmds?.enable || command.commands && Object.keys(command.commands).length > 0 || command.run)) {
293
306
  relinka(
294
307
  "error",
295
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."
@@ -310,7 +323,7 @@ This can cause recursion or unexpected behavior.`
310
323
  if (checkVersion(rawArgv)) {
311
324
  if (command.meta?.name) {
312
325
  relinka(
313
- "info",
326
+ "log",
314
327
  `${command.meta?.name} ${command.meta?.version ? `v${command.meta?.version}` : ""}`
315
328
  );
316
329
  }
@@ -338,10 +351,10 @@ This can cause recursion or unexpected behavior.`
338
351
  throw err;
339
352
  }
340
353
  }
341
- if (!fileBasedEnabled && command.subCommands && rawArgv.length > 0 && !isFlag(rawArgv[0])) {
354
+ if (!fileBasedEnabled && command.commands && rawArgv.length > 0 && !isFlag(rawArgv[0])) {
342
355
  const [maybeSub, ...subCmdArgv] = rawArgv;
343
356
  let subSpec;
344
- for (const [key, spec] of Object.entries(command.subCommands)) {
357
+ for (const [key, spec] of Object.entries(command.commands)) {
345
358
  if (key === maybeSub) {
346
359
  subSpec = spec;
347
360
  break;
@@ -353,7 +366,7 @@ This can cause recursion or unexpected behavior.`
353
366
  break;
354
367
  }
355
368
  } catch (err) {
356
- debugLog(`Error checking alias for subcommand ${key}:`, err);
369
+ debugLog(`Error checking alias for command ${key}:`, err);
357
370
  }
358
371
  }
359
372
  if (subSpec) {
@@ -556,7 +569,7 @@ async function runCommandWithArgs(command, argv, parserOptions) {
556
569
  if (command.run) {
557
570
  await command.run(ctx);
558
571
  } else {
559
- 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;
560
573
  const noSubcommandArgInCurrentCall = !argv.some((arg) => !isFlag(arg));
561
574
  if (isDispatcher && noSubcommandArgInCurrentCall) {
562
575
  relinka("warn", "Please specify a command");
@@ -624,3 +637,74 @@ function renderPositional(args) {
624
637
  export function defineArgs(args) {
625
638
  return args;
626
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
  }
package/bin/mod.d.ts CHANGED
@@ -4,7 +4,7 @@ export { startEditor } from "./components/editor/editor-mod.js";
4
4
  export { mainSymbols, fallbackSymbols, } from "./components/figures/figures-mod.js";
5
5
  export { confirmPrompt } from "./components/input/confirm-prompt.js";
6
6
  export { inputPrompt } from "./components/input/input-prompt.js";
7
- export type { ArgDefinition, ArgDefinitions, SubCommandsMap, Command, InferArgTypes, FileBasedCmdsOptions, } from "./components/launcher/launcher-mod.js";
7
+ export type { ArgDefinition, ArgDefinitions, CommandsMap, Command, InferArgTypes, FileBasedCmdsOptions, } from "./components/launcher/launcher-mod.js";
8
8
  export { defineCommand, defineArgs, showUsage, runMain, } from "./components/launcher/launcher-mod.js";
9
9
  export { toBaseColor, toSolidColor } from "./components/msg-fmt/colors.js";
10
10
  export { relinkaByRemptsDeprecated, relinkaAsyncByRemptsDeprecated, throwError, } from "./components/msg-fmt/logger.js";
package/bin/types.d.ts CHANGED
@@ -241,7 +241,7 @@ export type RenderParams = {
241
241
  /**
242
242
  * Known symbol names that will have IntelliSense support
243
243
  */
244
- export type SymbolName = "pointer" | "start" | "middle" | "end" | "line" | "corner_top_right" | "step_active" | "step_error" | "info" | "success";
244
+ export type SymbolName = "pointer" | "start" | "middle" | "end" | "line" | "corner_top_right" | "step_active" | "step_error" | "log" | "success" | "info" | "warn" | "error";
245
245
  export type Symbols = Record<SymbolName, string>;
246
246
  export type FmtMsgOptions = {
247
247
  type: MsgType;
@@ -21,14 +21,14 @@ export async function completePrompt(prompt, isCtrlC, _endTitle = "", _endTitleC
21
21
  return value ?? false;
22
22
  }
23
23
  export function renderEndLine() {
24
- const lineLength = getExactTerminalWidth() - 2;
25
- relinka("info", re.dim(symbols.middle));
26
- relinka("info", re.dim(`${symbols.end}${symbols.line.repeat(lineLength)}\u22B1`));
27
- relinka("info", "");
24
+ const lineLength = getExactTerminalWidth() - 6;
25
+ relinka("null", re.dim(symbols.middle));
26
+ relinka("null", re.dim(`${symbols.end}${symbols.line.repeat(lineLength)}\u22B1`));
27
+ relinka("null", "");
28
28
  }
29
29
  export function renderEndLineInput() {
30
- const lineLength = getExactTerminalWidth() - 2;
31
- relinka("info", "");
32
- relinka("info", re.dim(`${symbols.end}${symbols.line.repeat(lineLength)}\u22B1`));
33
- relinka("info", "");
30
+ const lineLength = getExactTerminalWidth() - 6;
31
+ relinka("null", "");
32
+ relinka("null", re.dim(`${symbols.end}${symbols.line.repeat(lineLength)}\u22B1`));
33
+ relinka("null", "");
34
34
  }
package/package.json CHANGED
@@ -2,8 +2,8 @@
2
2
  "dependencies": {
3
3
  "@figliolia/chalk-animation": "^1.0.4",
4
4
  "@reliverse/reliarg": "^1.0.3",
5
- "@reliverse/relico": "^1.1.1",
6
- "@reliverse/relinka": "^1.4.3",
5
+ "@reliverse/relico": "^1.1.2",
6
+ "@reliverse/relinka": "^1.4.5",
7
7
  "@reliverse/runtime": "^1.0.3",
8
8
  "ansi-escapes": "^7.0.0",
9
9
  "c12": "^3.0.3",
@@ -28,7 +28,7 @@
28
28
  "license": "MIT",
29
29
  "name": "@reliverse/rempts",
30
30
  "type": "module",
31
- "version": "1.7.1",
31
+ "version": "1.7.2",
32
32
  "author": "reliverse",
33
33
  "bugs": {
34
34
  "email": "blefnk@gmail.com",
@@ -58,7 +58,7 @@
58
58
  "eslint-plugin-no-relative-import-paths": "^1.6.1",
59
59
  "eslint-plugin-perfectionist": "^4.13.0",
60
60
  "jiti": "^2.4.2",
61
- "knip": "^5.55.1",
61
+ "knip": "^5.56.0",
62
62
  "typescript": "^5.8.3",
63
63
  "typescript-eslint": "^8.32.1",
64
64
  "vitest": "^3.1.3"