clap-ts 0.1.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.
package/README.md ADDED
@@ -0,0 +1,752 @@
1
+ # clap-ts
2
+
3
+ A type-safe CLI argument parser for TypeScript, inspired by Rust's [clap](https://docs.rs/clap/latest/clap/) crate.
4
+
5
+ Full clap-style parsing, validation, help generation, and subcommand support. Zero runtime dependencies -- built on `node:util parseArgs` with a rich layer on top.
6
+
7
+ ## Features
8
+
9
+ - **Full type inference** -- `defineCommand` infers exact types for parsed args, no casts needed
10
+ - **Clap-compatible argument model** -- boolean, string, number, enum, positional args with short/long flags
11
+ - **Subcommands** -- nested command trees with alias support, prefix inference, and external subcommands
12
+ - **Validation** -- required, exclusive, conflictsWith, requires, requiredUnlessPresent, requiredIfEq, valueParser, numArgs, argument groups
13
+ - **Custom value parsers** -- function-based parsers for custom validation and type conversion
14
+ - **Clap-style help** -- colored, terminal-width-aware help with custom headings, templates, and styles
15
+ - **Clap-style errors** -- "did you mean?" typo suggestions via Levenshtein distance
16
+ - **Environment variable fallback** -- `env` field on args, with CLI > env > default precedence
17
+ - **Lifecycle hooks** -- setup/run/cleanup pattern for resource management
18
+ - **Actions** -- `set` (default), `append` (collect into array), `count` (e.g. `-vvv` = 3)
19
+ - **Value delimiters** -- `--tags=a,b,c` splits into array with `valueDelimiter`
20
+ - **Boolean negation** -- `--no-verbose` automatically supported for boolean flags
21
+ - **Global args** -- `global: true` args inherited by all subcommands
22
+ - **Reusable arg groups** -- `defineArgs()` + spread for sharing args across commands
23
+ - **Trailing var args** -- last positional consumes all remaining args
24
+ - **Negative numbers** -- `--offset -10` with `allowNegativeNumbers`
25
+ - **Hyphen values** -- `--grep -pattern` with `allowHyphenValues`
26
+ - **Zero dependencies** -- only uses `node:util` (parseArgs + styleText)
27
+ - **Bun and Node.js** -- works on both runtimes
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ # npm
33
+ npm install clap-ts
34
+
35
+ # bun
36
+ bun add clap-ts
37
+
38
+ # pnpm
39
+ pnpm add clap-ts
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ```ts
45
+ import { defineCommand, runMain } from 'clap-ts';
46
+
47
+ const main = defineCommand({
48
+ meta: {
49
+ name: 'my-tool',
50
+ version: '1.0.0',
51
+ description: 'A great CLI tool',
52
+ },
53
+ args: {
54
+ name: {
55
+ type: 'string',
56
+ short: 'n',
57
+ description: 'Your name',
58
+ required: true,
59
+ },
60
+ verbose: {
61
+ type: 'boolean',
62
+ short: 'v',
63
+ description: 'Enable verbose output',
64
+ },
65
+ port: {
66
+ type: 'number',
67
+ short: 'p',
68
+ default: 3000,
69
+ description: 'Port to listen on',
70
+ },
71
+ },
72
+ run({ args }) {
73
+ // args.name is string (required, so never undefined)
74
+ // args.verbose is boolean | undefined
75
+ // args.port is number (has default, so never undefined)
76
+ console.log(`Hello ${args.name} on port ${args.port}`);
77
+ },
78
+ });
79
+
80
+ runMain(main);
81
+ ```
82
+
83
+ ```
84
+ $ my-tool --name World -p 8080
85
+ Hello World on port 8080
86
+
87
+ $ my-tool --help
88
+ A great CLI tool (my-tool v1.0.0)
89
+
90
+ Usage: my-tool [OPTIONS]
91
+
92
+ Options:
93
+ -n, --name <STRING> Your name [required]
94
+ -v, --verbose Enable verbose output
95
+ -p, --port <PORT> Port to listen on [default: 3000]
96
+ -h, --help Print help
97
+ -V, --version Print version
98
+ ```
99
+
100
+ ## API Reference
101
+
102
+ ### defineCommand
103
+
104
+ The primary API for creating commands. Returns the same object with full type inference.
105
+
106
+ ```ts
107
+ import { defineCommand } from 'clap-ts';
108
+
109
+ const cmd = defineCommand({
110
+ meta: {
111
+ name: 'serve',
112
+ version: '1.0.0',
113
+ description: 'Start the server', // one-line, shown in parent's subcommand list
114
+ about: 'Start the development server', // shown at top of this command's help
115
+ longAbout: 'Extended description...', // shown with --help (not -h)
116
+ beforeHelp: 'NOTE: Requires auth.', // text before help output
117
+ afterHelp: 'Examples:\n serve -p 8080', // text after help output
118
+ hidden: false, // hide from parent help
119
+ aliases: ['s', 'start'], // subcommand aliases
120
+
121
+ // Subcommand behavior
122
+ subcommandRequired: true, // error if no subcommand
123
+ inferSubcommands: true, // 'ser' matches 'serve'
124
+ inferLongArgs: true, // '--verb' matches '--verbose'
125
+ allowExternalSubcommands: true, // accept undefined subcommands
126
+ subcommandNegatesReqs: true, // subcommand waives parent required args
127
+ argsConflictsWithSubcommands: true, // args and subcommands mutually exclusive
128
+ argRequiredElseHelp: true, // show help if no args provided
129
+
130
+ // Help customization
131
+ helpTemplate: '{name} v{version}\n{usage}\n{options}',
132
+ },
133
+ args: { /* ... */ },
134
+ subCommands: { /* ... */ },
135
+ groups: [ /* ... */ ],
136
+ setup(ctx) { /* pre-run init */ },
137
+ run(ctx) { /* main handler */ },
138
+ cleanup(ctx) { /* always runs, even on error */ },
139
+ });
140
+ ```
141
+
142
+ ### Argument Definition
143
+
144
+ Each argument is defined with an `ArgDef`:
145
+
146
+ ```ts
147
+ const cmd = defineCommand({
148
+ meta: { name: 'tool' },
149
+ args: {
150
+ // String argument
151
+ name: {
152
+ type: 'string',
153
+ short: 'n', // -n
154
+ long: 'name', // --name (defaults to key if omitted)
155
+ description: 'User name',
156
+ required: true,
157
+ valueName: 'NAME', // shown in help: --name <NAME>
158
+ env: 'TOOL_NAME', // fallback to $TOOL_NAME
159
+ },
160
+
161
+ // Number argument
162
+ port: {
163
+ type: 'number',
164
+ short: 'p',
165
+ default: 3000,
166
+ description: 'Port number',
167
+ },
168
+
169
+ // Boolean flag
170
+ verbose: {
171
+ type: 'boolean',
172
+ short: 'v',
173
+ description: 'Verbose output',
174
+ negativeDescription: 'Disable verbose output', // help text for --no-verbose
175
+ },
176
+
177
+ // Enum (restricted values)
178
+ env: {
179
+ type: 'enum',
180
+ short: 'e',
181
+ valueParser: ['dev', 'staging', 'prod'],
182
+ description: 'Environment',
183
+ },
184
+
185
+ // Positional argument
186
+ file: {
187
+ type: 'positional',
188
+ valueName: 'FILE',
189
+ required: true,
190
+ description: 'Input file path',
191
+ },
192
+
193
+ // Append action (collect multiple values)
194
+ header: {
195
+ type: 'string',
196
+ short: 'H',
197
+ action: 'append',
198
+ description: 'HTTP headers',
199
+ },
200
+
201
+ // Count action (-vvv = 3)
202
+ verbosity: {
203
+ type: 'boolean',
204
+ short: 'V',
205
+ action: 'count',
206
+ description: 'Increase verbosity',
207
+ },
208
+
209
+ // Optional value (--flag or --flag=value)
210
+ level: {
211
+ type: 'string',
212
+ numArgs: { min: 0, max: 1 },
213
+ defaultMissingValue: 'info',
214
+ description: 'Log level (default: info when flag present)',
215
+ },
216
+
217
+ // Aliases (hidden from help)
218
+ config: {
219
+ type: 'string',
220
+ alias: ['c', 'conf', 'configuration'], // hidden from help
221
+ description: 'Config file path',
222
+ },
223
+
224
+ // Visible aliases (shown in help)
225
+ output: {
226
+ type: 'string',
227
+ visibleAlias: ['out', 'o'], // shown in help output
228
+ description: 'Output path',
229
+ },
230
+
231
+ // Value delimiter (--tags=a,b,c -> ['a', 'b', 'c'])
232
+ tags: {
233
+ type: 'string',
234
+ valueDelimiter: ',',
235
+ description: 'Comma-separated tags',
236
+ },
237
+
238
+ // Allow negative numbers (--offset -10)
239
+ offset: {
240
+ type: 'number',
241
+ allowNegativeNumbers: true,
242
+ description: 'Offset (can be negative)',
243
+ },
244
+
245
+ // Allow hyphen values (--grep -pattern)
246
+ grep: {
247
+ type: 'string',
248
+ allowHyphenValues: true,
249
+ description: 'Search pattern (can start with -)',
250
+ },
251
+
252
+ // Custom value parser (function)
253
+ port2: {
254
+ type: 'string',
255
+ valueParser: (v) => {
256
+ const n = parseInt(v, 10);
257
+ if (n < 1 || n > 65535) throw new Error('port must be 1-65535');
258
+ return n;
259
+ },
260
+ description: 'Port with range validation',
261
+ },
262
+
263
+ // Help heading (group args under custom sections)
264
+ host: {
265
+ type: 'string',
266
+ helpHeading: 'Network',
267
+ description: 'Server host',
268
+ },
269
+
270
+ // Hide from specific help modes
271
+ debug: {
272
+ type: 'boolean',
273
+ hideShortHelp: true, // hidden from -h, shown in --help
274
+ description: 'Debug mode',
275
+ },
276
+ internal: {
277
+ type: 'boolean',
278
+ hideLongHelp: true, // hidden from --help, shown in -h
279
+ description: 'Internal flag',
280
+ },
281
+
282
+ // Hide possible values from help
283
+ format: {
284
+ type: 'string',
285
+ valueParser: ['json', 'yaml', 'toml'],
286
+ hidePossibleValues: true,
287
+ description: 'Output format',
288
+ },
289
+ },
290
+ });
291
+ ```
292
+
293
+ ### Argument Constraints
294
+
295
+ ```ts
296
+ const cmd = defineCommand({
297
+ meta: { name: 'tool' },
298
+ args: {
299
+ // Mutual exclusion
300
+ json: {
301
+ type: 'boolean',
302
+ conflictsWith: ['yaml', 'table'],
303
+ },
304
+ yaml: { type: 'boolean' },
305
+ table: { type: 'boolean' },
306
+
307
+ // Exclusive: cannot be used with ANY other arg
308
+ init: {
309
+ type: 'boolean',
310
+ exclusive: true,
311
+ },
312
+
313
+ // Companion requirement
314
+ 'tls-cert': {
315
+ type: 'string',
316
+ requires: ['tls'],
317
+ },
318
+ tls: { type: 'boolean' },
319
+
320
+ // Required unless another arg is present
321
+ file: {
322
+ type: 'string',
323
+ required: true,
324
+ requiredUnlessPresent: 'stdin', // or ['stdin', 'generate']
325
+ },
326
+ stdin: { type: 'boolean' },
327
+
328
+ // Conditionally required
329
+ output: {
330
+ type: 'string',
331
+ requiredIfEq: ['format', 'file'], // required when --format=file
332
+ },
333
+ format: { type: 'string' },
334
+
335
+ // Conditional default
336
+ port: {
337
+ type: 'number',
338
+ defaultValueIf: ['env', 'prod', 443], // default 443 when --env=prod
339
+ },
340
+ env: { type: 'string' },
341
+
342
+ // Value count constraint
343
+ files: {
344
+ type: 'string',
345
+ action: 'append',
346
+ numArgs: { min: 1, max: 10 },
347
+ },
348
+ },
349
+
350
+ // Argument groups
351
+ groups: [
352
+ {
353
+ name: 'output-format',
354
+ args: ['json', 'yaml', 'table'],
355
+ required: true, // at least one must be set
356
+ multiple: false, // only one allowed
357
+ },
358
+ ],
359
+ });
360
+ ```
361
+
362
+ ### Trailing Var Args and Last Positional
363
+
364
+ ```ts
365
+ // trailingVarArg: last positional consumes all remaining args
366
+ const exec = defineCommand({
367
+ meta: { name: 'exec' },
368
+ args: {
369
+ cmd: { type: 'positional', valueName: 'CMD' },
370
+ rest: { type: 'positional', valueName: 'ARGS', trailingVarArg: true },
371
+ },
372
+ run({ args }) {
373
+ // exec echo hello world
374
+ // args.cmd = 'echo', args.rest = ['hello', 'world']
375
+ },
376
+ });
377
+
378
+ // last: positional only assigned from args after --
379
+ const run = defineCommand({
380
+ meta: { name: 'run' },
381
+ args: {
382
+ verbose: { type: 'boolean' },
383
+ script: { type: 'positional', valueName: 'SCRIPT', last: true },
384
+ },
385
+ run({ args }) {
386
+ // run --verbose -- myscript.sh
387
+ // args.script = 'myscript.sh'
388
+ },
389
+ });
390
+ ```
391
+
392
+ ### Subcommands
393
+
394
+ ```ts
395
+ const root = defineCommand({
396
+ meta: {
397
+ name: 'app',
398
+ version: '1.0.0',
399
+ inferSubcommands: true, // 'ser' matches 'serve'
400
+ },
401
+ args: {
402
+ verbose: { type: 'boolean', short: 'v', global: true },
403
+ },
404
+ subCommands: {
405
+ serve: defineCommand({
406
+ meta: { name: 'serve', description: 'Start server', aliases: ['s'] },
407
+ args: {
408
+ port: { type: 'number', short: 'p', default: 3000 },
409
+ },
410
+ run({ args }) {
411
+ console.log(`Serving on :${args.port}`);
412
+ },
413
+ }),
414
+ build: defineCommand({
415
+ meta: { name: 'build', description: 'Build project' },
416
+ args: {
417
+ outDir: { type: 'string', default: 'dist' },
418
+ },
419
+ run({ args }) {
420
+ console.log(`Building to ${args.outDir}`);
421
+ },
422
+ }),
423
+ },
424
+ });
425
+
426
+ runMain(root);
427
+ // $ app serve -p 8080
428
+ // $ app s -p 8080 (alias)
429
+ // $ app ser -p 8080 (inferred)
430
+ // $ app build --out-dir ./out
431
+ ```
432
+
433
+ ### External Subcommands
434
+
435
+ Accept undefined subcommands and handle them in the parent:
436
+
437
+ ```ts
438
+ const git = defineCommand({
439
+ meta: { name: 'git', allowExternalSubcommands: true },
440
+ run({ subCommand, rawArgs }) {
441
+ // $ git my-plugin arg1 arg2
442
+ // subCommand = 'my-plugin', rawArgs = ['arg1', 'arg2']
443
+ console.log(`Running plugin: ${subCommand}`);
444
+ },
445
+ });
446
+ ```
447
+
448
+ ### Reusable Argument Groups
449
+
450
+ ```ts
451
+ import { defineArgs, defineCommand } from 'clap-ts';
452
+
453
+ const authArgs = defineArgs({
454
+ user: { type: 'string', short: 'u', env: 'APP_USER' },
455
+ token: { type: 'string', short: 't', env: 'APP_TOKEN', hidden: true },
456
+ });
457
+
458
+ const loggingArgs = defineArgs({
459
+ verbose: { type: 'boolean', short: 'v' },
460
+ quiet: { type: 'boolean', short: 'q', conflictsWith: ['verbose'] },
461
+ });
462
+
463
+ const cmd = defineCommand({
464
+ meta: { name: 'deploy' },
465
+ args: {
466
+ ...authArgs,
467
+ ...loggingArgs,
468
+ target: { type: 'string', required: true },
469
+ },
470
+ run({ args }) {
471
+ // args.user, args.token, args.verbose, args.quiet, args.target
472
+ // all fully typed
473
+ },
474
+ });
475
+ ```
476
+
477
+ ### Lifecycle Hooks
478
+
479
+ Commands support setup/run/cleanup lifecycle hooks. `cleanup` always runs, even if `run` throws.
480
+
481
+ ```ts
482
+ const cmd = defineCommand({
483
+ meta: { name: 'server' },
484
+ args: { port: { type: 'number', default: 3000 } },
485
+
486
+ async setup(ctx) {
487
+ ctx.data.db = await connectToDatabase();
488
+ },
489
+
490
+ async run(ctx) {
491
+ const db = ctx.data.db as Database;
492
+ await startServer(ctx.args.port, db);
493
+ },
494
+
495
+ async cleanup(ctx) {
496
+ const db = ctx.data.db as Database;
497
+ await db?.close();
498
+ },
499
+ });
500
+ ```
501
+
502
+ ### Custom Styles
503
+
504
+ Override the default terminal colors for help and error output:
505
+
506
+ ```ts
507
+ import { runMain } from 'clap-ts';
508
+
509
+ runMain(rootCommand, {
510
+ styles: {
511
+ heading: (s) => `\x1b[35m${s}\x1b[0m`, // magenta headings
512
+ flag: (s) => `\x1b[36m${s}\x1b[0m`, // cyan flags
513
+ command: (s) => `\x1b[1m${s}\x1b[0m`, // bold commands
514
+ },
515
+ });
516
+ ```
517
+
518
+ ### runMain
519
+
520
+ Entry point for CLI applications. Handles argv parsing, subcommand resolution, validation, help/version, and error display.
521
+
522
+ ```ts
523
+ import { runMain } from 'clap-ts';
524
+
525
+ runMain(rootCommand);
526
+
527
+ runMain(rootCommand, {
528
+ argv: ['serve', '--port', '8080'], // override argv (for testing)
529
+ exit: false, // don't call process.exit (for testing)
530
+ showHelpOnEmpty: true, // show help when no args (default: true)
531
+ styles: { /* custom styles */ }, // override terminal colors
532
+ });
533
+ ```
534
+
535
+ ### Low-Level API
536
+
537
+ For advanced use cases, you can use the parser and validator directly:
538
+
539
+ ```ts
540
+ import { parseArgs, validate, renderHelp, CliParseError } from 'clap-ts';
541
+
542
+ const command = defineCommand({
543
+ meta: { name: 'tool' },
544
+ args: { port: { type: 'number', default: 3000 } },
545
+ });
546
+
547
+ // Parse without validation
548
+ const result = parseArgs(['--port', '8080'], command);
549
+ // result.args, result.positionals, result.rest, result.unknown
550
+ // result.explicitlySet -- Set of arg keys that were explicitly provided
551
+
552
+ // Validate separately
553
+ validate(result, command); // throws CliParseError on failure
554
+
555
+ // Render help text
556
+ const helpText = renderHelp(command);
557
+
558
+ // Render short help (-h style, hides hideShortHelp args)
559
+ const shortHelp = renderHelp(command, undefined, true);
560
+ ```
561
+
562
+ ## Value Precedence
563
+
564
+ Arguments are resolved in this order (highest wins):
565
+
566
+ 1. **CLI flags** -- `--port 8080`
567
+ 2. **Environment variables** -- `PORT=8080` (when `env: 'PORT'` is set)
568
+ 3. **Conditional defaults** -- `defaultValueIf: ['env', 'prod', 443]`
569
+ 4. **Static defaults** -- `default: 3000`
570
+
571
+ ## Error Messages
572
+
573
+ Errors match clap's format with typo suggestions:
574
+
575
+ ```
576
+ error: unexpected argument '--verbos' found
577
+
578
+ tip: a similar argument exists: '--verbose'
579
+
580
+ Usage: my-tool [OPTIONS]
581
+
582
+ For more information, try '--help'.
583
+ ```
584
+
585
+ ```
586
+ error: the argument '--json' cannot be used with '--yaml'
587
+
588
+ Usage: my-tool [OPTIONS]
589
+
590
+ For more information, try '--help'.
591
+ ```
592
+
593
+ ```
594
+ error: the following required arguments were not provided:
595
+ --name
596
+ --port
597
+
598
+ Usage: my-tool [OPTIONS]
599
+
600
+ For more information, try '--help'.
601
+ ```
602
+
603
+ ## Help Output
604
+
605
+ Help is automatically generated in clap's format, respecting `NO_COLOR`, `TERM=dumb`, and terminal width:
606
+
607
+ ```
608
+ A great CLI tool (my-tool v1.0.0)
609
+
610
+ Usage: my-tool [OPTIONS] <FILE> [COMMAND]
611
+
612
+ Arguments:
613
+ <FILE> Input file path [required]
614
+
615
+ Commands:
616
+ serve (s) Start the development server
617
+ build Build the project
618
+
619
+ Options:
620
+ -n, --name <NAME> User name [required]
621
+ -e, --env <ENV> Environment [possible values: dev, staging, prod]
622
+ -p, --port <PORT> Port number [default: 3000] [env: PORT]
623
+ --verbose Enable verbose output
624
+ -h, --help Print help
625
+ -V, --version Print version
626
+
627
+ Network:
628
+ --host <STRING> Server host
629
+ --proxy <STRING> Proxy URL
630
+
631
+ Examples:
632
+ my-tool -n World serve -p 8080
633
+ ```
634
+
635
+ ### Help Template
636
+
637
+ Use `helpTemplate` for full control over help layout:
638
+
639
+ ```ts
640
+ const cmd = defineCommand({
641
+ meta: {
642
+ name: 'tool',
643
+ version: '1.0.0',
644
+ helpTemplate: `{before-help}{name} v{version}
645
+
646
+ {usage}
647
+
648
+ {all-args}
649
+ {commands}
650
+ {after-help}`,
651
+ },
652
+ // ...
653
+ });
654
+ ```
655
+
656
+ Placeholders: `{name}`, `{version}`, `{about}`, `{usage}`, `{all-args}`, `{arguments}`, `{options}`, `{commands}`, `{before-help}`, `{after-help}`
657
+
658
+ ## Exit Codes
659
+
660
+ | Code | Meaning |
661
+ |------|---------|
662
+ | 0 | Success |
663
+ | 1 | Runtime error (unhandled exception in run/setup/cleanup) |
664
+ | 2 | Usage error (parse failure, validation failure, unknown flags) |
665
+
666
+ ## Performance
667
+
668
+ clap-ts is built for performance. Core parsing delegates to the native `node:util parseArgs` and adds a thin layer for type coercion, env fallback, and validation. Flag lookup uses precomputed `Map`s for O(1) resolution. All new feature fields short-circuit on `undefined` -- zero overhead when unused.
669
+
670
+ Benchmarks on Apple M3 Pro (Bun 1.3):
671
+
672
+ | Scenario | Time |
673
+ |----------|------|
674
+ | Minimal (no args) | ~1.7us |
675
+ | Simple (5 flags) | ~11us |
676
+ | Complex (22 flags) | ~16us |
677
+ | Subcommand detection | ~12us |
678
+ | Full pipeline (parse + validate) | ~26us |
679
+
680
+ To run benchmarks yourself:
681
+
682
+ ```bash
683
+ bun run bench/parse.bench.ts
684
+ ```
685
+
686
+ ## Comparison with Rust clap
687
+
688
+ | Feature | clap (Rust) | clap-ts |
689
+ |---------|------------|---------|
690
+ | Boolean/string/number args | Yes | Yes |
691
+ | Short and long flags | Yes | Yes |
692
+ | Flag aliases (hidden + visible) | Yes | Yes |
693
+ | Subcommands with aliases | Yes | Yes |
694
+ | Nested subcommands | Yes | Yes |
695
+ | Global args | Yes | Yes |
696
+ | Required args | Yes | Yes |
697
+ | Default values | Yes | Yes |
698
+ | Conditional defaults (defaultValueIf) | Yes | Yes |
699
+ | Env var fallback | Yes | Yes |
700
+ | conflictsWith | Yes | Yes |
701
+ | requires | Yes | Yes |
702
+ | exclusive | Yes | Yes |
703
+ | requiredUnlessPresent | Yes | Yes |
704
+ | requiredIfEq | Yes | Yes |
705
+ | Argument groups | Yes | Yes |
706
+ | Enum values (valueParser) | Yes | Yes |
707
+ | Custom value parsers (function) | Yes | Yes |
708
+ | numArgs (min/max) | Yes | Yes |
709
+ | valueDelimiter | Yes | Yes |
710
+ | append action | Yes | Yes |
711
+ | count action | Yes | Yes |
712
+ | Boolean negation (--no-X) | Yes | Yes |
713
+ | Positional args | Yes | Yes |
714
+ | trailingVarArg | Yes | Yes |
715
+ | last (positional after --) | Yes | Yes |
716
+ | -- rest separator | Yes | Yes |
717
+ | allowHyphenValues | Yes | Yes |
718
+ | allowNegativeNumbers | Yes | Yes |
719
+ | Typo suggestions | Yes | Yes |
720
+ | inferSubcommands | Yes | Yes |
721
+ | inferLongArgs | Yes | Yes |
722
+ | subcommandRequired | Yes | Yes |
723
+ | subcommandNegatesReqs | Yes | Yes |
724
+ | allowExternalSubcommands | Yes | Yes |
725
+ | argsConflictsWithSubcommands | Yes | Yes |
726
+ | argRequiredElseHelp | Yes | Yes |
727
+ | Colored help output | Yes | Yes |
728
+ | Custom styles | Yes | Yes |
729
+ | beforeHelp / afterHelp | Yes | Yes |
730
+ | helpHeading (option grouping) | Yes | Yes |
731
+ | helpTemplate | Yes | Yes |
732
+ | hideShortHelp / hideLongHelp | Yes | Yes |
733
+ | hidePossibleValues | Yes | Yes |
734
+ | Hidden args/commands | Yes | Yes |
735
+ | Type-safe parsed args | derive macro | generics |
736
+ | Shell completions | Yes | Not yet |
737
+ | Man page generation | Yes | Not yet |
738
+
739
+ ## Roadmap
740
+
741
+ - Shell completion generation (bash/zsh/fish)
742
+ - Man page generation
743
+ - Markdown help output
744
+
745
+ ## Requirements
746
+
747
+ - Node.js >= 20.0.0 or Bun >= 1.0
748
+ - TypeScript >= 5.0 (for `const` type parameter inference)
749
+
750
+ ## License
751
+
752
+ MIT