Skip to content

Getting started

@staticbolt/args-parser turns terminal input into typed, validated values. You describe your CLI once, and you get parsing, validation, help output, Markdown docs, and shell completion from that single description.

Features

  • Typed and validated: every option and argument gets its type from a schema, and the parsed result is typed to match.
  • Flexible syntax: --option value, --option=value, short flags, flag coupling (-rf), and -- to stop option parsing.
  • Repeatable options: array and set options accept --item a --item b and --item a,b.
  • Boolean flags: negation with --no-verbose, with or without an explicit value.
  • Subcommands: each one has its own options, arguments, and handler.
  • Generated help and docs: help messages, Markdown files, and completion scripts for Bash, Zsh, Fish, and PowerShell.
  • Schema agnostic: works with any library that implements Standard Schema.
  • Runs anywhere: Node.js, Bun, Deno, and browsers.

Install

Important

You need a validation library alongside the parser. It must implement Standard Schema and let primitive types be optional or carry a default value.

Terminal window
npm install zod @staticbolt/args-parser

Zod is used in every example here. Valibot, Sury, and Decoders work too. See Using schemas.

Your first CLI

import { defineCLI } from "@staticbolt/args-parser";
import * as z from "zod";
const cli = defineCLI({
cliName: "hello",
options: {
/** `--name` or `-n` */
name: {
aliases: ["n"],
schema: z.string().default("world"),
},
},
});
cli.onExecute(({ options }) => {
console.log(`Hello, ${options.name}!`);
});
const result = cli.run(process.argv.slice(2));
if (result.error) {
console.error(result.error.message);
}
Terminal window
hello # Hello, world!
hello --name Sam # Hello, Sam!
hello -n Sam # Hello, Sam!

Three pieces do the work:

Piece What it does
defineCLI Describes the CLI: its name, options, arguments, and subcommands.
onExecute Receives the parsed values once everything validates. You can attach more than one.
run Parses the input, runs the matching handlers, and returns { value } or { error }.

Option names are written as JavaScript identifiers and converted to flags: name becomes --name, and outputDir becomes --output-dir.

Handling errors

run never throws for bad input. Parsing and validation problems come back in result.error:

const result = cli.run(process.argv.slice(2));
if (result.error) {
console.error(result.error.message);
process.exit(1);
}

Errors thrown inside an onExecute handler are a different matter: those propagate to the caller. If a handler is async, use runAsync so its rejection is awaited. run does not wait for handlers.

Where to go next