A thin and strongly typed CLI arguments parser for Node.js.
- Install
# pnpm
pnpm add argue-cli
# yarn
yarn add argue-cli
# npm
npm i argue-cli- Import in your code and use it!
import { read, end, expect, alias, option, readOptions } from 'argue-cli'
/**
* Expect and read one of the commands
*/
const command = expect(
alias('install', 'i'),
'remove'
)
let options = {}
if (command === 'install') {
/**
* Read passed options
*/
options = readOptions(
option(alias('save', 'S'), Boolean),
option(alias('saveDev', 'save-dev', 'D'), Boolean),
option('workspace', String)
)
}
/**
* Read next argument
*/
const packageName = read()
/**
* Expect end of the arguments
*/
end()
/* ... */Argue reads arguments sequentially from an internal state, which is initialized with process.argv. Every call consumes the arguments it reads, so you describe your CLI step by step: expect a command, read its options, read positional arguments, and finally assert the end.
Tip
The internal state can be controlled manually with setArgs(...args) and resetArgs() — handy in tests.
function read(): stringReads the next argument and returns it. Throws an error if there are no arguments left.
// my-cli sort-imports
const fileName = read() // 'sort-imports'function rest(): string[]Reads all remaining arguments and returns them. Returns an empty array if there are none — unlike read, it never throws.
// my-cli format a.js b.js c.js
expect('format')
const files = rest() // ['a.js', 'b.js', 'c.js']
end() // always passes after rest()function end(): voidAsserts that all arguments were consumed. Throws an error if there are any arguments left — useful to catch typos and unexpected input.
// my-cli install --sav
expect('install')
readOptions(
option(alias('save', 'S'), Boolean)
)
end() // throws: Unexpected argument "--sav"function expect(...argRefs: ArgRef[]): stringExpects the next argument to be one of the given ones and returns the matched name. If an alias matches, the main name is returned. Throws an error on any other input.
The return type is inferred as a union of the given names:
// my-cli i
const command = expect(alias('install', 'i'), 'remove')
// typeof command: 'install' | 'remove'
// command === 'install'function alias(name: string, ...aliases: string[]): AliasArgRefDescribes an argument that has alternative names. Use it anywhere an argument name is expected — in expect and option.
alias('install', 'i')
alias('saveDev', 'save-dev', 'D')function autocase(argRef: ArgRef): ArgRefDescribes an argument that matches both camelCase and kebab-case forms. The name can be given in either form — the twin form is added as an alias. Aliases longer than one character are autocased too. Use it anywhere an argument name is expected.
autocase('firstRelease')
// --firstRelease and --first-release are both accepted
autocase(alias('save-dev', 'D'))
// --save-dev, --saveDev and -Dfunction option(argRef: ArgRef, type: PrimitiveConstructor): OptionReaderDescribes an option with a value of the given type, to be read by readOptions:
String— takes the next argument as a value:--workspace packages/appNumber— parses the next argument as a number:--port 8080Boolean— a flag without a value,truewhen present:--verboseArray— splits the next argument by commas; repeated options are merged:--plugins eslint,swc --plugins tsc→['eslint', 'swc', 'tsc']
function flag(argRef: ArgRef): OptionReaderDescribes a boolean flag with --no-* negation support, to be read by readOptions: --verbose sets true, --no-verbose sets false. Use option(argRef, Boolean) when negation is not wanted.
const options = readOptions(
flag(autocase('firstRelease'))
)
// --first-release → { firstRelease: true }
// --no-first-release, --no-firstRelease → { firstRelease: false }function readOptions(...optionReaders: OptionReader[]): OptionResultScans the arguments and reads all described options. Both --option and -o prefixes are accepted, and a value can be passed either as the next argument or inline: --workspace packages/app and --workspace=packages/app are equivalent. Arguments that don't match any described option are left untouched, so you can continue reading them afterwards.
The result is a strongly typed object, where every property is optional — an option simply may not be passed:
// my-cli --save-dev --workspace packages/app my-package
const options = readOptions(
option(alias('saveDev', 'save-dev', 'D'), Boolean),
option('workspace', String)
)
// typeof options: { saveDev?: boolean, workspace?: string }
// options: { saveDev: true, workspace: 'packages/app' }
const packageName = read() // 'my-package'In the API section types are described in a simplified way. A detailed example of the inferred types you can see in type tests.