@jondotsoy/flags
v3.1.3
Published
A Javascript arguments processor
Downloads
111
Maintainers
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-fsyntax - 🔗 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/flagsQuick 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
--verboseor-v - Commands: Subcommands like
buildorserve - 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 encounteredRequiredFlagMissingError- When a required flag is missingRequiredArgumentMissingError- 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 argumentArgument 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 projectError 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
- Must enable feature: Call
.combineShortFlags()on the parser - Only single-letter flags:
-abcworks, but-testdoes not expand - Only boolean flags: All flags in the combination must be boolean type
- No equals syntax:
-a=valueis not expanded (treated as single flag) - 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 -xComplete 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 8080File 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.
