Back to skills
extension
Category: Development & EngineeringNo API key required

stricli

Build type-safe CLI applications with Stricli. Use when creating TypeScript CLIs with typed flags/positional args, multi-command routing, or automatic help generation. Stricli catches parameter errors at compile time. Use this whenever the user mentions CLI frameworks, command-line tools, argument parsing, or typed commands in TypeScript.

personAuthor: jakexiaohubgithub

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

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. Parser specifics (built-ins, custom, async) in references/parsers.md.

Recommended workflow

Single-command CLI

buildCommandbuildApplication(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: 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.
  • Upstream docs are npm-first; show pnpm / bun equivalents when the user uses them.

References

  • routing.mdbuildCommand, buildRouteMap, buildApplication, run, lazy loaders, aliases, default commands
  • parameters.md — flag kinds and positional modes
  • parsers.md — built-in / custom / async parsers
  • context.mdCommandContext, custom context, testing, exit codes
  • integrations.md — integrations, lifecycle hooks, application flags, customizing help/version (1.3.0+)
  • auto-complete.md — bash auto-complete integration
  • examples.md — composite patterns, end-to-end apps, testing error paths

External