Socket
Book a DemoInstallSign in
Socket

@thi.ng/args

Package Overview
Dependencies
Maintainers
1
Versions
188
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@thi.ng/args

Declarative, functional CLI argument/options parser, app framework, arg value coercions, multi/sub-commands, usage generation, error handling etc.

latest
Source
npmnpm
Version
3.1.0
Version published
Maintainers
1
Created
Source

@thi.ng/args

npm version npm downloads Mastodon Follow

[!NOTE] This is one of 210 standalone projects, maintained as part of the @thi.ng/umbrella monorepo and anti-framework.

🚀 Please help me to work full-time on these projects by sponsoring me on GitHub. Thank you! ❤️

About

Declarative, functional CLI argument/options parser, app framework, arg value coercions, multi/sub-commands, usage generation, error handling etc..

Built-in argument types

The parser includes built-in support for the following argument types (of course custom arg types are supported too):

Argument typeMultipleExampleResult
Flag--force, -fforce: true
String--foo barfoo: "bar"
Float/int/hex--bg ff997fbg: 16750975
Enum--type pngtype: "png"
KV pairs--define foo=bardefine: { foo: "bar" }
KV multi pairs-D foo=bar -D foo=bazdefine: { foo: ["bar", "baz"] }
JSON--config '{"foo": [23]}'config: { foo: [23] }
Fixed size tuple--size 640x480size: { value: [640, 480] }

If multiple values/repetitions are allowed for an argument, the values will be collected into an array (apart from KV pairs, which will yield an object). Furthermore, for multi-args and tuples, an optional delimiter can be specified to extract individual values, e.g. -a 1,2,3 equals -a 1 -a 2 -a 3

Re-usable argument presets

The following commonly used arguments are available as predefined presets:

Higher order, configurable preset specs:

To use these presets, simply import and splice them into your own arg definitions (see code examples below).

CLI app framework

The package provides a simple framework to conveniently define single and multi-command applications in a declarative and modular manner. Such apps are defined via command specs and other configuration options. The framework then handles all argument parsing, validation, usage display and delegation to sub-commands.

The wrapper defines a user-customizable command context with all important information which is passed to the commands and also includes a logger (writing to stderr). Other help/usage and error output also respects the NO_COLOR convention.

A fully documented code example is further below.

For some publicly available production uses, please see the related packages section in this readme.

Status

STABLE - used in production

Search or submit any issues for this package

Breaking changes in 3.0.0

  • Required arguments are now to be specified using either required: true or given a default value
  • All factory functions now only accept a single arg spec, with any type-specific options moved into the spec, for example:
    • old: oneOf(["a","b","c"], {...})
    • new: oneOf({ opts: ["a","b","c"], ...})
    • old: tuple(identity, 3, {...})
    • new: tuple({ size: 3, coerce: identity, ...})
  • Where applicable, delimiters are now to be included in the arg spec (rather than given as separate function arg)

Installation

yarn add @thi.ng/args

ESM import:

import * as args from "@thi.ng/args";

For Node.js REPL:

const args = await import("@thi.ng/args");

Package sizes (brotli'd, pre-treeshake): ESM: 3.47 KB

Dependencies

Note: @thi.ng/api is in most cases a type-only import (not used at runtime)

Projects using this package

API

Generated API docs

Basic usage

import {
    ARG_VERBOSE,
    flag,
    hex,
    json,
    kvPairs,
    oneOf,
    parse,
    size,
    string,
    vec,
    type Args,
    type KVDict,
    type Tuple,
} from "@thi.ng/args";

type ImgType = "png" | "jpg" | "gif" | "tiff";

// CLI args will be validated against this interface
interface TestArgs {
    configPath?: string;
    force?: boolean;
    bg: number;
    type: ImgType;
    size?: Tuple<number>;
    pos?: Tuple<number>;
    xtra?: { a: number; b: string };
    define?: KVDict;
    verbose: boolean;
}

// arg specifications
const specs: Args<TestArgs> = {
    // re-use predefined preset (see readme section above)
    ...ARG_VERBOSE,

    // string arg
    configPath: string({
        alias: "c",
        hint: "PATH",
        desc: "Config file path (CLI args always take precedence over those settings)",
    }),

    // boolean flag (default: false)
    force: flag({
        alias: "f",
        desc: "Force operation",
        // side effect and/or validation
        // parsing only continues if function returns true
        fn: (_) => (console.log("force mode enabled"), true),
    }),

    // hex int value
    bg: hex({
        desc: "Background color",
        // mandatory args require a `default` value and/or `required: true`
        default: 0xffffff,
        defaultHint: "ffffff",
    }),

    // enum value (mandatory)
    type: oneOf({
        alias: "t",
        desc: "Image type",
        opts: ["png", "jpg", "gif", "tiff"],
        // mandatory args require a `default` value and/or `required: true`
        required: true,
    }),

    // fixed size numeric tuple w/ `x` as delimiter
    size: size({ size: 2, hint: "WxH", desc: "Target size", delim: "x" }),
    // syntax sugar for:
    // size: tuple(2, coerceInt, { hint: "WxH", desc: "Target size" }, "x"),

    // another version for tuples of floating point values
    pos: vec({ size: 2, desc: "Lat/Lon coordinates", hint: "LAT,LON" }),
    // syntax sugar for:
    // pos: tuple(2, coerceFloat, { desc: "Lat/Lon" }),

    // JSON string arg
    xtra: json({
        alias: "x",
        desc: "Extra options",
        group: "extra",
    }),

    // key-value pairs parsed into an object (multiple allowed)
    define: kvPairs({
        alias: "D",
        desc: "Define dict entry",
        group: "extra"
    }),
};

try {
    // parse argv w/ above argument specs & default options
    // (by default usage is shown if error occurs)
    const args = parse(specs, process.argv, {
        usageOpts: {
            prefix: `
 █ █   █           │
██ █               │
 █ █ █ █   █ █ █ █ │ @thi.ng/args demo app
 █ █ █ █ █ █ █ █ █ │ v1.0.0
                 █ │
               █ █ │\n\n`,
            showGroupNames: true,
            groups: ["flags", "main", "extra"],
            lineWidth: 72,
        },
    });
    console.log(args);
} catch (_) {}

Invoking this as CLI script without arguments will generate an error about a missing --type arg and output the generated usage info (by default with ANSI color highlights):

illegal argument(s): missing arg: --type

 █ █   █           │
██ █               │
 █ █ █ █   █ █ █ █ │ @thi.ng/args demo app
 █ █ █ █ █ █ █ █ █ │ v1.0.0
                 █ │
               █ █ │

Flags:

-f, --force                     Force operation
-v, --verbose                   Display extra information

Main:

--bg HEX                        Background color (default: "ffffff")
-c PATH, --config-path PATH     Config file path (CLI args always take
                                precedence over those settings)
--pos N,N                       Lat/Lon coordinates
--size WxH                      Target size
-t ID, --type ID                [required] Image type: "png", "jpg",
                                "gif", "tiff"

Extra:

-D key=val, --define key=val    [multiple] Define dict entry
-x JSON, --xtra JSON            Extra options

Generate & display help

Usage information can be generated via usage() and is automatically triggered via the special --help option (configurable, see ParseOpts).

Each arg can be associated with arbitrary group IDs, which are then used to segment usage output. By default, flag() args are assigned to a "flags" group, all others to "main". The default output order too is ["flags", "main"], but can be configured via a group option given an arg spec or factory function.

By default, ANSI colors are used to format the result string of usage(), but can be disabled (see UsageOpts).

Parsing, value coercions & side effects

The below invocation demonstrates how the various argument types are handled & represented in the result. Parsing stops with the first non-argument value (here sourcefile.png) and the remaining values are made available via rest in the result object.

bun index.ts \
    -f -t png --bg ff00ff --size 640x480 \
    -D author=toxi -D date=2018-03-24 \
    --xtra '{"foo": [23]}' \
    sourcefile.png

# force mode enabled
# {
#   result: {
#     force: true,
#     type: 'png',
#     bg: 16711935,
#     size: Tuple { value: [640, 480] }
#     define: { author: 'toxi', date: '2018-03-24' },
#     xtra: { foo: [23] },
#     verbose: false,
#   },
#   index: 15,
#   rest: [ 'sourcefile.png' ],
#   done: false
# }

Declarative, multi-command CLI application

The following example defines a CLI app with two sub-commands: hello and list. Each command has its own options, in addition to common/shared ones. Each command is defined in a modular manner (usually in its own source file). All aspects like arg parsing, validation, and command selection/delegation is handled by the cliApp() wrapper.

import {
    ARG_VERBOSE,
    cliApp,
    configureLogLevel,
    int,
    string,
    type Command,
    type CommandCtx,
} from "@thi.ng/args";
import { files } from "@thi.ng/file-io";

// common command opts
interface CommonOpts {
    verbose: boolean;
}

// custom command context
interface AppCtx<T extends CommonOpts> extends CommandCtx<T, CommonOpts> {
    // plus any custom additions here...
}

// command-specific options
interface HelloOpts extends CommonOpts {
    name: string;
}

// command definition
const HELLO: Command<HelloOpts, CommonOpts> = {
    // brief description (for `--help` usage)
    desc: "Print out a greeting",
    // command specific options (arguments)
    // (will be combined with common opts)
    opts: {
        name: string({
            alias: "n",
            desc: "Name for greeting",
            required: true,
        }),
    },
    // this command does not accept any inputs
    inputs: 0,
    // command implementation
    fn: async (ctx) => {
        // log message only shown if `--verbose`/`-v` given
        ctx.logger.debug("opts", ctx.opts);
        console.log(`Hello, ${ctx.opts.name}!`);
    },
};

// command-specific options
interface ListFilesOpts extends CommonOpts {
    depth: number;
    filter?: string;
}

// command definition
const LIST_FILES: Command<ListFilesOpts, CommonOpts> = {
    // brief description (for `--help` usage)
    desc: "List files in given dir",
    // command specific options
    opts: {
        filter: string({
            alias: "f",
            desc: "Filter regexp",
        }),
        depth: int({
            alias: "d",
            desc: "Recursion depth (directory levels)",
            default: Infinity,
        }),
    },
    // this command requires exactly 1 input
    // (if supporting a range, use `[min, max]`)
    inputs: 1,
    // command implementation
    fn: async (ctx) => {
        for (let f of files(ctx.inputs[0], ctx.opts.filter, ctx.opts.depth)) {
            console.log(f);
        }
    },
};

// define & start CLI app
cliApp<CommonOpts, AppCtx<any>>({
    // app name
    name: "example",
    // process.argv index from which to start parsing from
    start: 2,
    // list common command opts here
    opts: {
        // re-use verbose flag arg spec preset
        ...ARG_VERBOSE,
    },
    // list of commands
    commands: {
        hello: HELLO,
        list: LIST_FILES,
    },
    // set to true if only a single command
    // in this case the command name would NOT be required/expected
    // single: true,

    // usage opts
    usage: {
        // prefix/header string
        prefix: `Example app
===================================
Usage: example [opts] [inputs]\n`,
        // configure column width for param usage info
        paramWidth: 24,
        lineWidth: 80,
    },

    // context initialization/augmentation
    // (called before arg parsing commences)
    ctx: async (ctx) => {
        configureLogLevel(ctx.logger, ctx.opts.verbose);
        return ctx;
    },
});

Example usage (here using bun to launch the above CLI app, though the usage info is written to assume an example launcher/wrapper):

bun readme-cliapp.ts

# Example app
# ===================================
# Usage: example [opts] [inputs]
#
# Available commands:
#
# hello : Print out a greeting
# list  : List files in given dir
#
# -v, --verbose           Display extra information
# displaying help for a sub-command
bun readme-cliapp.ts hello --help

# Example app
# ===================================
# Usage: example [opts] [inputs]
#
# Current command:
#
# hello : Print out a greeting
#
# -v, --verbose           Display extra information
#
# -n STR, --name STR      [required] Name for greeting
# invoking `hello` sub-command (with verbose flag)
bun readme-cliapp.ts hello --name thi.ng -v
# [DEBUG] example: opts {"name":"thi.ng","verbose":true}
# Hello, thi.ng!
# invoking `list` sub-command
bun readme-cliapp.ts list -d 2 -f '.js' .
# ./dev/api.js
# ./dev/runtime.js
# ./dev/test/main.js
# ./index.js
# missing arg error
bun readme-cliapp.ts hello
# illegal argument(s): missing arg: --name
#
# (...additional usage output omitted for brevity)

Authors

If this project contributes to an academic publication, please cite it as:

@misc{thing-args,
  title = "@thi.ng/args",
  author = "Karsten Schmidt",
  note = "https://thi.ng/args",
  year = 2018
}

License

© 2018 - 2025 Karsten Schmidt // Apache License 2.0

Keywords

ansi

FAQs

Package last updated on 26 Sep 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts