Build complex CLIs with type safety and no dependencies
Type-safe flag definitions supporting boolean, counter, enum, and parsed types with optional, required, and variadic variants.
flags: {
// Boolean
verbose: { kind: "boolean", brief: "Verbose output", default: false },
dryRun: { kind: "boolean", brief: "Dry run", optional: true },
// Counter (repeatable)
verbosity: { kind: "counter", brief: "Verbosity level" },
// Enum (string literals)
env: { kind: "enum", values: ["dev", "prod"], brief: "Environment", default: "dev" },
// Parsed (custom type)
port: { kind: "parsed", parse: numberParser, brief: "Port", default: "3000" },
timeout: { kind: "parsed", parse: numberParser, brief: "Timeout", optional: true },
// Variadic
files: { kind: "parsed", parse: String, variadic: true, brief: "Files" },
tags: { kind: "parsed", parse: String, variadic: ",", brief: "Tags", optional: true }
},
aliases: { v: "verbose", e: "env", p: "port" }On/off toggles with optional negation syntax.
// Required with default
verbose: {
kind: "boolean",
brief: "Enable verbose output",
default: false // optional, defaults to false
}
// Optional
dryRun: {
kind: "boolean",
brief: "Perform dry run",
optional: true
}Usage: --verbose, --no-verbose, --dry-run
Increment with each occurrence.
verbosity: {
kind: "counter",
brief: "Increase verbosity (-v, -vv, -vvv)"
}Usage: -v (1), -vv (2), -vvv (3), --verbosity --verbosity (2)
Predefined set of string values.
// Single value
env: {
kind: "enum",
values: ["dev", "staging", "prod"],
brief: "Target environment",
default: "dev" // optional
}
// Variadic (multiple values)
features: {
kind: "enum",
values: ["auth", "api", "ui"],
brief: "Enabled features",
variadic: true // or "," for comma-separated
}Usage: --env prod, --features auth --features api, --features auth,api,ui
Custom parser for any type.
// Single value with default
port: {
kind: "parsed",
parse: numberParser,
brief: "Server port",
default: "3000"
}
// Optional
timeout: {
kind: "parsed",
parse: (input) => parseInt(input, 10),
brief: "Timeout in seconds",
optional: true
}
// Variadic
files: {
kind: "parsed",
parse: String,
variadic: true,
brief: "Input files",
proposeCompletions: async () => await readdir(".") // optional
}Common:
kind: "boolean" | "counter" | "enum" | "parsed"brief: string (description)optional: boolean (makes flag optional, undefined if not provided)default: string (for parsed/enum) | boolean (for boolean) - conflicts with optionalplaceholder: string (custom placeholder in usage line)hidden: boolean (hide from help text)Parsed-specific:
parse: InputParser<T, CONTEXT>variadic: boolean | string (true or separator like ",")inferEmpty: boolean (if flag specified without value, use "")proposeCompletions: (partial: string) => string[] | Promise<string[]>Enum-specific:
values: readonly T[]variadic: boolean | stringGeneric type for defining flag parameters with full type inference. Automatically infers the appropriate flag type based on TypeScript types.
/**
* Definition of a flag parameter
* Properties vary based on type T:
* - boolean types -> BooleanFlagParameter
* - number types -> CounterFlagParameter or ParsedFlagParameter
* - string literal unions -> EnumFlagParameter
* - arrays -> Variadic flags
* - undefined in union -> Optional flags
*/
type TypedFlagParameter<T, CONTEXT extends CommandContext = CommandContext> =
undefined extends T
? TypedFlagParameter_Optional<NonNullable<T>, CONTEXT>
: TypedFlagParameter_Required<T, CONTEXT>;Mapped type defining flags for each named parameter in type T. Transforms TypeScript interface into flag parameter definitions.
/**
* Definition of flags for each named parameter
* Transforms TypeScript interface into flag parameter definitions
*/
type FlagParametersForType<T, CONTEXT extends CommandContext = CommandContext> = {
readonly [K in keyof T]-?: TypedFlagParameter<T[K], CONTEXT>;
};Usage Example:
import { FlagParametersForType, buildCommand } from "@stricli/core";
// Define flags type
interface MyFlags {
verbose: boolean;
output?: string;
count: number;
mode: "fast" | "slow";
}
// Type-safe flag parameters
const flagParams: FlagParametersForType<MyFlags> = {
verbose: {
kind: "boolean",
brief: "Enable verbose mode"
},
output: {
kind: "parsed",
parse: (s) => s,
brief: "Output file path",
optional: true
},
count: {
kind: "counter",
brief: "Repeat count"
},
mode: {
kind: "enum",
values: ["fast", "slow"],
brief: "Processing mode",
default: "fast"
}
};
const command = buildCommand({
func: async function(flags: MyFlags) {
// flags is fully typed!
if (flags.verbose) {
this.process.stdout.write("Verbose mode\n");
}
},
parameters: {
flags: flagParams
},
docs: {
brief: "My command"
}
});Typed definitions for all flag parameters with optional aliases mapping.
/**
* Typed definitions for all flag parameters
*/
interface TypedCommandFlagParameters<
FLAGS extends BaseFlags,
CONTEXT extends CommandContext
> {
/** Typed definitions for all flag parameters */
readonly flags: FlagParametersForType<FLAGS, CONTEXT>;
/** Object that aliases single characters to flag names */
readonly aliases?: Aliases<keyof FLAGS & string>;
}
/** Root constraint for all flag type parameters */
type BaseFlags = Readonly<Record<string, unknown>>;Object mapping single-character aliases to flag names. Limited to single letters a-g, i-z, A-Z (h is reserved).
/**
* Object mapping single-character aliases to flag names
* Limited to single letters a-g, i-z, A-Z (h is reserved)
*/
type Aliases<T> = Readonly<Partial<Record<AvailableAlias, T>>>;
type AvailableAlias = Exclude<
| "a" | "b" | "c" | "d" | "e" | "f" | "g"
| "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r"
| "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"
| "A" | "B" | "C" | "D" | "E" | "F" | "G"
| "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R"
| "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z",
"h"
>;Usage Example:
parameters: {
flags: { verbose, output, force },
aliases: { v: "verbose", o: "output", f: "force" }
}
// Usage: -v -o file.txt -f
// Usage: -vof file.txt (combined)
// Usage: --verbose --output file.txt --forceinterface DeployFlags {
environment: "dev" | "staging" | "prod";
verbose: boolean;
dryRun?: boolean;
timeout?: number;
replicas: number;
services: readonly string[];
tags?: readonly string[];
}
const deployCommand = buildCommand({
func: async function(flags: DeployFlags) {
this.process.stdout.write(`Deploying to ${flags.environment}\n`);
this.process.stdout.write(`Services: ${flags.services.join(", ")}\n`);
if (flags.dryRun) {
this.process.stdout.write("DRY RUN\n");
return;
}
},
parameters: {
flags: {
environment: { kind: "enum", values: ["dev", "staging", "prod"], brief: "Environment", default: "dev" },
verbose: { kind: "boolean", brief: "Verbose logging", default: false },
dryRun: { kind: "boolean", brief: "Dry run mode", optional: true },
timeout: { kind: "parsed", parse: numberParser, brief: "Timeout (seconds)", optional: true },
replicas: { kind: "counter", brief: "Replicas (specify multiple times)" },
services: { kind: "parsed", parse: String, variadic: true, brief: "Services to deploy" },
tags: { kind: "parsed", parse: String, variadic: ",", optional: true, brief: "Tags (comma-separated)" }
},
aliases: { e: "environment", v: "verbose", d: "dryRun", t: "timeout", r: "replicas" }
},
docs: { brief: "Deploy services" }
});
// Usage:
// myapp -e prod -v -rrr --services api --services web
// myapp --environment staging --dry-run --timeout 300 --services api
// myapp --tags v2.0,stable --services api --services webInstall with Tessl CLI
npx tessl i tessl/npm-stricli--core