or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

application.mdcommands-and-routing.mdconfiguration-and-context.mddocumentation-and-help.mderror-handling.mdexit-codes.mdflag-parameters.mdindex.mdparameter-parsers.mdpositional-parameters.mdtext-and-localization.md
tile.json

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

index.mddocs/

@stricli/core

@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.

Package: @stricli/core | Type: npm | Language: TypeScript

Installation

npm install @stricli/core

Core Imports

import {
  buildApplication,
  buildCommand,
  buildRouteMap,
  run,
  type CommandContext,
  type Application
} from "@stricli/core";

For CommonJS:

const {
  buildApplication,
  buildCommand,
  buildRouteMap,
  run
} = require("@stricli/core");

Quick Start

import { buildApplication, buildCommand, run } from "@stricli/core";

const app = buildApplication(
  buildCommand({
    func: async function(flags, name) {
      this.process.stdout.write(`Hello, ${name || flags.default}!\n`);
    },
    parameters: {
      flags: {
        default: { kind: "parsed", parse: String, brief: "Default name", default: "World" }
      },
      positional: {
        kind: "tuple",
        parameters: [{ brief: "Name", parse: String, optional: true }]
      }
    },
    docs: { brief: "Greet someone" }
  }),
  { name: "greet" }
);

await run(app, process.argv.slice(2), { process });

Common Patterns Cheat Sheet

Basic Command

buildCommand({
  func: async function(flags) { /* ... */ },
  parameters: { flags: { /* ... */ } },
  docs: { brief: "Description" }
})

Route Map (Multiple Commands)

buildApplication(
  buildRouteMap({
    routes: { cmd1: command1, cmd2: command2 },
    docs: { brief: "CLI description" }
  }),
  { name: "myapp" }
)

Flag Types

// Boolean
verbose: { kind: "boolean", brief: "Enable verbose", default: false }

// Counter (repeatable -vvv)
verbosity: { kind: "counter", brief: "Verbosity level" }

// Enum (predefined values)
env: { kind: "enum", values: ["dev", "prod"], brief: "Environment", default: "dev" }

// Parsed (custom type)
port: { kind: "parsed", parse: numberParser, brief: "Port", default: "3000" }

// Variadic (multiple values)
files: { kind: "parsed", parse: String, variadic: true, brief: "Files" }

// Optional
timeout: { kind: "parsed", parse: numberParser, brief: "Timeout", optional: true }

Positional Arguments

// Tuple (fixed args)
positional: {
  kind: "tuple",
  parameters: [
    { brief: "Source", parse: String },
    { brief: "Dest", parse: String, optional: true }
  ]
}

// Array (variadic args)
positional: {
  kind: "array",
  parameter: { brief: "Files", parse: String },
  minimum: 1
}

Aliases

parameters: {
  flags: { verbose: { /* ... */ }, output: { /* ... */ } },
  aliases: { v: "verbose", o: "output" }  // -v, -o
}

Decision Guide

When to Use Each Flag Type

  • boolean: On/off toggle (--verbose, --force)
  • counter: Repeated flag for levels (-v, -vv, -vvv)
  • enum: Fixed set of string values (--env dev|staging|prod)
  • parsed: Any type needing transformation (numbers, URLs, dates, custom objects)

When to Use enum vs parsed with buildChoiceParser

  • Use enum: String literal unions, simple validation
  • Use parsed: Need custom error messages, complex validation, or non-string types

When to Use tuple vs array for Positional Args

  • tuple: Fixed number of named arguments (copy source dest)
  • array: Variable number of similar arguments (process file1 file2 file3...)

Architecture

@stricli/core is built around several key components:

  • Application Layer: buildApplication and run functions for creating and executing CLI applications
  • Routing System: buildCommand and buildRouteMap for defining commands and nested command structures
  • Parameter System: Type-safe flag and positional parameter definitions with automatic parsing and validation
  • Documentation Engine: Automatic help text generation with customizable formatting and localization
  • Context System: Generic context threading for passing custom data through command execution
  • Error Handling: Comprehensive error classes with detailed validation and "did you mean?" suggestions
  • Configuration: Extensive configuration for scanner behavior, documentation style, and completion proposals

Core API Reference

Application & Execution

Core functionality for creating and running CLI applications. Supports both single-command and multi-command (route map) applications with full configuration.

/**
 * Builds a CLI application from a command or route map with configuration
 */
function buildApplication<CONTEXT extends CommandContext>(
  root: Command<CONTEXT>,
  appConfig: PartialApplicationConfiguration
): Application<CONTEXT>;
function buildApplication<CONTEXT extends CommandContext>(
  root: RouteMap<CONTEXT>,
  config: PartialApplicationConfiguration
): Application<CONTEXT>;

/**
 * Runs a CLI application with given inputs and context
 */
async function run<CONTEXT extends CommandContext>(
  app: Application<CONTEXT>,
  inputs: readonly string[],
  context: StricliDynamicCommandContext<CONTEXT>
): Promise<void>;

interface Application<CONTEXT extends CommandContext> {
  root: Command<CONTEXT> | RouteMap<CONTEXT>;
  config: ApplicationConfiguration;
  defaultText: ApplicationText;
}

Full Documentation

Commands & Routing

System for defining individual commands and organizing them into nested route maps. Supports both immediate and lazy-loaded command functions with full type inference.

/**
 * Builds a command with parameters, action, and documentation
 */
function buildCommand<
  const FLAGS extends Readonly<Partial<Record<keyof FLAGS, unknown>>> = NonNullable<unknown>,
  const ARGS extends BaseArgs = [],
  const CONTEXT extends CommandContext = CommandContext
>(builderArgs: CommandBuilderArguments<FLAGS, ARGS, CONTEXT>): Command<CONTEXT>;

/**
 * Builds a route map for organizing multiple commands
 */
function buildRouteMap<
  R extends string,
  CONTEXT extends CommandContext = CommandContext
>({
  routes,
  defaultCommand,
  docs,
  aliases
}: RouteMapBuilderArguments<R, CONTEXT>): RouteMap<CONTEXT>;

interface Command<CONTEXT extends CommandContext> {
  kind: typeof CommandSymbol;
  loader: CommandFunctionLoader<BaseFlags, BaseArgs, CONTEXT>;
  parameters: CommandParameters;
  usesFlag: (flagName: string) => boolean;
  brief: string;
  formatUsageLine: (args: UsageFormattingArguments) => string;
  formatHelp: (args: HelpFormattingArguments) => string;
}

interface RouteMap<CONTEXT extends CommandContext> {
  kind: typeof RouteMapSymbol;
  getRoutingTargetForInput: (input: string) => Command<CONTEXT> | RouteMap<CONTEXT> | undefined;
  getDefaultCommand: () => Command<CONTEXT> | undefined;
  getOtherAliasesForInput: (input: string, caseStyle: ScannerCaseStyle) => Readonly<Record<DisplayCaseStyle, readonly string[]>>;
  getAllEntries: () => readonly RouteMapEntry<CONTEXT>[];
  brief: string;
  formatUsageLine: (args: UsageFormattingArguments) => string;
  formatHelp: (args: HelpFormattingArguments) => string;
}

type CommandFunction<FLAGS extends BaseFlags, ARGS extends BaseArgs, CONTEXT extends CommandContext> =
  (this: CONTEXT, flags: FLAGS, ...args: ARGS) => void | Error | Promise<void | Error>;

Full Documentation

Parameter Parsers

Built-in parsers for common types and utilities for building custom parsers. Supports synchronous and asynchronous parsing.

/**
 * Generic input parser type
 */
type InputParser<T, CONTEXT extends CommandContext = CommandContext> =
  (this: CONTEXT, input: string) => T | Promise<T>;

/**
 * Parse boolean values (strict: "true" or "false")
 */
const booleanParser: (input: string) => boolean;

/**
 * Parse boolean values (loose: "yes", "no", "on", "off", "1", "0", etc.)
 */
const looseBooleanParser: (input: string) => boolean;

/**
 * Parse numeric values
 */
const numberParser: (input: string) => number;

/**
 * Build a parser that validates against a set of choices
 */
function buildChoiceParser<T extends string>(choices: readonly T[]): InputParser<T>;

Full Documentation

Configuration

interface PartialApplicationConfiguration {
  name: string
  versionInfo?: VersionInfo
  scanner?: Partial<ScannerConfiguration>
  documentation?: Partial<DocumentationConfiguration>
  completion?: Partial<CompletionConfiguration>
  localization?: Partial<LocalizationConfiguration>
  determineExitCode?: (exc: unknown) => number
}

Scanner Options:

  • caseStyle: "original" | "allow-kebab-for-camel" (default: "original")
  • allowArgumentEscapeSequence: boolean (default: false) - Enable -- separator

Documentation Options:

  • caseStyle: "original" | "convert-camel-to-kebab"
  • useAliasInUsageLine: boolean (default: false)
  • disableAnsiColor: boolean (default: false)

Full Documentation

Custom Context

interface CommandContext {
  process: { stdout: Writable; stderr: Writable }
}

// Extend for custom context
interface MyContext extends CommandContext {
  database: Database
  logger: Logger
}

await run(app, inputs, {
  process,
  async forCommand(info) {
    return { process, database: await connectDB(), logger: createLogger() }
  }
})

Full Documentation

Exit Codes

const ExitCode = {
  Success: 0,
  CommandRunError: 1,
  InternalError: -1,
  CommandLoadError: -2,
  ContextLoadError: -3,
  InvalidArgument: -4,
  UnknownCommand: -5
}

Full Documentation

Error Handling

Comprehensive error classes for parameter parsing and validation with detailed error messages and "did you mean?" suggestions.

/**
 * Base class for all argument scanner errors
 */
abstract class ArgumentScannerError extends Error {}

class FlagNotFoundError extends ArgumentScannerError {
  readonly input: string;
  readonly corrections: readonly string[];
  readonly aliasName?: string;
}

class AliasNotFoundError extends ArgumentScannerError {
  readonly input: string;
}

class ArgumentParseError extends ArgumentScannerError {
  readonly externalFlagNameOrPlaceholder: string;
  readonly input: string;
  readonly exception: unknown;
}

class EnumValidationError extends ArgumentScannerError {
  readonly externalFlagName: string;
  readonly input: string;
  readonly values: readonly string[];
}

class UnsatisfiedFlagError extends ArgumentScannerError {
  readonly externalFlagName: string;
  readonly nextFlagName?: string;
}

class UnsatisfiedPositionalError extends ArgumentScannerError {
  readonly placeholder: string;
  readonly limit?: [minimum: number, count: number];
}

class UnexpectedPositionalError extends ArgumentScannerError {
  readonly expectedCount: number;
  readonly input: string;
}

class UnexpectedFlagError extends ArgumentScannerError {
  readonly externalFlagName: string;
  readonly previousInput: string;
  readonly input: string;
}

class InvalidNegatedFlagSyntaxError extends ArgumentScannerError {
  readonly externalFlagName: string;
  readonly valueText: string;
}

/**
 * Utility for formatting scanner error messages
 */
function formatMessageForArgumentScannerError(
  error: ArgumentScannerError,
  formatter: Partial<ArgumentScannerErrorFormatter>
): string;

Full Documentation

Documentation & Completions

Automatic help text generation for all commands with customizable formatting and localization support. Includes shell completion proposals.

/**
 * Generates help text for all commands in an application
 */
function generateHelpTextForAllCommands(
  app: Application<CommandContext>,
  locale?: string
): readonly DocumentedCommand[];

type DocumentedCommand = readonly [route: string, helpText: string];

/**
 * Proposes completions for partial input (aliased as proposeCompletions)
 */
async function proposeCompletionsForApplication<CONTEXT extends CommandContext>(
  app: Application<CONTEXT>,
  rawInputs: readonly string[],
  context: StricliDynamicCommandContext<CONTEXT>
): Promise<readonly InputCompletion[]>;

type InputCompletion = ArgumentCompletion | RoutingTargetCompletion;

interface RoutingTargetCompletion {
  kind: "routing-target:command" | "routing-target:route-map";
  completion: string;
  brief: string;
}

Full Documentation

Complete Example

import {
  buildApplication,
  buildRouteMap,
  buildCommand,
  run,
  numberParser,
  type CommandContext
} from "@stricli/core";

// Custom context
interface AppContext extends CommandContext {
  config: { apiUrl: string };
}

// Commands
const deployCmd = buildCommand({
  func: async function(this: AppContext, flags, service) {
    this.process.stdout.write(
      `Deploying ${service} to ${flags.env} (${this.config.apiUrl})\n`
    );
    if (flags.force) this.process.stdout.write("Force mode enabled\n");
  },
  parameters: {
    flags: {
      env: { kind: "enum", values: ["dev", "staging", "prod"], brief: "Environment", default: "dev" },
      force: { kind: "boolean", brief: "Force deployment", default: false },
      timeout: { kind: "parsed", parse: numberParser, brief: "Timeout (seconds)", optional: true }
    },
    positional: {
      kind: "tuple",
      parameters: [{ brief: "Service name", parse: String }]
    },
    aliases: { e: "env", f: "force", t: "timeout" }
  },
  docs: { brief: "Deploy a service" }
});

const statusCmd = buildCommand({
  func: async function() {
    this.process.stdout.write("All systems operational\n");
  },
  parameters: {},
  docs: { brief: "Check status" }
});

// Application
const app = buildApplication(
  buildRouteMap({
    routes: { deploy: deployCmd, status: statusCmd },
    docs: { brief: "Deployment CLI" }
  }),
  {
    name: "myapp",
    versionInfo: { currentVersion: "1.0.0" },
    scanner: { caseStyle: "allow-kebab-for-camel", allowArgumentEscapeSequence: true }
  }
);

// Run with custom context
await run(app, process.argv.slice(2), {
  process,
  locale: "en",
  async forCommand() {
    return { process, config: { apiUrl: "https://api.example.com" } };
  }
});

Detailed Documentation

Text and Localization

Comprehensive localization system with customizable text for all user-facing strings including help text, error messages, and documentation.

interface ApplicationText extends ApplicationErrorFormatting {
  keywords: DocumentationKeywords;
  headers: DocumentationHeaders;
  briefs: DocumentationBriefs;
  currentVersionIsNotLatest: (args: {
    currentVersion: string;
    latestVersion: string;
    upgradeCommand?: string;
    ansiColor: boolean;
  }) => string;
}

/**
 * Default English text implementation
 */
const text_en: ApplicationText;

interface DocumentationKeywords {
  default: string;
  separator: string;
}

interface DocumentationHeaders {
  usage: string;
  aliases: string;
  commands: string;
  flags: string;
  arguments: string;
}

interface DocumentationBriefs {
  help: string;
  helpAll: string;
  version: string;
  argumentEscapeSequence: string;
}

Full Documentation

Exit Codes

Standard exit codes returned by Stricli applications for different execution outcomes.

/**
 * Enumeration of all possible exit codes
 */
const ExitCode: {
  readonly UnknownCommand: -5;
  readonly InvalidArgument: -4;
  readonly ContextLoadError: -3;
  readonly CommandLoadError: -2;
  readonly InternalError: -1;
  readonly Success: 0;
  readonly CommandRunError: 1;
};

/**
 * Environment variable names used by Stricli
 */
type EnvironmentVariableName =
  | "STRICLI_SKIP_VERSION_CHECK"
  | "STRICLI_NO_COLOR";

Full Documentation

Types

Core Type Definitions

interface StricliProcess {
  stdout: Writable;
  stderr: Writable;
  env?: Readonly<Partial<Record<string, string>>>;
  exitCode?: number | string | null;
}

interface Writable {
  write: (str: string) => void;
  getColorDepth?: (env?: Readonly<Partial<Record<string, string>>>) => number;
}

interface CommandInfo {
  prefix: readonly string[];
}

type StricliCommandContextBuilder<CONTEXT extends CommandContext> =
  (info: CommandInfo) => CONTEXT | Promise<CONTEXT>;

interface CommandModule<
  FLAGS extends BaseFlags,
  ARGS extends BaseArgs,
  CONTEXT extends CommandContext
> {
  default: CommandFunction<FLAGS, ARGS, CONTEXT>;
}

type CommandFunctionLoader<
  FLAGS extends BaseFlags,
  ARGS extends BaseArgs,
  CONTEXT extends CommandContext
> = () => Promise<CommandModule<FLAGS, ARGS, CONTEXT> | CommandFunction<FLAGS, ARGS, CONTEXT>>;

Environment Variables

  • STRICLI_SKIP_VERSION_CHECK=1 - Skip automatic version checking
  • STRICLI_NO_COLOR=1 - Disable ANSI color output