Command-line interfaces (CLIs) for Node.js and the web

The carnesen/cli package includes runtime JavaScript files (ES2019 + CommonJS) and TypeScript type declarations (3.7+). It has no npm dependencies and is known to work with Node.js 12+ and all modern web browsers.

## Features

- Easy to use: We ❤️ CLIs and want to use them everywhere for everything. This library makes it easy to create beautiful well-behaved CLIs for Node.js and for web browsers.

- Universal: As far as we know, this is the universe's only universal JavaScript CLI framework. Run your browser CLI in a terminal emulator like our online examples or in the browser's built-in terminal, the JavaScript console!

- Intelligent types: This is first and foremost a TypeScript library. All components are intelligently typed, and our built-in parsers ensure that the types are respected at runtime.

- Automatic documentation: Build your CLI with our factories, and we'll automatically generate pretty-good command-line usage out of the box. Even better if you add `description`s and custom `<placeholder>`s to your command objects, we'll automatically fold the text to fit the user's terminal.

- Built-in ANSI decoration: Bring your CLI to life with our built-in ANSI text decoration methods.

- Hidden commands Add "hidden=true" to any argument/command/group, and we'll hide it in the usage documentations. We use this feature for easter eggs and internal/beta commands.

- Automatic autocomplete (Coming soon!): Autocomplete supercharges a CLI. We've implemented automatic autocomplete in the live examples and plan to add this as a feature to the core library for the next release.

## Stability

This library has 99% test coverage and heavy usage by its authors but should be considered 0.x beta software.

## More information

Questions, bugs, feature requests? Please file an issue or submit a pull request on this project's repository on GitHub, and check out our:

- Homepage
- Online documentation
- Example project

## License

MIT © Chris Arnesen

## Usage

Install this library using `npm`:

    npm install @carnesen/cli

Here is a TypeScript Node.js CLI that does some basic arithmetic:

    // src/multiply.ts
    import {
       Cli,
       CliCommand,
       CliNumberArrayArgGroup,
    } from '@carnesen/cli';

    const multiplyCommand = CliCommand({
       name: 'multiply',
       description: 'Multiply numbers and print the result',
       positionalArgGroup: CliNumberArrayArgGroup({
          required: true,
       }),
       action({ positionalValue: numbers }) {
          return numbers.reduce((a, b) => a * b, 1);
       },
    });

    // Export for unit testing
    export const cli = Cli(multiplyCommand);

    // If this module is the entrypoint for this Node.js process
    if (require.main === module) {
       cli.run();
    }

Here's how that Node.js CLI behaves in a terminal:

The only Node.js-specific code is the `if (require.main === module)` block. To instead make a web browser console CLI, replace that with:

    (window as any).multiply = (line: string) => {
       cli.runLine(line);
    };

Here's that CLI in a JavaScript console:

The resolved value of `0` means the command finished successfully. A non-zero exit code means an error occurred. Try it you yourself at cli.carnesen.com! Here's how to open the console in Firefox and Google Chrome.

## Structure

The general structure of a `@carnesen/cli` command line is:

    <command group> <command> \
       <positional-args> \
       --name <args> \
       -- <double-dash-args>

Only `<command>` is required. This section of the documentation describes each of these pieces in more detail.

### Command

A command defines an action `function` or `async function` and together with its command-line arguments. For example, in `cloud users list`, the command is `list`. Some commands don't have any arguments:

    import { CliCommand } from '@carnesen/cli';

    export const listUsersCommand = CliCommand({
       name: 'list',
       async action() {
          // Fetch all users
       }
    })

`CliCommand` is a factory function (not a constructor) that returns an object of shape `ICliCommand`.

Most commands define arguments through the `positionalArgGroup`, `namedArgGroups`, and/or `doubleDashArgGroup` properties as described below.

### Command group

Command groups provide a way to organize the commands in your CLI. For example, in `cloud users list`, `cloud` and `users` are command groups and `list` is a command. Command groups are optional. Organize your CLI to suit your needs and taste:

- `list-cloud-users`: No command groups
- `cloud list-users`: A single command group
- `cloud users list`: A hierarchical command tree

    import { CliCommandGroup } from '@carnesen/cli';
    import { listUsersCommand } from './list-users-command';

    export const usersCommandGroup = CliCommandGroup({
       name: 'users',
       subcommands: [ listUsersCommand ]
    })

    export const rootCommandGroup = CliCommandGroup({
       name: 'cloud',
       subcommands: [ usersCommandGroup ]
    })

`CliCommandGroup` is a factory function (not a constructor) that returns an object of shape `ICliCommandGroup`.

### Positional arguments

A command's `positionalArgGroup` receives all the command-line arguments after the command but before the first argument that starts with `--`. For example, in `cloud users delete carl karen --force`, the positional arguments are `carl` and `karen`. The argument group's parsed value is the `positionalValue` property of the action input:

    import { CliCommand, CliStringArrayArgGroup } from '@carnesen/cli';

    export const deleteCommand = CliCommand({
       name: 'delete',
       positionalArgGroup: CliStringArrayArgGroup({ required: true }),
       async action({ positionalValue: usernames }) {
          // The CliStringArrayArgGroup parser returns an array of strings e.g.
          //   ["carl", "karen"]
          // Delete the users ...
       }
    })

The `CliStringArrayArgGroup` parser throws a `CliUsageError` when `required == true` and no argument is provided. Conversely, if `required == false` and no argument is provided, `positionalValue` is `undefined`.

### Named arguments

A command's `namedArgGroups` receives all the command-line arguments of the form `--name value`. The parsed values are passed into the command's action as a property called `namedValues` of shape `{ name: <value>, ... }`. For example, in `cloud users list` let's add a command-line flag to filter out inactive users:

    import { CliCommand, CliFlagArgGroup } from '@carnesen/cli';

    export const listCommand = CliCommand({
       name: 'list',
       namedArgGroups: {
          active: CliFlagArgGroup(),
       },
       async action({ namedValues: { active } }) {
          // CliFlagArgGroup returns false unless --active was passed
          // Fetch the users ...
       }
    })

### Double-dash arguments

All command-line arguments after a lone `--` are passed to the command's `doubleDashArgGroup`. After the lone `--`, things like `--name` aren't interpreted as argument group separators. This is particularly useful for passing arguments through to another CLI like `do -- git --version`. The argument group's return value is the `doubleDashValue` property of the action's input:

    import { CliCommand, CliStringArrayArgGroup } from '@carnesen/cli';

    export const doCommand = CliCommand({
       name: 'do',
       doubleDashArgGroup: CliStringArrayArgGroup(),
       async action({ doubleDashValue: args }) {
          // Do stuff ...
       }
    })

Go back to top

Generated using TypeDoc and MonoLisa