or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

tessl/npm-stricli--core

Build complex CLIs with type safety and no dependencies

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/@stricli/core@1.2.x

To install, run

npx @tessl/cli install tessl/npm-stricli--core@1.2.0

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