npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@jondotsoy/flags

v3.1.3

Published

A Javascript arguments processor

Downloads

111

Readme

> flags{args}

A powerful and type-safe JavaScript/TypeScript command-line arguments parser with a fluent builder API.

Features

  • 🔥 Type-safe: Full TypeScript support with type inference
  • 🎯 Fluent API: Chainable builder pattern for intuitive flag definitions
  • 📋 Multiple formats: Support for --flag=value, --flag value, and -f syntax
  • 🔗 Combined flags: Automatic expansion of combined short flags like -abc-a -b -c
  • 🔧 Built-in types: Boolean, string, strings array, number, and key-value handlers
  • 📚 Commands: Support for subcommands and rest arguments
  • 📍 Positional arguments: Ordered argument parsing
  • 🆘 Help generation: Automatic help message generation
  • Lightweight: Zero dependencies, minimal overhead

Installation

npm install @jondotsoy/flags
# or
yarn add @jondotsoy/flags
# or
bun add @jondotsoy/flags

Quick Start

import { flags, flag, command, argument } from "@jondotsoy/flags";

// Define your schema with fluent builder API
const parser = flags({
  verbose: flag("--verbose", "-v").boolean().describe("Enable verbose output"),
  name: flag("--name", "-n").string().describe("Set application name"),
  port: flag("--port", "-p").number().default(3000).describe("Server port"),
  help: flag("--help", "-h").boolean().describe("Show help message"),
  serve: command("serve").restArgs().describe("Start the server"),
  input: argument().string().required().describe("Input file"),
})
  .program("mycli")
  .describe("My awesome CLI tool");

// Parse command line arguments
const args = ["input.txt", "--name=myapp", "-v", "--port", "8080"];

try {
  const options = parser.parse(args);

  // Handle help flag
  if (options.help) {
    console.log(parser.helpMessage());
    process.exit(0);
  }

  console.log("Parsed options:", options);
  // Output: { input: "input.txt", name: "myapp", verbose: true, port: 8080, help: false, serve: null }
} catch (error) {
  console.error(`Error: ${error.message}`);
  console.log(parser.helpMessage());
  process.exit(1);
}

Core Concepts

The library uses a fluent builder API with three main building blocks:

  • Flags: Named options like --verbose or -v
  • Commands: Subcommands like build or serve
  • Arguments: Positional arguments like file paths

API Reference

flags(schema)

Creates a parser with the given schema. Returns a FlagsParser instance.

const parser = flags({
  verbose: flag("--verbose", "-v").boolean(),
  port: flag("--port").number().default(3000),
});

Parser Methods

.program(name: string)

Sets the program name for help messages.

parser.program("mycli");

.describe(description: string)

Sets the program description for help messages.

parser.describe("A powerful CLI tool");

.combineShortFlags()

Enables automatic expansion of combined short boolean flags (e.g., -abc-a -b -c).

const parser = flags({
  all: flag("-a").boolean(),
  long: flag("-l").boolean(),
  human: flag("-h").boolean(),
}).combineShortFlags();

parser.parse(["-alh"]); // { all: true, long: true, human: true }

.parse(args: string[])

Parses the arguments and returns the result object.

const options = parser.parse(process.argv.slice(2));

Throws:

  • UnexpectedArgumentError - When an unknown argument is encountered
  • RequiredFlagMissingError - When a required flag is missing
  • RequiredArgumentMissingError - When a required argument is missing

.helpMessage()

Generates and returns a formatted help message.

console.log(parser.helpMessage());

Builder Functions

flag(...names: string[])

Creates a flag builder for named options. Supports multiple aliases.

// Single name
flag("--port"); // Matches --port only

// Multiple names (aliases)
flag("--verbose", "-v"); // Matches --verbose or -v
flag("-t", "--tty"); // Matches -t or --tty

// Multiple long and short names
flag("--help", "-h", "-?"); // Matches --help, -h, or -?

Examples:

const parser = flags({
  // Long form only
  version: flag("--version").boolean(),

  // Short and long form
  verbose: flag("-v", "--verbose").boolean(),

  // Multiple aliases
  help: flag("-h", "--help", "-?").boolean(),

  // Docker-style: short first, then long
  tty: flag("-t", "--tty").boolean(),
  interactive: flag("-i", "--interactive").boolean(),
});

// All of these work:
parser.parse(["--verbose"]); // { verbose: true, ... }
parser.parse(["-v"]); // { verbose: true, ... }
parser.parse(["-ti"]); // { tty: true, interactive: true, ... } (combined!)
parser.parse(["--tty", "--interactive"]); // { tty: true, interactive: true, ... }

Flag Type Methods

.boolean() - Boolean flag (presence = true)

flag("--verbose").boolean();
// --verbose → true

// Single-letter boolean flags support combined syntax
flag("-t").boolean();
flag("-i").boolean();
// -ti → { t: true, i: true } (automatically expanded)

.string() - String value flag

flag("--name").string();
// --name John → "John"
// --name=John → "John"
// --name=John=Doe → "John=Doe" (multiple = preserved)
// --name =John → "=John" (leading = preserved)

// Single dash also supported
flag("-name").string();
// -name John → "John"
// -name=John → "John"

.strings() - Array of strings (accumulates multiple values)

flag("--include").strings();
// --include src --include lib → ["src", "lib"]
// -l blue -l red → ["blue", "red"]

// Supports = syntax and flag-like values
flag("-l").strings();
// -l=-l -l=red → ["-l", "red"]
// -l -l -l red → ["-l", "red"] (flag-like values preserved)

.number() - Numeric value flag

flag("--port").number();
// --port 3000 → 3000

.keyValue() - Key-value pairs (accumulates into object)

flag("--config").keyValue();
// --config name=value → { name: "value" }
// --config name value → { name: "value" }
// --config name=value --config foo=bar → { name: "value", foo: "bar" }

// Multiple syntaxes supported
flag("--set").keyValue();
// --set foo taz → { foo: "taz" }
// --set foo=taz → { foo: "taz" }
// --set=foo=taz → { foo: "taz" }

// Flag-like keys are preserved
// --set --foo taz → { "--foo": "taz" }
// --set=--foo=taz → { "--foo": "taz" }

Flag Modifiers

.required() - Makes the flag required

flag("--output").string().required();
// Throws RequiredFlagMissingError if not provided

.default(value) - Sets a default value

flag("--port").number().default(3000);
// Returns 3000 if --port is not provided

// Without default, optional flags return null when not provided
flag("--name").string();
// Returns null if --name is not provided

flag("--verbose").boolean();
// Returns false if --verbose is not provided (boolean default)

.describe(description) - Adds description for help

flag("--verbose").boolean().describe("Enable verbose output");

command(name: string)

Creates a command builder for subcommands.

command("build"); // Matches exactly "build"

Command Type Methods

.boolean() - Boolean command (presence = true)

command("build").boolean();
// build → true

.restArgs() - Captures all remaining arguments

command("serve").restArgs();
// serve --watch --port 3000 → ["--watch", "--port", "3000"]

Command Modifiers

.describe(description) - Adds description for help

command("build").boolean().describe("Build the project");

argument()

Creates a positional argument builder. Arguments are matched in order.

argument(); // Matches the next positional argument

Argument Type Methods

.string() - String argument (default)

argument().string();
// Returns null if not provided

.strings() - Array of strings (captures all remaining positional arguments)

argument().strings();
// foo tar biz → ["foo", "tar", "biz"]

// Works with interspersed flags
flags({
  verbose: flag("-V", "--verbose").boolean(),
  names: argument().strings(),
});
// foo --verbose tar biz → { verbose: true, names: ["foo", "tar", "biz"] }

Note: When argument().strings() is defined first in the schema, it takes priority and captures all arguments, including flag-like values:

flags({
  arg: argument().strings(),
  labels: flag("-l").strings(),
});
// -l=-l -l=red foo → { labels: [], arg: ['-l=-l', '-l=red', "foo"] }

Argument Modifiers

.required() - Makes the argument required

argument().string().required();
// Throws RequiredArgumentMissingError if not provided

.describe(description) - Adds description for help

argument().string().required().describe("Input file path");

.delimiter(separator) - Parse flag with custom delimiter

flag("pr").string().delimiter(":");
// pr:foo → "foo"
// pr:bar → "bar"
// Returns null if not provided

.match(regex) - Match argument with regex and extract named groups

argument().match(/^(?<part1>\w+):(?<part2>\w+)$/);
// tar:foo → { part1: "tar", part2: "foo" }
// Returns null if pattern doesn't match

.refine(fn) - Custom refinement function for advanced parsing

argument().refine((arg, index, args, context) => {
  if (!arg.startsWith("tar:")) return null;
  return {
    index: index + 1,
    args: [arg],
    value: arg.split(":")[1],
  };
});
// tar:foo → "foo"

.transform(fn) - Transform the parsed value

argument()
  .string()
  .transform((value) => value.toUpperCase());
// tar → "TAR"

Type Inference

The library provides full type inference based on your schema:

const parser = flags({
  verbose: flag("--verbose").boolean(),
  name: flag("--name").string(),
  port: flag("--port").number().default(3000),
  tags: flag("--tag").strings(),
  config: flag("--config").keyValue(),
  build: command("build").boolean(),
  serve: command("serve").restArgs(),
  input: argument().string().required(),
});

const result = parser.parse(args);

// TypeScript knows the types:
// result.verbose: boolean
// result.name: string | null
// result.port: number (never null due to default)
// result.tags: string[]
// result.config: Record<string, string>
// result.build: boolean
// result.serve: string[] | null
// result.input: string (never null due to required)

Help Messages

The parser automatically generates help messages based on your schema:

const parser = flags({
  verbose: flag("--verbose", "-v").boolean().describe("Enable verbose output"),
  port: flag("--port", "-p").number().default(3000).describe("Server port"),
  ip: flag("--ip").string().describe("IPv4 address (e.g., 172.30.100.104)"),
  build: command("build").boolean().describe("Build the project"),
})
  .program("mycli")
  .describe("My awesome CLI tool");

console.log(parser.helpMessage());

Output:

Usage: mycli

My awesome CLI tool

Options:
  -v, --verbose.           Enable verbose output
  -p, --port <number>      Server port
      --ip                 IPv4 address (e.g., 172.30.100.104)

Commands:
  build                    Build the project

Error Handling

The library provides specific error types for better error handling:

FlagsParseError

Base error class for all parsing errors.

UnexpectedArgumentError

Thrown when an unknown argument is encountered.

import { flags, UnexpectedArgumentError } from "@jondotsoy/flags";

try {
  const options = parser.parse(process.argv.slice(2));
} catch (error) {
  if (error instanceof UnexpectedArgumentError) {
    console.error(`Error: ${error.message}`);
    console.error(`Unexpected argument: ${error.argument}`);
    console.log(parser.helpMessage());
    process.exit(1);
  }
  throw error;
}

RequiredFlagMissingError

Thrown when a required flag is not provided.

import { RequiredFlagMissingError } from "@jondotsoy/flags";

try {
  const options = parser.parse(args);
} catch (error) {
  if (error instanceof RequiredFlagMissingError) {
    console.error(`Required flag missing: ${error.flagName}`);
    process.exit(1);
  }
}

RequiredArgumentMissingError

Thrown when a required positional argument is not provided.

import { RequiredArgumentMissingError } from "@jondotsoy/flags";

try {
  const options = parser.parse(args);
} catch (error) {
  if (error instanceof RequiredArgumentMissingError) {
    console.error("Required argument missing");
    process.exit(1);
  }
}

Combined Short Flags

Single-letter boolean flags can be combined for convenience, similar to common Unix tools. You must call .combineShortFlags() on the parser to enable this feature:

import { flags, flag } from "@jondotsoy/flags";

const parser = flags({
  all: flag("-a", "--all").boolean().describe("Show all files"),
  long: flag("-l", "--long").boolean().describe("Use long listing format"),
  human: flag("-h", "--human-readable")
    .boolean()
    .describe("Human readable sizes"),
}).combineShortFlags();

// All of these are equivalent:
parser.parse(["-a", "-l", "-h"]); // Separate flags
parser.parse(["-alh"]); // Combined flags
parser.parse(["-lah"]); // Order doesn't matter
parser.parse(["-al", "-h"]); // Partially combined

// Result: { all: true, long: true, human: true }

Docker-style Example

const dockerParser = flags({
  tty: flag("-t", "--tty").boolean().describe("Allocate a pseudo-TTY"),
  interactive: flag("-i", "--interactive")
    .boolean()
    .describe("Keep STDIN open"),
  detach: flag("-d", "--detach").boolean().describe("Run in background"),
}).combineShortFlags();

// Docker-style combined flags
dockerParser.parse(["-ti"]); // { tty: true, interactive: true, detach: false }
dockerParser.parse(["-tid"]); // { tty: true, interactive: true, detach: true }

Rules for Combined Flags

  1. Must enable feature: Call .combineShortFlags() on the parser
  2. Only single-letter flags: -abc works, but -test does not expand
  3. Only boolean flags: All flags in the combination must be boolean type
  4. No equals syntax: -a=value is not expanded (treated as single flag)
  5. All must exist: If any letter is not a defined flag, the combination is not expanded
const parser = flags({
  all: flag("-a").boolean(),
  brief: flag("-b").boolean(),
  count: flag("-c").number(), // Not boolean!
}).combineShortFlags();

parser.parse(["-ab"]); // ✅ Works: { all: true, brief: true, count: null }
parser.parse(["-abc"]); // ❌ Throws: -c requires a value, cannot be combined
parser.parse(["-abx"]); // ❌ Throws: Unknown flag -x

Complete Examples

Basic CLI Tool

import { flags, flag, UnexpectedArgumentError } from "@jondotsoy/flags";

const parser = flags({
  verbose: flag("--verbose", "-v").boolean().describe("Enable verbose logging"),
  output: flag("--output", "-o")
    .string()
    .default("dist")
    .describe("Output file path"),
  port: flag("--port", "-p")
    .number()
    .default(3000)
    .describe("Server port number"),
  help: flag("--help", "-h").boolean().describe("Show help message"),
})
  .program("mycli")
  .describe("A simple CLI tool");

try {
  const options = parser.parse(process.argv.slice(2));

  if (options.help) {
    console.log(parser.helpMessage());
    process.exit(0);
  }

  console.log("Starting with options:", options);
} catch (error) {
  if (error instanceof UnexpectedArgumentError) {
    console.error(`Error: ${error.message}`);
    console.log(parser.helpMessage());
    process.exit(1);
  }
  throw error;
}

Multi-Command CLI (like npm/git)

import { flags, flag, command } from "@jondotsoy/flags";

const parser = flags({
  global: flag("--global", "-g").boolean().describe("Global mode"),
  verbose: flag("--verbose", "-v").boolean().describe("Verbose output"),
  build: command("build").restArgs().describe("Build the project"),
  test: command("test").restArgs().describe("Run tests"),
  serve: command("serve").restArgs().describe("Start dev server"),
})
  .program("mycli")
  .describe("Multi-command CLI tool");

const options = parser.parse(process.argv.slice(2));

if (options.build) {
  console.log("Building with args:", options.build);
  // Build logic here
} else if (options.test) {
  console.log("Testing with args:", options.test);
  // Test logic here
} else if (options.serve) {
  console.log("Serving with args:", options.serve);
  // Serve logic here
}

// Usage examples:
// mycli build --verbose
// mycli test src/**/*.test.ts
// mycli serve --port 8080

File Processing Tool with Multiple Inputs

import { flags, flag, argument } from "@jondotsoy/flags";

const parser = flags({
  input: flag("--input", "-i").strings().describe("Input files"),
  output: flag("--output", "-o").string().describe("Output directory"),
  format: flag("--format", "-f")
    .string()
    .describe("Output format (json, yaml, xml)"),
  exclude: flag("--exclude").strings().describe("Exclude patterns"),
  recursive: flag("--recursive", "-r")
    .boolean()
    .describe("Process recursively"),
  dryRun: flag("--dry-run").boolean().describe("Dry run mode"),
  file: argument().string().describe("Input file (positional)"),
})
  .program("fileprocessor")
  .describe("Process files with various options");

const options = parser.parse(process.argv.slice(2));

// Usage: fileprocessor input.txt --input src/ --exclude node_modules --format json --output dist/

Configuration with Key-Value Pairs

import { flags, flag } from "@jondotsoy/flags";

const parser = flags({
  env: flag("--env", "-e")
    .string()
    .default("development")
    .describe("Environment"),
  debug: flag("--debug", "-d").boolean().describe("Enable debug mode"),
  config: flag("--config", "-c")
    .keyValue()
    .describe("Configuration key-value pairs"),
  feature: flag("--feature").strings().describe("Enable features"),
})
  .program("myapp")
  .describe("Application with configuration");

const options = parser.parse(process.argv.slice(2));

console.log("Environment:", options.env);
console.log("Debug:", options.debug);
console.log("Config:", options.config);
console.log("Features:", options.feature);

// Usage: myapp --env production --config db=postgres --config port=5432 --feature auth --feature payments
// Result: {
//   env: "production",
//   debug: false,
//   config: { db: "postgres", port: "5432" },
//   feature: ["auth", "payments"]
// }

Best Practices

1. Use the Fluent API

Chain methods for clear and concise flag definitions:

flag("--port", "-p")
  .number()
  .default(3000)
  .describe("Port number for the server");

2. Provide Defaults

Use .default() for sensible default values:

const parser = flags({
  port: flag("--port").number().default(3000),
  output: flag("--output").string().default("dist"),
  verbose: flag("--verbose").boolean(), // defaults to false
});

3. Add Descriptions

Use .describe() for automatic help generation:

const parser = flags({
  verbose: flag("--verbose", "-v").boolean().describe("Enable verbose output"),
})
  .program("mycli")
  .describe("My CLI tool description");

4. Handle Errors Gracefully

Always wrap parsing in try-catch blocks:

try {
  const options = parser.parse(args);
  // Use options
} catch (error) {
  if (error instanceof UnexpectedArgumentError) {
    console.error(`Error: ${error.message}`);
    console.log(parser.helpMessage());
    process.exit(1);
  }
  throw error;
}

5. Use Required for Mandatory Options

Mark required flags and arguments explicitly:

const parser = flags({
  output: flag("--output").string().required(),
  input: argument().string().required(),
});

Migration Guide

From other CLI libraries

From yargs:

// Before (yargs)
const argv = yargs
  .option("verbose", { type: "boolean", alias: "v" })
  .option("port", { type: "number", default: 3000 }).argv;

// After (@jondotsoy/flags)
const parser = flags({
  verbose: flag("--verbose", "-v").boolean(),
  port: flag("--port").number().default(3000),
});
const options = parser.parse(process.argv.slice(2));

From commander:

// Before (commander)
program
  .option("-v, --verbose", "verbose output")
  .option("-p, --port <port>", "port number", "3000")
  .parse();

// After (@jondotsoy/flags)
const parser = flags({
  verbose: flag("--verbose", "-v").boolean().describe("verbose output"),
  port: flag("--port", "-p").number().default(3000).describe("port number"),
});
const options = parser.parse(process.argv.slice(2));

FAQ

Q: How do I handle subcommands like git commit or npm install?

A: Use the command() builder with .restArgs():

const parser = flags({
  commit: command("commit").restArgs(),
  install: command("install").restArgs(),
});

Q: Can I have flags that don't require values?

A: Yes! Use .boolean() for boolean flags:

flag("--verbose").boolean();
flag("--debug", "-d").boolean();

Q: How do I validate flag values?

A: Parse the result and validate after:

const options = parser.parse(args);
if (options.port && (options.port < 1 || options.port > 65535)) {
  throw new Error("Port must be between 1 and 65535");
}

Q: Can I have the same flag appear multiple times?

A: Yes! Use .strings() for string arrays:

flag("--include").strings();
// --include src --include lib → ["src", "lib"]

Q: Can I combine short flags like -abc?

A: Yes! Call .combineShortFlags() on the parser to enable automatic expansion of single-letter boolean flags:

const parser = flags({
  all: flag("-a").boolean(),
  brief: flag("-b").boolean(),
  color: flag("-c").boolean(),
}).combineShortFlags();

parser.parse(["-abc"]); // { all: true, brief: true, color: true }

Note: This only works for single-letter boolean flags. Flags with values or multi-letter flags are not expanded.

Q: How do I handle key-value pairs?

A: Use .keyValue():

flag("--config").keyValue();
// --config db=postgres --config port=5432 → { db: "postgres", port: "5432" }

License

MIT - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.