Stricli CLI Framework
Stricli is Bloomberg's type-safe CLI framework for TypeScript. Strongly-typed flags and positional arguments, explicit command routing, automatic help generation, and an isolated CommandContext per run.
Reference files live in ${CLAUDESKILLDIR}/references/.
When NOT to use
- The user has an existing CLI on a different framework (commander, yargs, oclif, minimist) — this skill doesn't migrate, and the APIs don't translate.
- The project uses
cleye — different library, non-transferable API. Use the cleye skill.
- Generic "which CLI framework should I use?" — that's a design conversation, not a Stricli question.
- Non-TypeScript CLIs — Stricli's core value is its compile-time type safety.
- Runtime debugging of an installed CLI (not developing it) — use shell/debugging tooling.
Core API surface
Stricli's public API is intentionally narrow. If something isn't listed here or in the references, assume it doesn't exist — checking the upstream repo is faster than guessing, and invented APIs compile until they don't.
| Entry point |
Purpose |
| `buildCommand({ func \ |
loader, parameters, docs })` |
Define a single command |
buildRouteMap({ routes, docs, aliases?, defaultCommand? }) |
Compose subcommands |
buildApplication(rootCommandOrRouteMap, config, integrations?) |
Wrap with app-level config (name, scanner, …); third argument registers integrations (1.3.0+) |
run(app, inputs, context) |
Execute the app against tokenized input + a runtime context |
CommandContext |
The shape that runtime context extends |
help(config) / version(config) |
Built-in integrations for --help / --version (1.3.0+) |
Version awareness
Check the installed @stricli/core before using version-gated API. The integrations argument, lifecycle hooks, and the exported help/version factories arrived in 1.3.0; withNegated on boolean flags and defaults on variadic flags arrived in 1.2.5. The rest of this skill applies across the 1.x line. Details in [references/integrations.md](references/integrations.md).
Installation
Upstream docs assume Node + npm. Stay agnostic to the user's package manager — pnpm and bun work equally well.
npm install @stricli/core # required
npm install @stricli/auto-complete # optional, bash completion
# pnpm add / bun add work the same way
Scaffolding a new app
npx @stricli/create-app@latest my-app
# pnpm dlx / bunx work the same way
The generator produces the reference directory layout. For hand-written apps, follow the quick start below.
Quick start: single-command CLI
1. Define the command
import { buildCommand, type CommandContext } from "@stricli/core";
interface GreetFlags {
readonly shout?: boolean;
}
export const greetCommand = buildCommand({
docs: { brief: "Print a greeting" },
parameters: {
flags: {
shout: {
kind: "boolean",
brief: "Uppercase the greeting",
optional: true,
},
},
positional: {
kind: "tuple",
parameters: [
{ brief: "Name to greet", parse: String, placeholder: "name" },
],
},
},
func(this: CommandContext, flags: GreetFlags, name: string) {
const message = `Hello, ${name}!`;
this.process.stdout.write(
`${flags.shout ? message.toUpperCase() : message}\n`
);
},
});
2. Build the application
import { buildApplication, help, version } from "@stricli/core";
import { version as currentVersion } from "../package.json";
import { greetCommand } from "./commands/greet";
const formatting = {
useAliasInUsageLine: false,
onlyRequiredInUsageLine: false,
caseStyle: "original",
} as const;
export const app = buildApplication(
greetCommand,
{ name: "my-cli" },
{
help: help({
brief: "Print help information and exit",
defaultForRouteMap: true,
formatting,
}),
helpAll: help({
brief:
"Print help information (including hidden commands/flags) and exit",
alias: "H",
hidden: true,
includeHidden: true,
formatting,
}),
version: version({
brief: "Print version information and exit",
info: { currentVersion },
}),
}
);
On 1.3.0+ the version integration is the primary way to enable --version; the config key versionInfo: { currentVersion } (with no third argument) is the legacy 1.2.x form and is @deprecated on 1.3.0, though it still works. Passing the third argument replaces every default, which is why help and helpAll are re-registered above.
3. Run it
import { run } from "@stricli/core";
import { app } from "./app";
await run(app, process.argv.slice(2), { process });
Parameter model
- Flag kinds:
parsed, enum, boolean, counter. Everything else is expressed via parse / variadic on a parsed flag, not a new kind.
- Positional modes:
tuple (fixed-shape, typed per-position) or array (variadic homogeneous).
- Variadic: set
variadic: true for repeated occurrences, or variadic: "," (or any separator) for delimited input. It's a property, not a kind.
Full details in [references/parameters.md](references/parameters.md). Parser specifics (built-ins, custom, async) in [references/parsers.md](references/parsers.md).
Recommended workflow
Single-command CLI
buildCommand → buildApplication(command, config) → run(app, argv, context).
Multi-command CLI
Define commands independently, compose with buildRouteMap, add aliases / defaultCommand where UX benefits. See [references/routing.md](references/routing.md).
Large CLIs — prefer the loader pattern
For commands whose implementation is expensive to import (heavy transitive deps, slow module-level work), use loader instead of inline func. Stricli resolves the loader only when that command is actually invoked, keeping startup fast.
import { buildCommand, numberParser } from "@stricli/core";
export const analyzeCommand = buildCommand({
docs: { brief: "Analyze a report" },
parameters: {
flags: {
depth: {
kind: "parsed",
parse: numberParser,
brief: "Traversal depth",
optional: true,
default: "1",
},
},
},
loader: async () => import("./impl"),
});
Rule of thumb: func for a few-line handler you don't mind parsing at app start; loader when the implementation (or its imports) would dominate cold start for unrelated commands. See [references/routing.md](references/routing.md).
Context and testing
- Runtime context extends
CommandContext. Inject logger / clients / clocks there — not module-level singletons — so tests can swap them.
- Command handlers receive context through
this.
- Test either end-to-end via
run(app, inputs, ctx) with a fake context, or import the command's implementation directly for pure unit tests.
See [references/context.md](references/context.md) and [references/examples.md](references/examples.md) (including Testing Error Paths for parser/missing-arg/enum-error tests).
Auto-complete
@stricli/auto-complete supports bash only. Integrate via the standalone install/uninstall flow plus buildInstallCommand() / buildUninstallCommand() added to your app. Details in [references/auto-complete.md](references/auto-complete.md).
Upstream conventions worth keeping
strict: true in tsconfig.json. Stricli leans on inference — loose mode loses the whole value proposition.
--version appears only when a version integration is registered (1.3.0+) or the legacy versionInfo config key is set (1.2.x; deprecated on 1.3.0).
--helpAll is built-in and surfaces hidden commands and flags.
- Reserved short flags:
-h (help), -H (helpAll), -v (version, when enabled).
- On 1.3.0+, passing the
integrations argument to buildApplication replaces the defaults — re-register help, helpAll, and version or you lose those flags. See [references/integrations.md](references/integrations.md).
- Upstream docs are npm-first; show
pnpm / bun equivalents when the user uses them.
References
- [
routing.md](references/routing.md) — buildCommand, buildRouteMap, buildApplication, run, lazy loaders, aliases, default commands
- [
parameters.md](references/parameters.md) — flag kinds and positional modes
- [
parsers.md](references/parsers.md) — built-in / custom / async parsers
- [
context.md](references/context.md) — CommandContext, custom context, testing, exit codes
- [
integrations.md](references/integrations.md) — integrations, lifecycle hooks, application flags, customizing help/version (1.3.0+)
- [
auto-complete.md](references/auto-complete.md) — bash auto-complete integration
- [
examples.md](references/examples.md) — composite patterns, end-to-end apps, testing error paths
External