0
# @stricli/core
1
2
@stricli/core is a comprehensive TypeScript library for building type-safe, complex command-line interface (CLI) applications with zero runtime dependencies. It provides a complete framework including command builders, parameter parsing, routing, automatic help text generation, shell completion support, and extensive configuration options. The library leverages TypeScript's type system to provide full compile-time type safety for CLI parameters and commands.
3
4
**Package:** `@stricli/core` | **Type:** npm | **Language:** TypeScript
5
6
## Installation
7
8
```bash
9
npm install @stricli/core
10
```
11
12
## Core Imports
13
14
```typescript
15
import {
16
buildApplication,
17
buildCommand,
18
buildRouteMap,
19
run,
20
type CommandContext,
21
type Application
22
} from "@stricli/core";
23
```
24
25
For CommonJS:
26
27
```javascript
28
const {
29
buildApplication,
30
buildCommand,
31
buildRouteMap,
32
run
33
} = require("@stricli/core");
34
```
35
36
## Quick Start
37
38
```typescript
39
import { buildApplication, buildCommand, run } from "@stricli/core";
40
41
const app = buildApplication(
42
buildCommand({
43
func: async function(flags, name) {
44
this.process.stdout.write(`Hello, ${name || flags.default}!\n`);
45
},
46
parameters: {
47
flags: {
48
default: { kind: "parsed", parse: String, brief: "Default name", default: "World" }
49
},
50
positional: {
51
kind: "tuple",
52
parameters: [{ brief: "Name", parse: String, optional: true }]
53
}
54
},
55
docs: { brief: "Greet someone" }
56
}),
57
{ name: "greet" }
58
);
59
60
await run(app, process.argv.slice(2), { process });
61
```
62
63
##
64
65
Common Patterns Cheat Sheet
66
67
### Basic Command
68
```typescript
69
buildCommand({
70
func: async function(flags) { /* ... */ },
71
parameters: { flags: { /* ... */ } },
72
docs: { brief: "Description" }
73
})
74
```
75
76
### Route Map (Multiple Commands)
77
```typescript
78
buildApplication(
79
buildRouteMap({
80
routes: { cmd1: command1, cmd2: command2 },
81
docs: { brief: "CLI description" }
82
}),
83
{ name: "myapp" }
84
)
85
```
86
87
### Flag Types
88
```typescript
89
// Boolean
90
verbose: { kind: "boolean", brief: "Enable verbose", default: false }
91
92
// Counter (repeatable -vvv)
93
verbosity: { kind: "counter", brief: "Verbosity level" }
94
95
// Enum (predefined values)
96
env: { kind: "enum", values: ["dev", "prod"], brief: "Environment", default: "dev" }
97
98
// Parsed (custom type)
99
port: { kind: "parsed", parse: numberParser, brief: "Port", default: "3000" }
100
101
// Variadic (multiple values)
102
files: { kind: "parsed", parse: String, variadic: true, brief: "Files" }
103
104
// Optional
105
timeout: { kind: "parsed", parse: numberParser, brief: "Timeout", optional: true }
106
```
107
108
### Positional Arguments
109
```typescript
110
// Tuple (fixed args)
111
positional: {
112
kind: "tuple",
113
parameters: [
114
{ brief: "Source", parse: String },
115
{ brief: "Dest", parse: String, optional: true }
116
]
117
}
118
119
// Array (variadic args)
120
positional: {
121
kind: "array",
122
parameter: { brief: "Files", parse: String },
123
minimum: 1
124
}
125
```
126
127
### Aliases
128
```typescript
129
parameters: {
130
flags: { verbose: { /* ... */ }, output: { /* ... */ } },
131
aliases: { v: "verbose", o: "output" } // -v, -o
132
}
133
```
134
135
## Decision Guide
136
137
### When to Use Each Flag Type
138
139
- **boolean**: On/off toggle (--verbose, --force)
140
- **counter**: Repeated flag for levels (-v, -vv, -vvv)
141
- **enum**: Fixed set of string values (--env dev|staging|prod)
142
- **parsed**: Any type needing transformation (numbers, URLs, dates, custom objects)
143
144
### When to Use enum vs parsed with buildChoiceParser
145
146
- **Use enum**: String literal unions, simple validation
147
- **Use parsed**: Need custom error messages, complex validation, or non-string types
148
149
### When to Use tuple vs array for Positional Args
150
151
- **tuple**: Fixed number of named arguments (copy source dest)
152
- **array**: Variable number of similar arguments (process file1 file2 file3...)
153
154
## Architecture
155
156
@stricli/core is built around several key components:
157
158
- **Application Layer**: `buildApplication` and `run` functions for creating and executing CLI applications
159
- **Routing System**: `buildCommand` and `buildRouteMap` for defining commands and nested command structures
160
- **Parameter System**: Type-safe flag and positional parameter definitions with automatic parsing and validation
161
- **Documentation Engine**: Automatic help text generation with customizable formatting and localization
162
- **Context System**: Generic context threading for passing custom data through command execution
163
- **Error Handling**: Comprehensive error classes with detailed validation and "did you mean?" suggestions
164
- **Configuration**: Extensive configuration for scanner behavior, documentation style, and completion proposals
165
166
## Core API Reference
167
168
### Application & Execution
169
170
Core functionality for creating and running CLI applications. Supports both single-command and multi-command (route map) applications with full configuration.
171
172
```typescript { .api }
173
/**
174
* Builds a CLI application from a command or route map with configuration
175
*/
176
function buildApplication<CONTEXT extends CommandContext>(
177
root: Command<CONTEXT>,
178
appConfig: PartialApplicationConfiguration
179
): Application<CONTEXT>;
180
function buildApplication<CONTEXT extends CommandContext>(
181
root: RouteMap<CONTEXT>,
182
config: PartialApplicationConfiguration
183
): Application<CONTEXT>;
184
185
/**
186
* Runs a CLI application with given inputs and context
187
*/
188
async function run<CONTEXT extends CommandContext>(
189
app: Application<CONTEXT>,
190
inputs: readonly string[],
191
context: StricliDynamicCommandContext<CONTEXT>
192
): Promise<void>;
193
194
interface Application<CONTEXT extends CommandContext> {
195
root: Command<CONTEXT> | RouteMap<CONTEXT>;
196
config: ApplicationConfiguration;
197
defaultText: ApplicationText;
198
}
199
```
200
201
[Full Documentation](./application.md)
202
203
### Commands & Routing
204
205
System for defining individual commands and organizing them into nested route maps. Supports both immediate and lazy-loaded command functions with full type inference.
206
207
```typescript { .api }
208
/**
209
* Builds a command with parameters, action, and documentation
210
*/
211
function buildCommand<
212
const FLAGS extends Readonly<Partial<Record<keyof FLAGS, unknown>>> = NonNullable<unknown>,
213
const ARGS extends BaseArgs = [],
214
const CONTEXT extends CommandContext = CommandContext
215
>(builderArgs: CommandBuilderArguments<FLAGS, ARGS, CONTEXT>): Command<CONTEXT>;
216
217
/**
218
* Builds a route map for organizing multiple commands
219
*/
220
function buildRouteMap<
221
R extends string,
222
CONTEXT extends CommandContext = CommandContext
223
>({
224
routes,
225
defaultCommand,
226
docs,
227
aliases
228
}: RouteMapBuilderArguments<R, CONTEXT>): RouteMap<CONTEXT>;
229
230
interface Command<CONTEXT extends CommandContext> {
231
kind: typeof CommandSymbol;
232
loader: CommandFunctionLoader<BaseFlags, BaseArgs, CONTEXT>;
233
parameters: CommandParameters;
234
usesFlag: (flagName: string) => boolean;
235
brief: string;
236
formatUsageLine: (args: UsageFormattingArguments) => string;
237
formatHelp: (args: HelpFormattingArguments) => string;
238
}
239
240
interface RouteMap<CONTEXT extends CommandContext> {
241
kind: typeof RouteMapSymbol;
242
getRoutingTargetForInput: (input: string) => Command<CONTEXT> | RouteMap<CONTEXT> | undefined;
243
getDefaultCommand: () => Command<CONTEXT> | undefined;
244
getOtherAliasesForInput: (input: string, caseStyle: ScannerCaseStyle) => Readonly<Record<DisplayCaseStyle, readonly string[]>>;
245
getAllEntries: () => readonly RouteMapEntry<CONTEXT>[];
246
brief: string;
247
formatUsageLine: (args: UsageFormattingArguments) => string;
248
formatHelp: (args: HelpFormattingArguments) => string;
249
}
250
251
type CommandFunction<FLAGS extends BaseFlags, ARGS extends BaseArgs, CONTEXT extends CommandContext> =
252
(this: CONTEXT, flags: FLAGS, ...args: ARGS) => void | Error | Promise<void | Error>;
253
```
254
255
[Full Documentation](./commands-and-routing.md)
256
257
### Parameter Parsers
258
259
Built-in parsers for common types and utilities for building custom parsers. Supports synchronous and asynchronous parsing.
260
261
```typescript { .api }
262
/**
263
* Generic input parser type
264
*/
265
type InputParser<T, CONTEXT extends CommandContext = CommandContext> =
266
(this: CONTEXT, input: string) => T | Promise<T>;
267
268
/**
269
* Parse boolean values (strict: "true" or "false")
270
*/
271
const booleanParser: (input: string) => boolean;
272
273
/**
274
* Parse boolean values (loose: "yes", "no", "on", "off", "1", "0", etc.)
275
*/
276
const looseBooleanParser: (input: string) => boolean;
277
278
/**
279
* Parse numeric values
280
*/
281
const numberParser: (input: string) => number;
282
283
/**
284
* Build a parser that validates against a set of choices
285
*/
286
function buildChoiceParser<T extends string>(choices: readonly T[]): InputParser<T>;
287
```
288
289
[Full Documentation](./parameter-parsers.md)
290
291
### Configuration
292
293
```typescript
294
interface PartialApplicationConfiguration {
295
name: string
296
versionInfo?: VersionInfo
297
scanner?: Partial<ScannerConfiguration>
298
documentation?: Partial<DocumentationConfiguration>
299
completion?: Partial<CompletionConfiguration>
300
localization?: Partial<LocalizationConfiguration>
301
determineExitCode?: (exc: unknown) => number
302
}
303
```
304
305
**Scanner Options:**
306
- `caseStyle`: `"original"` | `"allow-kebab-for-camel"` (default: `"original"`)
307
- `allowArgumentEscapeSequence`: boolean (default: `false`) - Enable `--` separator
308
309
**Documentation Options:**
310
- `caseStyle`: `"original"` | `"convert-camel-to-kebab"`
311
- `useAliasInUsageLine`: boolean (default: `false`)
312
- `disableAnsiColor`: boolean (default: `false`)
313
314
[Full Documentation](./configuration-and-context.md)
315
316
### Custom Context
317
318
```typescript
319
interface CommandContext {
320
process: { stdout: Writable; stderr: Writable }
321
}
322
323
// Extend for custom context
324
interface MyContext extends CommandContext {
325
database: Database
326
logger: Logger
327
}
328
329
await run(app, inputs, {
330
process,
331
async forCommand(info) {
332
return { process, database: await connectDB(), logger: createLogger() }
333
}
334
})
335
```
336
337
[Full Documentation](./configuration-and-context.md)
338
339
### Exit Codes
340
341
```typescript
342
const ExitCode = {
343
Success: 0,
344
CommandRunError: 1,
345
InternalError: -1,
346
CommandLoadError: -2,
347
ContextLoadError: -3,
348
InvalidArgument: -4,
349
UnknownCommand: -5
350
}
351
```
352
353
[Full Documentation](./exit-codes.md)
354
355
### Error Handling
356
357
Comprehensive error classes for parameter parsing and validation with detailed error messages and "did you mean?" suggestions.
358
359
```typescript { .api }
360
/**
361
* Base class for all argument scanner errors
362
*/
363
abstract class ArgumentScannerError extends Error {}
364
365
class FlagNotFoundError extends ArgumentScannerError {
366
readonly input: string;
367
readonly corrections: readonly string[];
368
readonly aliasName?: string;
369
}
370
371
class AliasNotFoundError extends ArgumentScannerError {
372
readonly input: string;
373
}
374
375
class ArgumentParseError extends ArgumentScannerError {
376
readonly externalFlagNameOrPlaceholder: string;
377
readonly input: string;
378
readonly exception: unknown;
379
}
380
381
class EnumValidationError extends ArgumentScannerError {
382
readonly externalFlagName: string;
383
readonly input: string;
384
readonly values: readonly string[];
385
}
386
387
class UnsatisfiedFlagError extends ArgumentScannerError {
388
readonly externalFlagName: string;
389
readonly nextFlagName?: string;
390
}
391
392
class UnsatisfiedPositionalError extends ArgumentScannerError {
393
readonly placeholder: string;
394
readonly limit?: [minimum: number, count: number];
395
}
396
397
class UnexpectedPositionalError extends ArgumentScannerError {
398
readonly expectedCount: number;
399
readonly input: string;
400
}
401
402
class UnexpectedFlagError extends ArgumentScannerError {
403
readonly externalFlagName: string;
404
readonly previousInput: string;
405
readonly input: string;
406
}
407
408
class InvalidNegatedFlagSyntaxError extends ArgumentScannerError {
409
readonly externalFlagName: string;
410
readonly valueText: string;
411
}
412
413
/**
414
* Utility for formatting scanner error messages
415
*/
416
function formatMessageForArgumentScannerError(
417
error: ArgumentScannerError,
418
formatter: Partial<ArgumentScannerErrorFormatter>
419
): string;
420
```
421
422
[Full Documentation](./error-handling.md)
423
424
### Documentation & Completions
425
426
Automatic help text generation for all commands with customizable formatting and localization support. Includes shell completion proposals.
427
428
```typescript { .api }
429
/**
430
* Generates help text for all commands in an application
431
*/
432
function generateHelpTextForAllCommands(
433
app: Application<CommandContext>,
434
locale?: string
435
): readonly DocumentedCommand[];
436
437
type DocumentedCommand = readonly [route: string, helpText: string];
438
439
/**
440
* Proposes completions for partial input (aliased as proposeCompletions)
441
*/
442
async function proposeCompletionsForApplication<CONTEXT extends CommandContext>(
443
app: Application<CONTEXT>,
444
rawInputs: readonly string[],
445
context: StricliDynamicCommandContext<CONTEXT>
446
): Promise<readonly InputCompletion[]>;
447
448
type InputCompletion = ArgumentCompletion | RoutingTargetCompletion;
449
450
interface RoutingTargetCompletion {
451
kind: "routing-target:command" | "routing-target:route-map";
452
completion: string;
453
brief: string;
454
}
455
```
456
457
[Full Documentation](./documentation-and-help.md)
458
459
## Complete Example
460
461
```typescript
462
import {
463
buildApplication,
464
buildRouteMap,
465
buildCommand,
466
run,
467
numberParser,
468
type CommandContext
469
} from "@stricli/core";
470
471
// Custom context
472
interface AppContext extends CommandContext {
473
config: { apiUrl: string };
474
}
475
476
// Commands
477
const deployCmd = buildCommand({
478
func: async function(this: AppContext, flags, service) {
479
this.process.stdout.write(
480
`Deploying ${service} to ${flags.env} (${this.config.apiUrl})\n`
481
);
482
if (flags.force) this.process.stdout.write("Force mode enabled\n");
483
},
484
parameters: {
485
flags: {
486
env: { kind: "enum", values: ["dev", "staging", "prod"], brief: "Environment", default: "dev" },
487
force: { kind: "boolean", brief: "Force deployment", default: false },
488
timeout: { kind: "parsed", parse: numberParser, brief: "Timeout (seconds)", optional: true }
489
},
490
positional: {
491
kind: "tuple",
492
parameters: [{ brief: "Service name", parse: String }]
493
},
494
aliases: { e: "env", f: "force", t: "timeout" }
495
},
496
docs: { brief: "Deploy a service" }
497
});
498
499
const statusCmd = buildCommand({
500
func: async function() {
501
this.process.stdout.write("All systems operational\n");
502
},
503
parameters: {},
504
docs: { brief: "Check status" }
505
});
506
507
// Application
508
const app = buildApplication(
509
buildRouteMap({
510
routes: { deploy: deployCmd, status: statusCmd },
511
docs: { brief: "Deployment CLI" }
512
}),
513
{
514
name: "myapp",
515
versionInfo: { currentVersion: "1.0.0" },
516
scanner: { caseStyle: "allow-kebab-for-camel", allowArgumentEscapeSequence: true }
517
}
518
);
519
520
// Run with custom context
521
await run(app, process.argv.slice(2), {
522
process,
523
locale: "en",
524
async forCommand() {
525
return { process, config: { apiUrl: "https://api.example.com" } };
526
}
527
});
528
```
529
530
## Detailed Documentation
531
532
- [Application Building and Execution](./application.md) - buildApplication, run, Application interface
533
- [Commands and Routing](./commands-and-routing.md) - buildCommand, buildRouteMap, lazy loading
534
- [Flag Parameters](./flag-parameters.md) - Boolean, counter, enum, parsed, variadic flags
535
- [Positional Parameters](./positional-parameters.md) - Tuple and array forms, optional args
536
- [Parameter Parsers](./parameter-parsers.md) - Built-in parsers, custom parsers, async parsing
537
- [Configuration and Context](./configuration-and-context.md) - App config, custom context, scanner settings
538
- [Error Handling](./error-handling.md) - Error types, custom error messages, safe error handling
539
- [Documentation and Help](./documentation-and-help.md) - Help generation, shell completions
540
- [Text and Localization](./text-and-localization.md) - Custom text, multi-language support
541
- [Exit Codes](./exit-codes.md) - Exit code constants, custom exit codes, environment variables
542
543
### Text and Localization
544
545
Comprehensive localization system with customizable text for all user-facing strings including help text, error messages, and documentation.
546
547
```typescript { .api }
548
interface ApplicationText extends ApplicationErrorFormatting {
549
keywords: DocumentationKeywords;
550
headers: DocumentationHeaders;
551
briefs: DocumentationBriefs;
552
currentVersionIsNotLatest: (args: {
553
currentVersion: string;
554
latestVersion: string;
555
upgradeCommand?: string;
556
ansiColor: boolean;
557
}) => string;
558
}
559
560
/**
561
* Default English text implementation
562
*/
563
const text_en: ApplicationText;
564
565
interface DocumentationKeywords {
566
default: string;
567
separator: string;
568
}
569
570
interface DocumentationHeaders {
571
usage: string;
572
aliases: string;
573
commands: string;
574
flags: string;
575
arguments: string;
576
}
577
578
interface DocumentationBriefs {
579
help: string;
580
helpAll: string;
581
version: string;
582
argumentEscapeSequence: string;
583
}
584
```
585
586
[Full Documentation](./text-and-localization.md)
587
588
### Exit Codes
589
590
Standard exit codes returned by Stricli applications for different execution outcomes.
591
592
```typescript { .api }
593
/**
594
* Enumeration of all possible exit codes
595
*/
596
const ExitCode: {
597
readonly UnknownCommand: -5;
598
readonly InvalidArgument: -4;
599
readonly ContextLoadError: -3;
600
readonly CommandLoadError: -2;
601
readonly InternalError: -1;
602
readonly Success: 0;
603
readonly CommandRunError: 1;
604
};
605
606
/**
607
* Environment variable names used by Stricli
608
*/
609
type EnvironmentVariableName =
610
| "STRICLI_SKIP_VERSION_CHECK"
611
| "STRICLI_NO_COLOR";
612
```
613
614
[Full Documentation](./exit-codes.md)
615
616
## Types
617
618
### Core Type Definitions
619
620
```typescript { .api }
621
interface StricliProcess {
622
stdout: Writable;
623
stderr: Writable;
624
env?: Readonly<Partial<Record<string, string>>>;
625
exitCode?: number | string | null;
626
}
627
628
interface Writable {
629
write: (str: string) => void;
630
getColorDepth?: (env?: Readonly<Partial<Record<string, string>>>) => number;
631
}
632
633
interface CommandInfo {
634
prefix: readonly string[];
635
}
636
637
type StricliCommandContextBuilder<CONTEXT extends CommandContext> =
638
(info: CommandInfo) => CONTEXT | Promise<CONTEXT>;
639
640
interface CommandModule<
641
FLAGS extends BaseFlags,
642
ARGS extends BaseArgs,
643
CONTEXT extends CommandContext
644
> {
645
default: CommandFunction<FLAGS, ARGS, CONTEXT>;
646
}
647
648
type CommandFunctionLoader<
649
FLAGS extends BaseFlags,
650
ARGS extends BaseArgs,
651
CONTEXT extends CommandContext
652
> = () => Promise<CommandModule<FLAGS, ARGS, CONTEXT> | CommandFunction<FLAGS, ARGS, CONTEXT>>;
653
```
654
655
## Environment Variables
656
657
- `STRICLI_SKIP_VERSION_CHECK=1` - Skip automatic version checking
658
- `STRICLI_NO_COLOR=1` - Disable ANSI color output
659