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 ${CLAUDE_SKILL_DIR}/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) | Wrap with app-level config (name, version, scanner, integrations) |
| 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 — integrations, lifecycle hooks, and the exported help/version factories arrived in 1.3.0 and don't exist on 1.2.x. Everything else in this skill applies across the 1.x line. Details in 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 } 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, flags: GreetFlags, name: string) {
const message = `Hello, ${name}!`;
this.process.stdout.write(`${flags.shout ? message.toUpperCase() : message}\n`);
}
});
2. Build the application
import { buildApplication } from "@stricli/core";
import { version } from "../package.json";
import { greetCommand } from "./commands/greet";
export const app = buildApplication(greetCommand, {
name: "my-cli",
versionInfo: { currentVersion: version }
});
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 viaparse/variadicon aparsedflag, not a new kind. - Positional modes:
tuple(fixed-shape, typed per-position) orarray(variadic homogeneous). - Variadic: set
variadic: truefor repeated occurrences, orvariadic: ","(or any separator) for delimited input. It's a property, not a kind.
Full details in references/parameters.md. Parser specifics (built-ins, custom, async) in 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.
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.
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 and 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.
Upstream conventions worth keeping
strict: trueintsconfig.json. Stricli leans on inference — loose mode loses the whole value proposition.--versionappears only whenversionInfois configured on the application.--helpAllis built-in and surfaces hidden commands and flags.- Reserved short flags:
-h(help),-H(helpAll),-v(version, when enabled). - On 1.3.0+, passing an
integrationsrecord tobuildApplicationreplaces the defaults — re-registerhelpandversionor you lose those flags. Seereferences/integrations.md. - Upstream docs are npm-first; show
pnpm/bunequivalents when the user uses them.
References
routing.md—buildCommand,buildRouteMap,buildApplication,run, lazy loaders, aliases, default commandsparameters.md— flag kinds and positional modesparsers.md— built-in / custom / async parserscontext.md—CommandContext, custom context, testing, exit codesintegrations.md— integrations, lifecycle hooks, application flags, customizinghelp/version(1.3.0+)auto-complete.md— bash auto-complete integrationexamples.md— composite patterns, end-to-end apps, testing error paths
微信扫一扫