johnieskills

Builds type-safe command-line tools with cleye.

Build type-safe TypeScript CLIs with the cleye argv parser. Use when the project already depends on cleye or the user names cleye — authoring a cli() call, adding typed flags/parameters, defining subcommands with command(), handling parsed output in a callback, using cleye/formats helpers (oneOf/commaList/integer/float/range/url), customizing --help via help.render, or wiring strictFlags/booleanFlagNegation. For Bloomberg's Stricli framework use the stricli skill instead; skip for commander/yargs/oclif/minimist and for generic "which CLI framework should I use?" questions.

$ npx skills add johnie/skills --skill cleye
skills/cleye/SKILL.mdGitHub

cleye CLI Tool

cleye is an intuitive argv-parsing tool for Node.js CLIs. It turns a declarative options object into strongly-typed parameters and flags, auto-generates --help and --version documentation, and supports nested commands. Flag parsing is powered by type-flag.

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 Bloomberg's Stricli — different library, non-transferable API. Use the stricli skill.
  • Generic "which CLI framework should I use?" — that's a design conversation, not a cleye question.
  • Non-TypeScript CLIs — cleye works in plain JS, but its core value is compile-time type inference of flags and parameters.
  • Runtime debugging of an installed CLI (not developing it) — use shell/debugging tooling.

Core API surface

cleye's API is intentionally small. 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.

This skill targets the 2.x line (cleye@latest). Check the installed version before reaching for newer options: cleye/formats needs ≥2.6 and booleanFlagNegation ≥2.3. A 3.0.0-beta.1 is published on the beta dist-tag (npm i cleye@beta) with breaking changes — if the project is on the 3.x beta, read references/v3-migration.md before trusting the shapes below.

Entry point Purpose
cli(options, callback?, argvs?) Parse argv from a declarative options object; returns ParsedArgv
command(options, callback?) Define a subcommand; same options as cli plus name/alias
cleye/formats Tree-shakable type-function helpers (oneOf, commaList, integer, float, range, url)
Flags / Renderers / TypeFlag Exported types for flag objects, help renderers, and portable flag declarations

cli() returns a ParsedArgv:

type ParsedArgv = {
  _: string[] & Parameters; // positional args, named in camelCase
  flags: { [flagName: string]: InferredType };
  unknownFlags: { [flagName: string]: (string | boolean)[] };
  command?: string; // present when commands are registered
  showVersion: () => void;
  showHelp: (options?: HelpOptions) => void;
};

Installation

Upstream docs are npm-first. Stay agnostic to the user's package manager — pnpm and bun work equally well.

npm i cleye
# pnpm add cleye / bun add cleye work the same way

Quick start: single-command CLI

// greet.ts
import { cli } from "cleye";

const argv = cli({
  name: "greet.js",
  parameters: [
    "<first name>", // required
    "[last name]", // optional
  ],
  flags: {
    time: {
      type: String,
      description: "Time of day to greet (morning or evening)",
      default: "morning",
    },
  },
});

const name = [argv._.firstName, argv._.lastName].filter(Boolean).join(" ");

if (argv.flags.time === "morning") {
  console.log(`Good morning ${name}!`);
} else {
  console.log(`Good evening ${name}!`);
}

Run it:

$ node greet.js John Doe --time evening
Good evening John Doe!

Generated help (--help is handled automatically):

$ node greet.js --help

greet.js

Usage:
  greet.js [flags...] <first name> [last name]

Flags:
  -h, --help                 Show help
      --time <string>        Time of day to greet (morning or evening) (default: "morning")

Parameter & flag model

  • Parameters (positionals) use string-format markers: <required>, [optional], <spread...> / [spread...]. Required must precede optional; spread must be last. Access in camelCase on argv._. Full details in references/parameters.md.
  • Flags map a camelCase key to a type function (String, Number, Boolean, custom) or a descriptor object (type, alias, default, description, placeholder). Wrap the type in an array ([Number]) to accept multiple values. Full details in references/flags.md.
  • Validation is done with type functions that throw on bad input. Use the ready-made helpers in references/formats.md (cleye/formats) or write your own.

Commands

Define subcommands with command() and register them in the commands array of cli(). The active command is reported on argv.command, which narrows the parsed types. For larger CLIs, give each command its own file and handle its output in a callback. See references/commands.md.

import { cli, command } from "cleye";

const argv = cli({
  name: "npm",
  commands: [
    command({
      name: "install",
      parameters: ["<package name>"],
      flags: { saveDev: Boolean },
    }),
  ],
});
// $ npm install lodash  →  argv.command === "install", argv._.packageName === "lodash"

Help & version

--help/-h is automatic (disable with help: false, print manually with argv.showHelp()), --version is automatic only when version is set, and help.render(nodes, renderers) plus usage/examples/description shape the document. Details in references/help.md.

Single-command CLI

cli({ name, parameters, flags }) → read argv._ / argv.flags. Pass a callback as the second argument when you prefer to keep handling co-located, and return a Promise from it to await cli(...).

Multi-command CLI

Define each command with command() (ideally one per file with its own callback), register them in commands, then branch on argv.command if you handle output centrally. See references/commands.md.

Conventions worth keeping

  • Define flag keys in camelCase; cleye automatically parses the kebab-case equivalent (--saveDev ⇄ --save-dev).
  • Use strict: true in tsconfig.json — cleye's flag/parameter inference is the whole point, and loose mode weakens it.
  • Type functions throw on invalid input; never return Error values.
  • Reserve -h/--help and --version; cleye handles them unless told otherwise.
  • Enable strictFlags to reject unknown flags (with did-you-mean suggestions); otherwise they land in argv.unknownFlags.
  • Commands inherit strictFlags and booleanFlagNegation from the parent cli() but can override them.

References

  • parameters.md — positional args: required/optional/spread, ordering, argv._, end-of-flags --
  • flags.md — flag type functions, arrays, descriptors, delimiters, boolean negation/inversion, strict flags, unknown flags
  • formats.md — cleye/formats helpers and custom type functions
  • commands.md — command(), registration, type narrowing, callbacks, aliases, option inheritance
  • help.md — auto docs, help options, render customization, responsive tables
  • examples.md — composite end-to-end patterns (multi-command, async, validation, ignoreArgv, manual showHelp)
  • v3-migration.md — 3.x beta breaking changes: commands record, callback/return changes, PascalCase formats, atom-based help, group(), strictCommands, CleyeExit

External