CLI tool
privatenumber/cleye avatar
privatenumber/cleye

cleye: typed argv parsing and generated help for Node command line tools

👁‍🗨 Strongly typed CLI development for Node.js

694 stars17 forksTypeScriptMIT

At a glance

What is it?
cleye is a small TypeScript library that turns process.argv into typed parameters and flags for a Node.js script and writes the --help documentation from the same description. It suits a developer building a CLI with subcommands and typed options, and it is not a replacement for a process supervisor or a script runner.
Who is it for?
Adopt cleye if you are writing a Node.js or TypeScript command line tool and want typed positional parameters, a defined flag set, and help output generated from the same declaration, since the two cannot drift apart. Do not adopt it for a script with no user interface of its own, or on the expectation that it will handle unknown flags gracefully, because that behaviour is decided by the type-flag parser underneath.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 36 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One declaration produces the types and the help text

cleye is described as the intuitive command-line interface development tool, and its feature list is short: a minimal API surface, powerful flag parsing, strongly typed parameters and flags, command support, and help documentation generation that is customizable too.

The mechanism is a single call. You describe your CLI with a name, a list of parameters and a set of flags, and the library hands back an object whose properties are typed, and a help page that is generated from that same description. The documentation's own words are that it handles argv parsing to give you strongly typed parameters plus flags and generates --help documentation based on the provided information.

That coupling is the useful part. The help text and the type definitions are not two things you maintain, so the flag you describe cannot be missing from the help output, and the help output cannot advertise a flag the parser does not know.

The project publishes a small set of reimplementations as reference material, and they are worth reading as documentation. The examples directory contains a working greet script, a reimplementation of the npm install command line, a reimplementation of the TypeScript tsc command line, a reimplementation of snap-tweet, and a reimplementation of pkg-size. There is also an online demo built on StackBlitz so you can see the API without installing anything.

Your first script is one import and one call

Installation is a single npm command:

bash
npm i cleye

The script itself declares the parameters and flags it accepts. This is the greet example, trimmed to the declaration:

ts
import { cli } from 'cleye'

// Parse argv
const argv = cli({
    name: 'greet.js',

    // Define parameters
    parameters: [
        '<first name>', // First name is required
        '[last name]' // Last name is optional
    ],

The help output is generated from that description, and the documentation shows what it looks like:

sh
$ 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")

And running the script with a value for the flag gives the expected result:

sh
$ node greet.js John Doe --time evening

Good evening John Doe!

The script then reads the parsed values, joining the two parameters into a name and branching on the flag. The documentation also notes that in development, type hints are provided on the parsed flags and parameters, which is what makes the library worth using in a TypeScript project rather than a JavaScript one.

Parameters are declared with brackets, and the order is constrained

Arguments are values passed into the script that are not associated with flags, and they are accessible from the _ array property of the returned object. Parameters are the names that map against those argument values: parameters are variable names, arguments are the values associated with the variables.

The declaration format is a convention you can see at a glance in the source:

- Required parameters are indicated by angle brackets, as in <parameter name>. - Optional parameters are indicated by square brackets, as in [parameter name]. - Spread parameters are indicated by a ... suffix, as in <parameter name...> or [parameter name...].

There are two ordering rules, and they are the kind of detail that bites when you do not know them. Required parameters cannot come after optional parameters, and spread parameters must be last.

Once declared, parameters are read in camelCase on the _ property, which is the part that makes the code readable. Given a declaration of <required parameter>, [optional parameter] and [optional spread...], a run of my-script a b c d gives requiredParameter as a string, optionalParameter as a string or undefined, and optionalSpread as a string array. The reason to declare parameters at all, rather than indexing into an array, is that it gives you readable code, enforced validation, and help documentation, all from the same list.

End-of-flags gives you the argument convention npm relies on

End-of-flags, written --, also known as end-of-options, allows users to pass in a subset of arguments. The point is to let a user hand over arguments that should be parsed separately from the rest, or that look like flags themselves.

The example given is npm run:

sh
$ npm run <script> -- <script arguments>

The -- indicates that all arguments afterwards should be passed into the script rather than into npm.

In cleye, everything after the end-of-flags marker is collected under argv._['--']. There is also a second way to use it, which is to put -- inside the parameters array itself, so the end-of-flags arguments are parsed as named parameters:

ts
const argv = cli({
    name: 'npm-run',
    parameters: [
        '<script>',
        '--',
        '[arguments...]'
    ]
})

With a run of npm-run echo -- hello world, argv._.script is the string echo and argv._.arguments is the string array of hello and world. The second form is the better one to reach for when the trailing arguments are meaningful, since it gives them a name and a type rather than leaving them in a bucket.

The flag parser underneath accepts more delimiters than you would guess

cleye's flag parsing is powered by type-flag, and the documented parsing features are worth reading before you write a user-facing help page that promises a particular syntax.

Flags are key-value pairs passed in the format --flag-name <value>, and the parser accepts several delimiters: --flag value, --flag=value, --flag:value and --flag.value. Combined aliases work too, so -abcd 2 is read as -a -b -c -d 2.

Beyond that there are array and custom types, end of flags to stop parsing, and handling for unknown flags, which the documentation describes as unexpected, meaning an unknown flag is surfaced rather than silently ignored.

The dependency stack is visible in the package manifest and is short: type-flag at ^4.5.2 does the parsing, ansis at 4.2.0 handles the coloured output, and terminal-columns at ^2.0.0 is used for the help layout. That division is worth understanding, because it tells you which behaviours belong to cleye and which belong to type-flag.

The two are maintained by the same author, privatenumber, and the type-flag repository is the one to read when a flag behaves in a way the cleye documentation does not explain.

The package ships ESM and CommonJS builds plus a skills directory

The manifest is worth a look if you are integrating cleye into a larger tool, because it makes several choices explicit.

The package is an ES module by type, with sideEffects set to false, and it publishes dual entry points: main points at ./dist/index.cjs for CommonJS, module at ./dist/index.mjs for ESM, and there are matching declaration files for both. The exports map exposes the root entry and a second entry, ./formats, each with require and import conditions and their own type declarations. There is also an internal imports map using the #cleye and #cleye/formats specifiers, which resolve to the TypeScript sources during development and to the built files otherwise, so the test suite can run against the source without a build step.

Two details stand out. The version field reads 0.0.0-semantic-release, which is the placeholder used when the release tooling writes the version, so the published version comes from the release pipeline rather than from a hand edited field. And the files array lists only dist and skills, meaning the skills directory is shipped as part of the package, with a prepare script of skills-npm handling it.

The development scripts are also complete: the build is pkgroll --minify, linting runs lintroll with a cache and the git flag, type checking is tsc, and the tests run with node tests/index.ts, with a dev script that watches the same file under the development condition. The package manager is pinned to [email protected].

Version 3 is in beta while 2.7 is the current release

The release history shows the project in the middle of a major version change. v2.7.0 was published on 2026-09-19 at 01:12, and v3.0.0-beta.2 was published the same day at 01:44, with v3.0.0-beta.1 on 2026-07-12. The last push to the repository was on 2026-09-19.

So the 3.0 line exists as a beta while 2.7.0 is what most installs will resolve to. That is the practical decision to make before you adopt the library: whether to start on the current release or to pin a beta in exchange for whatever 3.0 changes. The documentation text in the repository does not label the 3.0 release as a beta in the prose, but the tag names do, and a beta tag is a beta tag.

The licence is MIT, with the author listed as Hiroki Osame, and the repository links to a sponsors page and a separate development repository for discussion.

For a small dependency like this the cost of adoption is mostly the risk of a major version arriving while you are on the 2.x line, so reading the changes between 2.7 and 3.0 before you upgrade is worth the five minutes it takes.

Editorial conclusion

Adopt cleye if you are writing a Node.js or TypeScript command line tool and want typed positional parameters, a defined flag set, and help output generated from the same declaration, since the two cannot drift apart. Do not adopt it for a script with no user interface of its own, or on the expectation that it will handle unknown flags gracefully, because that behaviour is decided by the type-flag parser underneath. Verify first whether the 2.x line or the 3.0 beta fits your project, since v2.7.0 and v3.0.0-beta.2 were both published on 2026-09-19, and check that your flags map cleanly onto the delimiter forms the parser supports.

Frequently asked questions

How do I install cleye?

Run npm i cleye. The package is published as cleye, is an ES module with a CommonJS build alongside it, and is developed with pnpm, which the manifest pins at [email protected].

How do I declare required and optional parameters in cleye?

Required parameters use angle brackets such as <first name>, optional parameters use square brackets such as [last name], and spread parameters use a ... suffix. Required parameters cannot follow optional ones, spread parameters must be last, and the parsed values are read in camelCase from argv._.

How do I handle end-of-flags arguments in cleye?

Everything after -- is collected under argv._['--']. You can also declare -- inside the parameters array, as in ['<script>', '--', '[arguments...]'], so the trailing arguments come back as a named parameter with a type.

Which flag syntaxes does cleye support?

Flag parsing is powered by type-flag and supports --flag value, --flag=value, --flag:value and --flag.value delimiters, combined aliases such as -abcd 2, array and custom types, end of flags, and reporting of unknown flags.

Does cleye generate the help text automatically?

Yes. The help page is generated from the same description you use to define parameters and flags, so it can be viewed with the --help flag, and the documentation describes the generation as customizable.

Official sources

  1. Issues
  2. License: MIT
  3. privatenumber/cleye on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/privatenumber-cleye.svg)](https://hysenlabs.com/projects/privatenumber-cleye)

Was this useful?