@boneskull/bargs 3.1.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +132 -24
  2. package/dist/bargs.cjs +193 -22
  3. package/dist/bargs.cjs.map +1 -1
  4. package/dist/bargs.d.cts +51 -5
  5. package/dist/bargs.d.cts.map +1 -1
  6. package/dist/bargs.d.ts +51 -5
  7. package/dist/bargs.d.ts.map +1 -1
  8. package/dist/bargs.js +190 -21
  9. package/dist/bargs.js.map +1 -1
  10. package/dist/help.cjs +31 -2
  11. package/dist/help.cjs.map +1 -1
  12. package/dist/help.d.cts +4 -0
  13. package/dist/help.d.cts.map +1 -1
  14. package/dist/help.d.ts +4 -0
  15. package/dist/help.d.ts.map +1 -1
  16. package/dist/help.js +31 -2
  17. package/dist/help.js.map +1 -1
  18. package/dist/index.cjs +3 -3
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +3 -4
  21. package/dist/index.d.cts.map +1 -1
  22. package/dist/index.d.ts +3 -4
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +2 -3
  25. package/dist/index.js.map +1 -1
  26. package/dist/opt.cjs +38 -0
  27. package/dist/opt.cjs.map +1 -1
  28. package/dist/opt.d.cts +26 -0
  29. package/dist/opt.d.cts.map +1 -1
  30. package/dist/opt.d.ts +26 -0
  31. package/dist/opt.d.ts.map +1 -1
  32. package/dist/opt.js +38 -0
  33. package/dist/opt.js.map +1 -1
  34. package/dist/osc.cjs +12 -0
  35. package/dist/osc.cjs.map +1 -1
  36. package/dist/osc.d.cts +6 -0
  37. package/dist/osc.d.cts.map +1 -1
  38. package/dist/osc.d.ts +6 -0
  39. package/dist/osc.d.ts.map +1 -1
  40. package/dist/osc.js +12 -0
  41. package/dist/osc.js.map +1 -1
  42. package/dist/parser.cjs +48 -1
  43. package/dist/parser.cjs.map +1 -1
  44. package/dist/parser.d.cts +2 -0
  45. package/dist/parser.d.cts.map +1 -1
  46. package/dist/parser.d.ts +2 -0
  47. package/dist/parser.d.ts.map +1 -1
  48. package/dist/parser.js +48 -1
  49. package/dist/parser.js.map +1 -1
  50. package/dist/theme.cjs +8 -0
  51. package/dist/theme.cjs.map +1 -1
  52. package/dist/theme.d.cts +6 -0
  53. package/dist/theme.d.cts.map +1 -1
  54. package/dist/theme.d.ts +6 -0
  55. package/dist/theme.d.ts.map +1 -1
  56. package/dist/theme.js +8 -0
  57. package/dist/theme.js.map +1 -1
  58. package/dist/types.d.cts +34 -1
  59. package/dist/types.d.cts.map +1 -1
  60. package/dist/types.d.ts +34 -1
  61. package/dist/types.d.ts.map +1 -1
  62. package/dist/version.cjs +16 -0
  63. package/dist/version.cjs.map +1 -1
  64. package/dist/version.d.cts +4 -0
  65. package/dist/version.d.cts.map +1 -1
  66. package/dist/version.d.ts +4 -0
  67. package/dist/version.d.ts.map +1 -1
  68. package/dist/version.js +16 -0
  69. package/dist/version.js.map +1 -1
  70. package/package.json +9 -2
package/README.md CHANGED
@@ -27,8 +27,7 @@ A CLI with an optional command and a couple options:
27
27
  ```typescript
28
28
  import { bargs, opt, pos } from '@boneskull/bargs';
29
29
 
30
- await bargs
31
- .create('greet', { version: '1.0.0' })
30
+ await bargs('greet', { version: '1.0.0' })
32
31
  .globals(
33
32
  opt.options({
34
33
  name: opt.string({ default: 'world' }),
@@ -112,11 +111,10 @@ const parser = pos.positionals(pos.variadic('string', { name: 'text' }))(
112
111
  }),
113
112
  );
114
113
 
115
- const { values, positionals } = await bargs
116
- .create('echo', {
117
- description: 'Echo text to stdout',
118
- version: '1.0.0',
119
- })
114
+ const { values, positionals } = await bargs('echo', {
115
+ description: 'Echo text to stdout',
116
+ version: '1.0.0',
117
+ })
120
118
  .globals(parser)
121
119
  .parseAsync();
122
120
 
@@ -132,11 +130,10 @@ For a CLI with multiple subcommands:
132
130
  ```typescript
133
131
  import { bargs, merge, opt, pos } from '@boneskull/bargs';
134
132
 
135
- await bargs
136
- .create('tasks', {
137
- description: 'A task manager',
138
- version: '1.0.0',
139
- })
133
+ await bargs('tasks', {
134
+ description: 'A task manager',
135
+ version: '1.0.0',
136
+ })
140
137
  .globals(
141
138
  opt.options({
142
139
  verbose: opt.boolean({ aliases: ['v'], default: false }),
@@ -181,9 +178,51 @@ $ tasks list --all
181
178
  All tasks
182
179
  ```
183
180
 
181
+ ### Nested Commands (Subcommands)
182
+
183
+ Commands can be nested to arbitrary depth by passing a `CliBuilder` as the second argument to `.command()`:
184
+
185
+ ```typescript
186
+ import { bargs, opt, pos } from '@boneskull/bargs';
187
+
188
+ // Define subcommands as a separate builder
189
+ const remoteCommands = bargs('remote')
190
+ .command(
191
+ 'add',
192
+ pos.positionals(
193
+ pos.string({ name: 'name', required: true }),
194
+ pos.string({ name: 'url', required: true }),
195
+ ),
196
+ ({ positionals, values }) => {
197
+ const [name, url] = positionals;
198
+ // Parent globals (verbose) are available here!
199
+ if (values.verbose) console.log(`Adding ${name}: ${url}`);
200
+ },
201
+ 'Add a remote',
202
+ )
203
+ .command('remove' /* ... */)
204
+ .defaultCommand('add');
205
+
206
+ // Nest under parent CLI
207
+ await bargs('git')
208
+ .globals(opt.options({ verbose: opt.boolean({ aliases: ['v'] }) }))
209
+ .command('remote', remoteCommands, 'Manage remotes') // ← CliBuilder
210
+ .command('commit', commitParser, commitHandler) // ← Regular command
211
+ .parseAsync();
212
+ ```
213
+
214
+ ```shell
215
+ $ git --verbose remote add origin https://github.com/...
216
+ Adding origin: https://github.com/...
217
+
218
+ $ git remote remove origin
219
+ ```
220
+
221
+ Parent globals automatically flow to nested command handlers. You can nest as deep as you like—just nest `CliBuilder`s inside `CliBuilder`s. See `examples/nested-commands.ts` for a full example.
222
+
184
223
  ## API
185
224
 
186
- ### bargs.create(name, options?)
225
+ ### bargs(name, options?)
187
226
 
188
227
  Create a CLI builder.
189
228
 
@@ -199,7 +238,7 @@ Create a CLI builder.
199
238
  Set global options and transforms that apply to all commands.
200
239
 
201
240
  ```typescript
202
- bargs.create('my-cli').globals(opt.options({ verbose: opt.boolean() }));
241
+ bargs('my-cli').globals(opt.options({ verbose: opt.boolean() }));
203
242
  // ...
204
243
  ```
205
244
 
@@ -219,6 +258,21 @@ Register a command. The handler receives merged global + command types.
219
258
  )
220
259
  ```
221
260
 
261
+ ### .command(name, cliBuilder, description?)
262
+
263
+ Register a nested command group. The `cliBuilder` is another `CliBuilder` whose commands become subcommands. Parent globals are passed down to nested handlers.
264
+
265
+ ```typescript
266
+ const subCommands = bargs('sub').command('foo', ...).command('bar', ...);
267
+
268
+ bargs('main')
269
+ .command('nested', subCommands, 'Nested commands') // nested group
270
+ .parseAsync();
271
+
272
+ // $ main nested foo
273
+ // $ main nested bar
274
+ ```
275
+
222
276
  ### .defaultCommand(name)
223
277
 
224
278
  > Or `.defaultCommand(parser, handler)`
@@ -245,11 +299,11 @@ Parse arguments and execute handlers.
245
299
 
246
300
  ```typescript
247
301
  // Async (supports async transforms/handlers)
248
- const result = await bargs.create('my-cli').globals(...).parseAsync();
302
+ const result = await bargs('my-cli').globals(...).parseAsync();
249
303
  console.log(result.values, result.positionals, result.command);
250
304
 
251
305
  // Sync (no async transforms/handlers)
252
- const result = bargs.create('my-cli').globals(...).parse();
306
+ const result = bargs('my-cli').globals(...).parse();
253
307
  ```
254
308
 
255
309
  ## Option Helpers
@@ -277,6 +331,32 @@ opt.count(); // -vvv → 3
277
331
  | `hidden` | `boolean` | Hide from `--help` output |
278
332
  | `required` | `boolean` | Mark as required (makes the option non-nullable) |
279
333
 
334
+ ### Boolean Negation (`--no-<flag>`)
335
+
336
+ All boolean options automatically support a negated form `--no-<flag>` to explicitly set the option to `false`:
337
+
338
+ ```shell
339
+ $ my-cli --verbose # verbose: true
340
+ $ my-cli --no-verbose # verbose: false
341
+ $ my-cli # verbose: undefined (or default)
342
+ ```
343
+
344
+ If both `--flag` and `--no-flag` are specified, bargs throws an error:
345
+
346
+ ```shell
347
+ $ my-cli --verbose --no-verbose
348
+ Error: Conflicting options: --verbose and --no-verbose cannot both be specified
349
+ ```
350
+
351
+ In help output, booleans with `default: true` display as `--no-<flag>` (since that's how users would turn them off):
352
+
353
+ ```typescript
354
+ opt.options({
355
+ colors: opt.boolean({ default: true, description: 'Use colors' }),
356
+ });
357
+ // Help output shows: --no-colors Use colors [boolean] default: true
358
+ ```
359
+
280
360
  ### `opt.options(schema)`
281
361
 
282
362
  Create a parser from an options schema:
@@ -384,8 +464,7 @@ const globals = map(
384
464
  }),
385
465
  );
386
466
 
387
- await bargs
388
- .create('my-cli')
467
+ await bargs('my-cli')
389
468
  .globals(globals)
390
469
  .command(
391
470
  'info',
@@ -421,18 +500,47 @@ const globals = map(
421
500
  );
422
501
  ```
423
502
 
503
+ ### CamelCase Option Keys
504
+
505
+ If you prefer camelCase property names instead of kebab-case, use the `camelCaseValues` transform:
506
+
507
+ ```typescript
508
+ import { bargs, map, opt, camelCaseValues } from '@boneskull/bargs';
509
+
510
+ const { values } = await bargs('my-cli')
511
+ .globals(
512
+ map(
513
+ opt.options({
514
+ 'output-dir': opt.string({ default: '/tmp' }),
515
+ 'dry-run': opt.boolean(),
516
+ }),
517
+ camelCaseValues,
518
+ ),
519
+ )
520
+ .parseAsync(['--output-dir', './dist', '--dry-run']);
521
+
522
+ console.log(values.outputDir); // './dist'
523
+ console.log(values.dryRun); // true
524
+ ```
525
+
526
+ The `camelCaseValues` transform:
527
+
528
+ - Converts all kebab-case keys to camelCase (`output-dir` → `outputDir`)
529
+ - Preserves keys that are already camelCase or have no hyphens
530
+ - Is fully type-safe—TypeScript knows the transformed key names
531
+
424
532
  ## Epilog
425
533
 
426
534
  By default, **bargs** displays your package's homepage and repository URLs (from `package.json`) at the end of help output. URLs become clickable hyperlinks in supported terminals.
427
535
 
428
536
  ```typescript
429
537
  // Custom epilog
430
- bargs.create('my-cli', {
538
+ bargs('my-cli', {
431
539
  epilog: 'For more info, visit https://example.com',
432
540
  });
433
541
 
434
542
  // Disable epilog entirely
435
- bargs.create('my-cli', { epilog: false });
543
+ bargs('my-cli', { epilog: false });
436
544
  ```
437
545
 
438
546
  ## Theming
@@ -441,10 +549,10 @@ Customize help output colors with built-in themes or your own:
441
549
 
442
550
  ```typescript
443
551
  // Use a built-in theme: 'default', 'mono', 'ocean', 'warm'
444
- bargs.create('my-cli', { theme: 'ocean' });
552
+ bargs('my-cli', { theme: 'ocean' });
445
553
 
446
554
  // Disable colors entirely
447
- bargs.create('my-cli', { theme: 'mono' });
555
+ bargs('my-cli', { theme: 'mono' });
448
556
  ```
449
557
 
450
558
  The `ansi` export provides common ANSI escape codes for styled terminal output:
@@ -452,7 +560,7 @@ The `ansi` export provides common ANSI escape codes for styled terminal output:
452
560
  ```typescript
453
561
  import { ansi } from '@boneskull/bargs';
454
562
 
455
- bargs.create('my-cli', {
563
+ bargs('my-cli', {
456
564
  theme: {
457
565
  command: ansi.bold,
458
566
  flag: ansi.brightCyan,
@@ -498,7 +606,7 @@ import {
498
606
  } from '@boneskull/bargs';
499
607
 
500
608
  try {
501
- await bargs.create('my-cli').parseAsync();
609
+ await bargs('my-cli').parseAsync();
502
610
  } catch (error) {
503
611
  if (error instanceof ValidationError) {
504
612
  // Config validation failed (e.g., invalid schema)
package/dist/bargs.cjs CHANGED
@@ -2,13 +2,13 @@
2
2
  /**
3
3
  * Core bargs API using parser combinator pattern.
4
4
  *
5
- * Provides `bargs.create()` for building CLIs with a fluent API, plus
6
- * combinator functions like `pipe()`, `map()`, and `handle()`.
5
+ * Provides `bargs()` for building CLIs with a fluent API, plus combinator
6
+ * functions like `pipe()`, `map()`, and `handle()`.
7
7
  *
8
8
  * @packageDocumentation
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.bargs = void 0;
11
+ exports.bargs = exports.camelCaseValues = void 0;
12
12
  exports.handle = handle;
13
13
  exports.map = map;
14
14
  exports.merge = merge;
@@ -53,27 +53,47 @@ function handle(parserOrFn, maybeFn) {
53
53
  };
54
54
  }
55
55
  function map(parserOrFn, maybeFn) {
56
+ // Helper to compose transforms (chains existing + new)
57
+ /**
58
+ * @function
59
+ */
60
+ const composeTransform = (parser, fn) => {
61
+ const existing = parser.__transform;
62
+ if (!existing) {
63
+ return fn;
64
+ }
65
+ // Chain: existing transform first, then new transform
66
+ return (r) => {
67
+ const r1 = existing(r);
68
+ if (r1 instanceof Promise) {
69
+ return r1.then(fn);
70
+ }
71
+ return fn(r1);
72
+ };
73
+ };
56
74
  // Direct form: map(parser, fn) returns Parser
57
75
  // Check for Parser first since CallableParser is also a function
58
76
  if (isParser(parserOrFn)) {
59
77
  const parser = parserOrFn;
60
78
  const fn = maybeFn;
79
+ const composedTransform = composeTransform(parser, fn);
61
80
  return {
62
81
  ...parser,
63
82
  __brand: 'Parser',
64
83
  __positionals: [],
65
- __transform: fn,
84
+ __transform: composedTransform,
66
85
  __values: {},
67
86
  };
68
87
  }
69
88
  // Curried form: map(fn) returns (parser) => Parser
70
89
  const fn = parserOrFn;
71
90
  return (parser) => {
91
+ const composedTransform = composeTransform(parser, fn);
72
92
  return {
73
93
  ...parser,
74
94
  __brand: 'Parser',
75
95
  __positionals: [],
76
- __transform: fn,
96
+ __transform: composedTransform,
77
97
  __values: {},
78
98
  };
79
99
  };
@@ -130,6 +150,43 @@ function merge(...parsers) {
130
150
  return result;
131
151
  }
132
152
  // ═══════════════════════════════════════════════════════════════════════════════
153
+ // CAMEL CASE HELPER
154
+ // ═══════════════════════════════════════════════════════════════════════════════
155
+ /**
156
+ * Convert kebab-case string to camelCase.
157
+ *
158
+ * @function
159
+ */
160
+ const kebabToCamel = (s) => s.replace(/-([a-zA-Z])/g, (_, c) => c.toUpperCase());
161
+ /**
162
+ * Transform for use with `map()` that converts kebab-case option keys to
163
+ * camelCase.
164
+ *
165
+ * @example
166
+ *
167
+ * ```typescript
168
+ * import { bargs, opt, map, camelCaseValues } from '@boneskull/bargs';
169
+ *
170
+ * const { values } = await bargs('my-cli')
171
+ * .globals(
172
+ * map(opt.options({ 'output-dir': opt.string() }), camelCaseValues),
173
+ * )
174
+ * .parseAsync();
175
+ *
176
+ * console.log(values.outputDir); // camelCased!
177
+ * ```
178
+ *
179
+ * @function
180
+ */
181
+ const camelCaseValues = (result) => ({
182
+ ...result,
183
+ values: Object.fromEntries(Object.entries(result.values).map(([k, v]) => [
184
+ kebabToCamel(k),
185
+ v,
186
+ ])),
187
+ });
188
+ exports.camelCaseValues = camelCaseValues;
189
+ // ═══════════════════════════════════════════════════════════════════════════════
133
190
  // CLI BUILDER
134
191
  // ═══════════════════════════════════════════════════════════════════════════════
135
192
  /**
@@ -138,8 +195,7 @@ function merge(...parsers) {
138
195
  * @example
139
196
  *
140
197
  * ```typescript
141
- * const cli = await bargs
142
- * .create('my-app', { version: '1.0.0' })
198
+ * const cli = await bargs('my-app', { version: '1.0.0' })
143
199
  * .globals(
144
200
  * map(opt.options({ verbose: opt.boolean() }), ({ values }) => ({
145
201
  * values: { ...values, ts: Date.now() },
@@ -153,8 +209,10 @@ function merge(...parsers) {
153
209
  * )
154
210
  * .parseAsync();
155
211
  * ```
212
+ *
213
+ * @function
156
214
  */
157
- const create = (name, options = {}) => {
215
+ const bargs = (name, options = {}) => {
158
216
  const theme = options.theme ? (0, theme_js_1.getTheme)(options.theme) : theme_js_1.defaultTheme;
159
217
  return createCliBuilder({
160
218
  commands: new Map(),
@@ -163,8 +221,11 @@ const create = (name, options = {}) => {
163
221
  theme,
164
222
  });
165
223
  };
224
+ exports.bargs = bargs;
166
225
  /**
167
226
  * Check if something is a Command (has __brand: 'Command').
227
+ *
228
+ * @function
168
229
  */
169
230
  const isCommand = (x) => {
170
231
  if (x === null || x === undefined) {
@@ -175,21 +236,39 @@ const isCommand = (x) => {
175
236
  };
176
237
  /**
177
238
  * Create a CLI builder.
239
+ *
240
+ * @function
178
241
  */
179
242
  const createCliBuilder = (state) => {
180
- return {
181
- // Overloaded command(): accepts either (name, Command, desc?) or (name, Parser, handler, desc?)
182
- command(name, cmdOrParser, handlerOrDesc, maybeDesc) {
243
+ const builder = {
244
+ // Internal method for nested command support - not part of public API
245
+ __parseWithParentGlobals(args, parentGlobals, allowAsync) {
246
+ const stateWithGlobals = { ...state, parentGlobals };
247
+ return parseCore(stateWithGlobals, args, allowAsync);
248
+ },
249
+ // Overloaded command(): accepts (name, Command, desc?), (name, Parser, handler, desc?), or (name, CliBuilder, desc?)
250
+ command(name, cmdOrParserOrBuilder, handlerOrDesc, maybeDesc) {
251
+ // Form 3: command(name, CliBuilder, description?) - nested commands
252
+ if (isCliBuilder(cmdOrParserOrBuilder)) {
253
+ const builder = cmdOrParserOrBuilder;
254
+ const description = handlerOrDesc;
255
+ state.commands.set(name, {
256
+ builder: builder,
257
+ description,
258
+ type: 'nested',
259
+ });
260
+ return this;
261
+ }
183
262
  let cmd;
184
263
  let description;
185
- if (isCommand(cmdOrParser)) {
264
+ if (isCommand(cmdOrParserOrBuilder)) {
186
265
  // Form 1: command(name, Command, description?)
187
- cmd = cmdOrParser;
266
+ cmd = cmdOrParserOrBuilder;
188
267
  description = handlerOrDesc;
189
268
  }
190
- else if (isParser(cmdOrParser)) {
269
+ else if (isParser(cmdOrParserOrBuilder)) {
191
270
  // Form 2: command(name, Parser, handler, description?)
192
- const parser = cmdOrParser;
271
+ const parser = cmdOrParserOrBuilder;
193
272
  const handler = handlerOrDesc;
194
273
  description = maybeDesc;
195
274
  // Create Command from Parser + handler
@@ -207,9 +286,9 @@ const createCliBuilder = (state) => {
207
286
  cmd = newCmd;
208
287
  }
209
288
  else {
210
- throw new Error('command() requires a Command or Parser as second argument');
289
+ throw new Error('command() requires a Command, Parser, or CliBuilder as second argument');
211
290
  }
212
- state.commands.set(name, { cmd, description });
291
+ state.commands.set(name, { cmd, description, type: 'command' });
213
292
  return this;
214
293
  },
215
294
  // Overloaded defaultCommand(): accepts name, Command, or (Parser, handler)
@@ -228,6 +307,7 @@ const createCliBuilder = (state) => {
228
307
  state.commands.set(defaultName, {
229
308
  cmd: nameOrCmdOrParser,
230
309
  description: undefined,
310
+ type: 'command',
231
311
  });
232
312
  }
233
313
  else if (isParser(nameOrCmdOrParser)) {
@@ -248,6 +328,7 @@ const createCliBuilder = (state) => {
248
328
  state.commands.set(defaultName, {
249
329
  cmd: newCmd,
250
330
  description: undefined,
331
+ type: 'command',
251
332
  });
252
333
  }
253
334
  else {
@@ -275,9 +356,13 @@ const createCliBuilder = (state) => {
275
356
  return parseCore(state, args, true);
276
357
  },
277
358
  };
359
+ // Return as public CliBuilder (hiding internal method from type)
360
+ return builder;
278
361
  };
279
362
  /**
280
363
  * Core parse logic shared between parse() and parseAsync().
364
+ *
365
+ * @function
281
366
  */
282
367
  const parseCore = (state, args, allowAsync) => {
283
368
  const { commands, options, theme } = state;
@@ -310,12 +395,19 @@ const parseCore = (state, args, allowAsync) => {
310
395
  };
311
396
  /**
312
397
  * Generate command-specific help.
398
+ *
399
+ * @function
313
400
  */
314
401
  const generateCommandHelpNew = (state, commandName, theme) => {
315
402
  const commandEntry = state.commands.get(commandName);
316
403
  if (!commandEntry) {
317
404
  return `Unknown command: ${commandName}`;
318
405
  }
406
+ // Handle nested commands - show their subcommand list
407
+ if (commandEntry.type === 'nested') {
408
+ // TODO: Generate proper help for nested command groups
409
+ return `${commandName} is a command group. Run '${state.name} ${commandName} --help' for subcommands.`;
410
+ }
319
411
  // TODO: Implement proper command help generation
320
412
  const config = {
321
413
  commands: {
@@ -331,6 +423,8 @@ const generateCommandHelpNew = (state, commandName, theme) => {
331
423
  };
332
424
  /**
333
425
  * Generate help for the new CLI structure.
426
+ *
427
+ * @function
334
428
  */
335
429
  const generateHelpNew = (state, theme) => {
336
430
  // TODO: Implement proper help generation for new structure
@@ -350,6 +444,8 @@ const generateHelpNew = (state, theme) => {
350
444
  /**
351
445
  * Check if something is a Parser (has __brand: 'Parser'). Parsers can be either
352
446
  * objects or functions (CallableParser).
447
+ *
448
+ * @function
353
449
  */
354
450
  const isParser = (x) => {
355
451
  if (x === null || x === undefined) {
@@ -359,8 +455,26 @@ const isParser = (x) => {
359
455
  const obj = x;
360
456
  return '__brand' in obj && obj.__brand === 'Parser';
361
457
  };
458
+ /**
459
+ * Check if something is a CliBuilder (has command, globals, parse, parseAsync
460
+ * methods).
461
+ *
462
+ * @function
463
+ */
464
+ const isCliBuilder = (x) => {
465
+ if (x === null || x === undefined || typeof x !== 'object') {
466
+ return false;
467
+ }
468
+ const obj = x;
469
+ return (typeof obj.command === 'function' &&
470
+ typeof obj.globals === 'function' &&
471
+ typeof obj.parse === 'function' &&
472
+ typeof obj.parseAsync === 'function');
473
+ };
362
474
  /**
363
475
  * Run a simple CLI (no commands).
476
+ *
477
+ * @function
364
478
  */
365
479
  const runSimple = (state, args, allowAsync) => {
366
480
  const { globalParser } = state;
@@ -395,8 +509,20 @@ const runSimple = (state, args, allowAsync) => {
395
509
  // ═══════════════════════════════════════════════════════════════════════════════
396
510
  // PUBLIC API
397
511
  // ═══════════════════════════════════════════════════════════════════════════════
512
+ /**
513
+ * Delegate parsing to a nested CliBuilder, passing down parent globals.
514
+ *
515
+ * @function
516
+ */
517
+ const delegateToNestedBuilder = (builder, remainingArgs, parentGlobals, allowAsync) => {
518
+ // Access the internal parse function that accepts parent globals
519
+ const internalBuilder = builder;
520
+ return internalBuilder.__parseWithParentGlobals(remainingArgs, parentGlobals, allowAsync);
521
+ };
398
522
  /**
399
523
  * Run a CLI with commands.
524
+ *
525
+ * @function
400
526
  */
401
527
  const runWithCommands = (state, args, allowAsync) => {
402
528
  const { commands, defaultCommandName, globalParser } = state;
@@ -431,6 +557,38 @@ const runWithCommands = (state, args, allowAsync) => {
431
557
  if (!commandEntry) {
432
558
  throw new errors_js_1.HelpError(`Unknown command: ${commandName}`);
433
559
  }
560
+ // Handle nested commands (subcommands)
561
+ if (commandEntry.type === 'nested') {
562
+ const { builder } = commandEntry;
563
+ // Parse global options first (before the command name)
564
+ const globalOptionsSchema = globalParser?.__optionsSchema ?? {};
565
+ const globalParsed = (0, parser_js_1.parseSimple)({
566
+ args: args.slice(0, commandIndex),
567
+ options: globalOptionsSchema,
568
+ positionals: [],
569
+ });
570
+ // Apply global transforms if any
571
+ let globalResult = {
572
+ positionals: globalParsed.positionals,
573
+ values: globalParsed.values,
574
+ };
575
+ const globalTransform = globalParser?.__transform;
576
+ // Args for nested builder are ONLY those after the command name (not global options)
577
+ const nestedArgs = args.slice(commandIndex + 1);
578
+ if (globalTransform) {
579
+ const transformed = globalTransform(globalResult);
580
+ if (transformed instanceof Promise) {
581
+ if (!allowAsync) {
582
+ throw new errors_js_1.BargsError('Async global transform detected. Use parseAsync() instead of parse().');
583
+ }
584
+ return transformed.then((r) => {
585
+ return delegateToNestedBuilder(builder, nestedArgs, r, allowAsync);
586
+ });
587
+ }
588
+ globalResult = transformed;
589
+ }
590
+ return delegateToNestedBuilder(builder, nestedArgs, globalResult, allowAsync);
591
+ }
434
592
  const { cmd } = commandEntry;
435
593
  // Merge global and command options schemas
436
594
  const globalOptionsSchema = globalParser?.__optionsSchema ?? {};
@@ -446,11 +604,16 @@ const runWithCommands = (state, args, allowAsync) => {
446
604
  options: mergedOptionsSchema,
447
605
  positionals: commandPositionalsSchema,
448
606
  });
607
+ // Merge parent globals (from nested command delegation) with parsed values
608
+ const parentValues = state.parentGlobals?.values ?? {};
449
609
  let result = {
450
610
  positionals: parsed.positionals,
451
- values: parsed.values,
611
+ values: { ...parentValues, ...parsed.values },
452
612
  };
453
613
  // Helper to check for async and throw if not allowed
614
+ /**
615
+ * @function
616
+ */
454
617
  const checkAsync = (value, context) => {
455
618
  if (value instanceof Promise && !allowAsync) {
456
619
  throw new errors_js_1.BargsError(`Async ${context} detected. Use parseAsync() instead of parse().`);
@@ -460,6 +623,9 @@ const runWithCommands = (state, args, allowAsync) => {
460
623
  const globalTransform = globalParser?.__transform;
461
624
  const commandTransform = cmd?.__transform;
462
625
  // Apply transforms and run handler
626
+ /**
627
+ * @function
628
+ */
463
629
  const applyTransformsAndHandle = () => {
464
630
  // Apply global transforms first
465
631
  if (globalTransform) {
@@ -475,6 +641,9 @@ const runWithCommands = (state, args, allowAsync) => {
475
641
  }
476
642
  return continueWithCommandTransform();
477
643
  };
644
+ /**
645
+ * @function
646
+ */
478
647
  const continueWithCommandTransform = () => {
479
648
  // Apply command transforms
480
649
  if (commandTransform) {
@@ -490,6 +659,9 @@ const runWithCommands = (state, args, allowAsync) => {
490
659
  }
491
660
  return runHandler();
492
661
  };
662
+ /**
663
+ * @function
664
+ */
493
665
  const runHandler = () => {
494
666
  const handlerResult = cmd.handler(result);
495
667
  checkAsync(handlerResult, 'handler');
@@ -501,9 +673,8 @@ const runWithCommands = (state, args, allowAsync) => {
501
673
  return applyTransformsAndHandle();
502
674
  };
503
675
  /**
504
- * Main bargs namespace.
676
+ * @ignore
677
+ * @deprecated
505
678
  */
506
- exports.bargs = {
507
- create,
508
- };
679
+ exports.bargs.create = exports.bargs;
509
680
  //# sourceMappingURL=bargs.js.map