stricli
Stricli CLI Framework
Stricli is Bloomberg's type-safe CLI framework for TypeScript. It provides compile-time type checking for command parameters, automatic help generation, and flexible command routing.
Multi-Runtime Support: Works with Node.js, Bun, and Deno.
Quick Start
Create a minimal single-command CLI:
1. Installation
npm install @stricli/core
Optional packages:
npm install @stricli/auto-complete # Shell completion support
2. Project Structure
my-cli/
├── src/
│ ├── commands/
│ │ └── greet.ts # Command implementation
│ ├── app.ts # Application entry point
│ └── index.ts # CLI runner
├── package.json
└── tsconfig.json
3. Define a Command (src/commands/greet.ts)
import { buildCommand } from "@stricli/core";
export interface GreetFlags {
readonly name: string;
readonly shout: boolean;
}
export const greet = buildCommand({
docs: {
brief: "Greet a user",
description: "Prints a greeting message to the console"
},
parameters: {
flags: {
name: {
kind: "parsed",
brief: "Name to greet",
parse: String,
default: "World"
},
shout: {
kind: "boolean",
brief: "Use uppercase",
default: false
}
}
},
func(flags: GreetFlags) {
const message = "Hello, " + flags.name + "!";
console.log(flags.shout ? message.toUpperCase() : message);
}
});
4. Build Application (src/app.ts)
import { buildApplication } from "@stricli/core";
import { name, version, description } from "../package.json";
import { greet } from "./commands/greet";
export const app = buildApplication({
name,
version,
description,
command: greet
});
5. Run CLI (src/index.ts)
import { run } from "@stricli/core";
import { app } from "./app";
await run(app, process.argv.slice(2));
6. Configure package.json
{
"name": "my-cli",
"version": "1.0.0",
"type": "module",
"bin": {
"my-cli": "./src/index.ts"
},
"scripts": {
"start": "npx tsx src/index.ts"
}
}
Note: With Bun, you can run TypeScript directly. With Node.js, use tsx or compile with tsc first.
Core Concepts
buildCommand(config)— creates a command with typed parameters and afunchandlerbuildRouteMap(config)— organizes commands/sub-routes into a hierarchybuildApplication(config)— wraps a command or route map into an executable app (name, version, description)run(app, args, context?)— executes the application with CLI arguments
See Commands, Routing, and Applications for full API details and interfaces.
Parameter Types
Stricli supports five flag kinds (boolean, counter, enum, parsed, variadic) and two positional kinds (tuple for fixed args, array for variable-length). All flags support brief, description?, default?, and hidden?.
See Parameters for full interface definitions, examples, and advanced patterns.
Workflow
Building a Single-Command CLI
- Define command - Create command file with
buildCommand - Implement function - Add business logic in
funcproperty - Build application - Wrap command with
buildApplication - Run - Execute with
run(app, args)
Building a Multi-Command CLI
- Define commands - Create multiple command files
- Create route map - Organize commands with
buildRouteMap - Build application - Pass route map to
buildApplication - Run - Execute with command routing:
my-cli create ...
Adding Custom Parsers
const urlParser = (input: string): URL => {
try {
return new URL(input);
} catch (error) {
throw new Error(`Invalid URL: ${input}`);
}
};
flags: {
endpoint: {
kind: "parsed",
parse: urlParser,
brief: "API endpoint URL"
}
}
Using Custom Context
Pass shared state/dependencies to all commands:
interface AppContext {
readonly config: Config;
readonly logger: Logger;
}
// In func
func(flags, positional, context: AppContext) {
context.logger.info("Running command...");
}
// When running
await run(app, args, { config, logger });
Multi-Command Structure
Nested Routes
const projectRoutes = buildRouteMap({
routes: {
create: createProjectCommand,
delete: deleteProjectCommand,
list: listProjectsCommand
},
docs: { brief: "Manage projects" }
});
const taskRoutes = buildRouteMap({
routes: {
add: addTaskCommand,
complete: completeTaskCommand
},
docs: { brief: "Manage tasks" }
});
const rootRoutes = buildRouteMap({
routes: {
project: projectRoutes,
task: taskRoutes
}
});
const app = buildApplication({
name: "pm",
version: "1.0.0",
command: rootRoutes
});
Usage:
pm project create myapppm project listpm task add "Write docs"
Lazy Loading
For large CLIs, use lazy loading to improve startup time:
const routes = buildRouteMap({
routes: {
heavy: {
lazy: async () => {
const mod = await import("./commands/heavy");
return mod.heavyCommand;
},
brief: "Heavy operation"
}
}
});
Built-in Features
Stricli auto-generates --help (from docs.brief/docs.description) and --version for all commands and route maps.
Testing Commands
Commands are pure functions - easy to test:
// Use your preferred test runner (vitest, bun:test, jest, etc.)
import { test, expect } from "vitest";
import { greet } from "./commands/greet";
test("greet with default name", () => {
const output: string[] = [];
const mockConsole = {
log: (msg: string) => output.push(msg)
};
greet.func.call(
{ console: mockConsole },
{ name: "World", shout: false },
undefined,
undefined
);
expect(output[0]).toBe("Hello, World!");
});
Reference Documentation
For detailed API documentation and complete examples, see:
- Commands, Routing, and Applications - buildCommand, buildRouteMap, buildApplication, run, func signature, aliases, lazy loading, scanner config
- Parameters - Flag types (boolean, counter, enum, parsed, variadic) and positional types (tuple, array)
- Parsers - Built-in parsers, custom parsers, error handling
- Context - Custom context, LocalContext, exit codes
- Auto-Complete - Shell completion with @stricli/auto-complete
- Examples - Complete working examples including custom parsers, variadic flags, lazy loading, and testing