
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
cli-args-parser
Advanced tools
Expressive syntax meets powerful schema validation.
Zero dependencies • TypeScript-first • Nested subcommands • Custom validation
Parse key=value, key:=typed, Key:Meta — with fully customizable separators.
Quick Start · Syntax · Schema · Validation · CLI · API
npm install cli-args-parser
# or
pnpm add cli-args-parser
import { parse } from 'cli-args-parser'
const result = parse([
'https://api.example.com',
'name=Filipe',
'age:=35',
'active:=true',
'Authorization:Bearer TOKEN',
'--verbose',
'-o', 'output.json'
])
// {
// positional: ['https://api.example.com'],
// data: { name: 'Filipe', age: 35, active: true },
// meta: { Authorization: 'Bearer TOKEN' },
// flags: { verbose: true },
// options: { o: 'output.json' },
// errors: []
// }
| Category | Features |
|---|---|
| Syntax | Expressive key=value, key:=typed, Key:Value patterns |
| Separators | Fully customizable — define your own patterns and categories |
| Schema | Type coercion, required fields, choices, defaults, env vars |
| Validation | Built-in + custom validation functions |
| Commands | Nested subcommands with unlimited depth |
| Options | Auto-short generation, aliases, negation (--no-flag) |
| Output | Shell completion (Bash, Zsh, Fish), help generation |
| Formatting | Token-based theming, bring your own colors (chalk, picocolors, tuiuiu.js/colors, ANSI) |
| Quality | 431 tests, zero dependencies, TypeScript-first |
| Syntax | Category | Example | Result |
|---|---|---|---|
key=value | data | name=Filipe | data.name = "Filipe" |
key:=value | data (typed) | age:=35 | data.age = 35 |
Key:Value | meta | Auth:Bearer X | meta.Auth = "Bearer X" |
--flag | flags | --verbose | flags.verbose = true |
--no-flag | flags* | --no-color | flags.color = false |
-f | flags | -v | flags.v = true |
--opt=val | options | --output=file | options.output = "file" |
-o val | options | -o file | options.o = "file" |
-- | marker | -- --help | End of options, rest is positional |
Notes:
- The
Key:Valuemeta syntax requires the key to start with an uppercase letter (e.g.,Authorization:Bearer). Lowercase keys likenginx:latestare treated as positional to avoid conflicts with docker images and similar patterns.- Keys containing
/are rejected for=and:=separators to avoid matching URL-like patterns (e.g.,data:text/plain;base64,x=). Use positional arguments for paths:myapp -- path/to/fileinstead ofpath/to=file. Namespaced keys likeaws:region=valuework because they don't contain/.- *
--no-flagbehavior differs between APIs:
parse(): Always goes toflagsasflags.flag = falsecreateParser()/createCLI(): Goes tooptionsasoptions.flag = false(only for defined boolean options withnegatable !== false)
--)The -- marker signals the end of options. Everything after it is treated as positional, even if it looks like a flag or separator:
const result = parse(['--verbose', '--', '--help', 'name=value'])
// result.flags = { verbose: true }
// result.positional = ['--help', 'name=value'] // Treated as positional, not parsed
This is useful for passing arguments to subprocesses or handling filenames that start with -.
Behavior with createParser() and allowUnknown:
When using createParser() with positional definitions:
const parser = createParser({
positional: [{ name: 'file' }],
allowUnknown: true // default
})
// Without --: unknown flags go to options
parser.parse(['--unknown'])
// { options: { unknown: true }, positional: {}, rest: [] }
// With --: everything after is positional, fills schema positions first, then rest
parser.parse(['--', '--unknown', 'extra'])
// { positional: { file: '--unknown' }, rest: ['extra'], options: {} }
With allowUnknown: false, extra positionals after -- that don't fit schema positions are silently ignored:
const parser = createParser({
positional: [{ name: 'file' }],
allowUnknown: false
})
parser.parse(['--', 'file.txt', 'extra'])
// { positional: { file: 'file.txt' }, rest: [], options: {} }
// 'extra' is ignored, not stored in rest
Note:
createCLI()usesstrictinstead ofallowUnknown. Withstrict: true, unknown options cause errors, but extras after--always go torest(useful for passing args to subprocesses).
:=)The := separator automatically coerces values to their JavaScript types:
parse(['count:=42']) // data.count = 42 (number)
parse(['active:=true']) // data.active = true (boolean)
parse(['active:=false']) // data.active = false (boolean)
parse(['value:=null']) // data.value = null
parse(['items:=[1,2,3]']) // data.items = [1, 2, 3] (JSON array)
parse(['config:={"a":1}']) // data.config = { a: 1 } (JSON object)
parse(['tags:=a,b,c']) // data.tags = ["a", "b", "c"] (comma-separated)
parse(['ids:=1,2,3']) // data.ids = [1, 2, 3] (comma-separated with type coercion)
The separator system is fully customizable. Define your own patterns and category names:
import { parse } from 'cli-args-parser'
// Default separators
const defaults = {
'=': 'data', // key=value → data.key
':=': { to: 'data', typed: true }, // key:=value → data.key (with type coercion)
':': 'meta' // Key:Value → meta.Key
}
// Custom separators for your use case
const result = parse(['file@data.json', 'name->Filipe', 'count::42'], {
separators: {
'@': 'files', // file@path → files.file
'->': 'body', // key->value → body.key
'::': { to: 'body', typed: true } // key::value → body.key (typed)
}
})
// {
// positional: [],
// files: { file: 'data.json' },
// body: { name: 'Filipe', count: 42 },
// flags: {},
// options: {},
// errors: []
// }
Separator Configuration:
| Format | Description | Example |
|---|---|---|
string | Category name shorthand | '@': 'files' |
{ to, typed? } | Full config with type coercion | ':=': { to: 'data', typed: true } |
{ to, prefix: true } | Prefix separator (starts the arg) | '@': { to: 'tags', prefix: true } |
Regular separators expect key<sep>value format. Prefix separators start at position 0 and use : as the key/value delimiter:
import { parse } from 'cli-args-parser'
const result = parse(['@tag:important', '@env:production', 'name=test'], {
separators: {
'=': 'data',
'@': { to: 'tags', prefix: true } // @key:value format
}
})
// {
// positional: [],
// data: { name: 'test' },
// tags: { tag: 'important', env: 'production' },
// flags: {},
// options: {},
// errors: []
// }
Prefix separators are useful for tag-like syntax where you want a marker character to distinguish a category.
Add validation, defaults, and help generation:
import { createParser } from 'cli-args-parser'
const parser = createParser({
positional: [
{ name: 'url', required: true, description: 'Target URL' }
],
options: {
output: {
short: 'o',
type: 'string',
description: 'Output file'
},
verbose: {
short: 'v',
type: 'boolean',
default: false
},
format: {
type: 'string',
choices: ['json', 'yaml', 'xml'],
default: 'json'
},
retries: {
type: 'number',
default: 3,
env: 'MAX_RETRIES' // Fallback to env var
}
}
})
const result = parser.parse(['https://api.com', '-v', '--format=yaml'])
console.log(parser.help())
interface OptionDefinition {
short?: string // Short flag (-o)
aliases?: string[] // Alternative names
type?: 'string' | 'number' | 'boolean' | 'array'
default?: any // Default value
description?: string // Help text
required?: boolean // Required option
choices?: any[] // Allowed values
env?: string // Environment variable fallback
hidden?: boolean // Hide from help
negatable?: boolean // Allow --no-flag (default: true)
validate?: ValidateFn // Custom validation function
}
interface PositionalDefinition {
name: string // Argument name
description?: string // Help text
required?: boolean // Required argument
type?: 'string' | 'number' | 'boolean' | 'array'
default?: any // Default value
variadic?: boolean // Capture remaining args
validate?: ValidateFn // Custom validation function
}
Add custom validation logic to options and positionals:
import { createParser, ValidateFn } from 'cli-args-parser'
const parser = createParser({
options: {
port: {
type: 'number',
validate: (value) => {
const num = value as number
if (num < 1 || num > 65535) {
return `Port must be between 1 and 65535, got ${num}`
}
return true
}
},
email: {
type: 'string',
validate: (value) => {
const str = value as string
if (!str.includes('@')) {
return 'Invalid email format'
}
return true
}
}
},
positional: [
{
name: 'url',
required: true,
validate: (value) => {
const str = value as string
if (!str.startsWith('https://')) {
return 'URL must use HTTPS'
}
return true
}
}
]
})
const result = parser.parse(['http://example.com', '--port=70000'])
// result.errors = [
// 'URL must use HTTPS',
// 'Port must be between 1 and 65535, got 70000'
// ]
// Return true if valid, or error message string if invalid
type ValidationResult = true | string
type ValidateFn<T = PrimitiveValue | PrimitiveValue[]> = (value: T) => ValidationResult
Validation Behavior:
undefined)choices validation (choices checked first)result.errorsconst parser = createParser({
options: {
level: {
type: 'number',
choices: [1, 2, 3, 4, 5], // First check: must be in choices
validate: (value) => { // Second check: custom logic
if (value === 3) return 'Level 3 is temporarily disabled'
return true
}
}
}
})
parser.parse(['--level=10']) // Error: Invalid value (choices)
parser.parse(['--level=3']) // Error: Level 3 is temporarily disabled
parser.parse(['--level=2']) // OK
Automatically assigns short flags to options:
const parser = createParser({
autoShort: true,
options: {
verbose: { type: 'boolean' }, // Gets -v
output: { type: 'string' }, // Gets -o
format: { type: 'string' } // Gets -f
}
})
parser.parse(['-v', '-o', 'file.txt', '-f', 'json'])
Algorithm:
verbose → v)verbose → V)verbose → e)Build complex CLIs with nested command routing:
import { createCLI } from 'cli-args-parser'
const cli = createCLI({
name: 'myapp',
version: '1.0.0',
autoShort: true,
options: {
verbose: { type: 'boolean', description: 'Verbose output' }
},
commands: {
get: {
description: 'Fetch a resource',
positional: [{ name: 'url', required: true }],
options: {
output: { short: 'o', type: 'string' }
}
},
post: {
description: 'Create a resource',
positional: [{ name: 'url', required: true }]
}
}
})
cli.parse(['get', 'https://api.com', '-o', 'result.json', '--verbose'])
Commands can be nested to any depth:
const cli = createCLI({
name: 'kubectl',
commands: {
config: {
description: 'Manage configuration',
commands: {
get: {
description: 'Get config value',
positional: [{ name: 'key', required: true }]
},
context: {
description: 'Manage contexts',
commands: {
list: { description: 'List all contexts' },
use: {
description: 'Switch context',
positional: [{ name: 'name', required: true }]
}
}
}
}
}
}
})
cli.parse(['config', 'get', 'theme'])
// → command: ['config', 'get'], positional: { key: 'theme' }
cli.parse(['config', 'context', 'use', 'production'])
// → command: ['config', 'context', 'use'], positional: { name: 'production' }
Execute code when a command is matched:
const cli = createCLI({
name: 'deploy',
commands: {
staging: {
description: 'Deploy to staging',
handler: async (result) => {
console.log('Deploying to staging...')
// Your deploy logic
}
},
production: {
description: 'Deploy to production',
options: {
force: { type: 'boolean', default: false }
},
handler: async (result) => {
if (!result.options.force) {
console.log('Use --force to deploy to production')
return
}
console.log('Deploying to production...')
}
}
}
})
await cli.run(process.argv.slice(2))
commands: {
install: {
aliases: ['i', 'add'],
description: 'Install packages'
}
}
// All equivalent:
// myapp install lodash
// myapp i lodash
// myapp add lodash
Custom validation works in subcommands too:
const cli = createCLI({
name: 'server',
commands: {
start: {
description: 'Start the server',
options: {
port: {
type: 'number',
default: 3000,
validate: (value) => {
if (value < 1024) return 'Port must be >= 1024 (non-privileged)'
return true
}
}
},
positional: [
{
name: 'config',
validate: (value) => {
if (!value.endsWith('.json')) return 'Config must be a .json file'
return true
}
}
]
}
}
})
When you define a version in your CLI schema, automatic version handling is enabled:
const cli = createCLI({
name: 'myapp',
version: '1.0.0',
description: 'My awesome CLI'
})
// All of these work:
// myapp version
// myapp --version
// myapp -V
Output:
myapp 1.0.0
My awesome CLI
You can also call cli.version() programmatically:
console.log(cli.version())
The version output respects the formatter configuration (see Formatter & Theming).
Generate completion scripts for popular shells:
const cli = createCLI({ /* ... */ })
// Generate for your shell
const bashScript = cli.completion('bash')
const zshScript = cli.completion('zsh')
const fishScript = cli.completion('fish')
# Add to ~/.bashrc or ~/.zshrc
eval "$(myapp completion bash)"
The library outputs structured tokens that you can style with any coloring library. Zero colors bundled — bring your own chalk, picocolors, or raw ANSI codes.
All CLIs created with createCLI() automatically support the --no-color flag and respect the NO_COLOR standard:
# Disable colors via flag
myapp --help --no-color
# Enable colors explicitly
myapp --help --color
# Disable colors via environment variable
NO_COLOR=1 myapp --help
# Force colors (overrides NO_COLOR)
FORCE_COLOR=1 myapp --help
Priority (highest to lowest):
--no-color / --color CLI flagsFORCE_COLOR environment variableNO_COLOR environment variableProgrammatic Usage:
import { shouldUseColor, getEffectiveFormatter, type Formatter } from 'cli-args-parser'
// Check if colors should be used (respects env vars)
if (shouldUseColor()) {
console.log('Colors enabled')
}
// Get effective formatter (returns undefined if colors disabled)
const myFormatter: Formatter = { 'error-message': s => `\x1b[31m${s}\x1b[0m` }
const effectiveFormatter = getEffectiveFormatter(myFormatter)
// effectiveFormatter is undefined when NO_COLOR is set
import chalk from 'chalk'
import { createCLI, type Formatter } from 'cli-args-parser'
const cli = createCLI({
name: 'myapp',
version: '1.0.0',
commands: {
deploy: { description: 'Deploy to production' }
},
options: {
verbose: { short: 'v', type: 'boolean', description: 'Verbose output' }
},
formatter: {
'section-header': s => chalk.bold.white(s),
'program-name': s => chalk.magenta.bold(s),
'version': s => chalk.green(s),
'command-name': s => chalk.yellow(s),
// Command options
'option-flag': s => chalk.green(s),
'option-type': s => chalk.cyan(s),
'option-default': s => chalk.dim(s),
// Global options (same style or different)
'global-option-flag': s => chalk.green(s),
'global-option-type': s => chalk.cyan(s),
'global-option-default': s => chalk.dim(s),
'error-message': s => chalk.red(s)
}
})
console.log(cli.help()) // Colorized help
console.log(cli.version()) // Colorized version
Picocolors is 2x faster and 14x smaller than chalk:
import pc from 'picocolors'
import { createCLI, type Formatter } from 'cli-args-parser'
// Create a reusable theme
const theme: Formatter = {
'section-header': s => pc.bold(s),
'program-name': s => pc.magenta(pc.bold(s)),
'command-name': s => pc.yellow(s),
// Command options
'option-flag': s => pc.green(s),
'option-type': s => pc.cyan(s),
'option-default': s => pc.dim(s),
// Global options
'global-option-flag': s => pc.green(s),
'global-option-type': s => pc.cyan(s),
'global-option-default': s => pc.dim(s),
'error-message': s => pc.red(s)
}
const cli = createCLI({
name: 'api',
version: '1.0.0',
formatter: theme
})
tuiuiu.js/colors is a zero-dependency color library with a chainable API, template literals, and style composition — perfect for zero-dep CLIs:
| Feature | chalk | picocolors | tuiuiu.js/colors |
|---|---|---|---|
| Zero dependencies | ❌ | ✅ | ✅ |
| Chainable API | ✅ | ❌ | ✅ |
| Template literals | ❌ | ❌ | ✅ |
| Composable styles | ❌ | ❌ | ✅ |
| Background colors | ✅ | ✅ | ✅ |
| Tailwind palette | ❌ | ❌ | ✅ |
| Hex/RGB support | ✅ | ❌ | ✅ |
Chainable API:
import { c } from 'tuiuiu.js/colors'
import { createCLI, type Formatter } from 'cli-args-parser'
const theme: Formatter = {
'section-header': s => c.bold.white(s),
'program-name': s => c.magenta.bold(s),
'version': s => c.green(s),
'command-name': s => c.yellow(s),
// Command options
'option-flag': s => c.green(s),
'option-type': s => c.cyan(s),
'option-default': s => c.dim(s),
// Global options
'global-option-flag': s => c.green(s),
'global-option-type': s => c.cyan(s),
'global-option-default': s => c.dim(s),
'error-message': s => c.red(s),
}
const cli = createCLI({
name: 'api',
version: '1.0.0',
formatter: theme
})
Background Colors for Status Badges:
import { c } from 'tuiuiu.js/colors'
const formatter: Formatter = {
// Status badges with background
'error-header': s => c.bgRed.white.bold(` ${s} `),
'warning-header': s => c.bgYellow.black.bold(` ${s} `),
'success-header': s => c.bgGreen.black.bold(` ${s} `),
// Environment indicators
'env-production': s => c.bgRed.white.bold(` PROD `),
'env-staging': s => c.bgYellow.black(` STG `),
'env-development': s => c.bgGreen.black(` DEV `),
// Highlight dangerous commands
'command-name': s => s.includes('delete')
? c.bgRed.white.bold(s) // Dangerous commands in red
: c.yellow(s),
}
Template Literals for Complex Output:
import { tpl } from 'tuiuiu.js/colors'
// Instead of manual concatenation
console.log(tpl`{red Error:} File {bold ${filename}} not found`)
console.log(tpl`{bgRed.white DANGER } This action is {bold.red irreversible}`)
console.log(tpl`{green ✓} Deployed to {bgBlue.white ${env} } successfully`)
Composable Styles for Reusable Themes:
import { compose, red, bold, bgWhite, black, bgRed, white } from 'tuiuiu.js/colors'
// Pre-composed reusable styles
const errorStyle = compose(red, bold)
const highlightStyle = compose(bgWhite, black, bold)
const dangerBadge = compose(bgRed, white, bold)
const formatter: Formatter = {
'error-message': s => errorStyle(s),
'highlighted-text': s => highlightStyle(` ${s} `),
'danger-badge': s => dangerBadge(` ${s} `),
}
A professional theme inspired by Recker using orange as the primary color with multiple shades. Zero dependencies — pure ANSI escape codes:
import { createCLI, type Formatter } from 'cli-args-parser'
/**
* 🎨 Recker-inspired Orange Theme
*
* Uses ANSI 256 color codes for rich orange palette:
* - 208: Bright orange (#FF8700) — primary
* - 214: Light orange (#FFAF00) — accent
* - 202: Dark orange (#FF5F00) — emphasis
* - 166: Burnt orange (#D75F00) — subtle
* - 252: Light gray (#D0D0D0) — text
* - 245: Medium gray (#8A8A8A) — muted
*/
const ansi = {
// Styles
bold: (s: string) => `\x1b[1m${s}\x1b[22m`,
dim: (s: string) => `\x1b[2m${s}\x1b[22m`,
// Orange palette (ANSI 256)
orange: (s: string) => `\x1b[38;5;208m${s}\x1b[39m`, // Primary
lightOrange: (s: string) => `\x1b[38;5;214m${s}\x1b[39m`, // Accent
darkOrange: (s: string) => `\x1b[38;5;202m${s}\x1b[39m`, // Emphasis
burntOrange: (s: string) => `\x1b[38;5;166m${s}\x1b[39m`, // Subtle
// Neutrals
white: (s: string) => `\x1b[97m${s}\x1b[39m`,
gray: (s: string) => `\x1b[38;5;245m${s}\x1b[39m`,
lightGray: (s: string) => `\x1b[38;5;252m${s}\x1b[39m`,
// Semantic
error: (s: string) => `\x1b[31m${s}\x1b[39m`,
}
// Compose styles
const bold = ansi.bold
const orange = (s: string) => ansi.bold(ansi.orange(s))
const reckerTheme: Formatter = {
// Headers & structure
'section-header': s => bold(ansi.white(s)),
// Identity
'program-name': s => orange(s),
'version': s => ansi.lightOrange(s),
'description': s => ansi.lightGray(s),
// Commands
'command-name': s => ansi.orange(s),
'command-alias': s => ansi.gray(s),
'command-description': s => ansi.lightGray(s),
// Options
'option-flag': s => ansi.lightOrange(s),
'option-type': s => ansi.burntOrange(s),
'option-default': s => ansi.dim(ansi.gray(s)),
'option-description': s => ansi.lightGray(s),
// Positionals
'positional-name': s => ansi.darkOrange(s),
// Errors
'error-header': s => bold(ansi.error(s)),
'error-message': s => ansi.error(s),
'error-option': s => ansi.orange(s),
}
const cli = createCLI({
name: 'rek',
version: '1.0.0',
description: 'HTTP client for the AI era',
commands: {
get: { description: 'Make a GET request' },
post: { description: 'Make a POST request' },
ai: { description: 'AI-powered requests', aliases: ['chat'] }
},
options: {
output: { short: 'o', type: 'string', description: 'Output file' },
verbose: { short: 'v', type: 'boolean', description: 'Verbose output' },
timeout: { short: 't', type: 'number', default: 30000, description: 'Request timeout (ms)' }
},
formatter: reckerTheme
})
console.log(cli.help())
Output Preview:
rek 1.0.0 ← orange bold + light orange
HTTP client for the AI era ← light gray
Usage: rek [command] [options]
Commands:
get Make a GET request ← orange + light gray
post Make a POST request
ai AI-powered requests
Global Options:
-o, --output <string> Output file ← light orange + burnt orange
-v, --verbose Verbose output
-t, --timeout <number> Request timeout (ms) (default: 30000)
💡 Tip: Copy the
ansiobject to your project as a zero-dependency color utility. It works in any terminal that supports ANSI 256 colors (most modern terminals).
For advanced use cases (generating Markdown, HTML, JSON), access raw tokens directly:
import { createCLI, generateCLIHelpTokens, generateVersionTokens } from 'cli-args-parser'
const cli = createCLI({
name: 'myapp',
version: '1.0.0',
commands: {
deploy: { description: 'Deploy the app' }
}
})
// Get structured tokens
const helpTokens = generateCLIHelpTokens(cli.schema)
const versionTokens = generateVersionTokens(cli.schema)
// Example: Convert to Markdown
function toMarkdown(tokens) {
return tokens.map(({ type, value }) => {
switch (type) {
case 'section-header': return `\n## ${value}\n`
case 'program-name': return `**${value}**`
case 'command-name': return `\`${value.trim()}\``
case 'option-flag': return `\`${value}\``
default: return value
}
}).join('')
}
// Example: Extract data
const commands = helpTokens
.filter(t => t.type === 'command-name')
.map(t => t.value.trim())
// ['deploy']
Help Tokens (23 types):
| Token | Description | Example Value |
|---|---|---|
section-header | Section titles | "Usage:", "Options:" |
program-name | CLI name | "myapp" |
version | Version number | "1.0.0" |
description | CLI/command description | "My awesome CLI" |
command-name | Command name | "deploy" |
command-alias | Command aliases | "(aliases: d, dep)" |
command-description | Command description | "Deploy to production" |
option-flag | Command option flags | "-v, --verbose" |
option-type | Command option type hint | "<string>" |
option-default | Command option default | "(default: 3000)" |
option-description | Command option description | "Enable verbose output" |
global-option-flag | Global option flags | "-v, --verbose" |
global-option-type | Global option type hint | "<string>" |
global-option-default | Global option default | "(default: 3000)" |
global-option-description | Global option description | "Enable verbose output" |
positional-name | Positional arg name | "<file>" |
positional-optional | Optional marker | " (optional)" |
usage-text | Usage pattern | "[options] <file>" |
error-message | Error text | "Unknown command: xyz" |
text | Plain text/spacing | " " |
newline | Line break | "\n" |
indent | Indentation | " " |
💡 Global vs Command Options: Global options (defined at CLI root) use
global-option-*tokens. Command-specific options useoption-*tokens. This allows different visual styling for each.
Error Tokens (7 types):
| Token | Description |
|---|---|
error-header | Error section header |
error-bullet | Bullet point marker |
error-message | Error message text |
error-option | Option name in error |
error-value | Invalid value in error |
error-command | Command name in error |
text | Plain text |
Global options are defined at the CLI root level and apply to all commands. Command options are specific to each command.
const cli = createCLI({
name: 'deploy',
// Global options - available to all commands
options: {
verbose: { short: 'v', type: 'boolean', description: 'Verbose output' },
config: { short: 'c', type: 'string', description: 'Config file' }
},
commands: {
prod: {
description: 'Deploy to production',
// Command-specific options
options: {
force: { short: 'f', type: 'boolean', description: 'Force deployment' }
}
}
}
})
Token Differentiation:
In help output, global options use global-option-* tokens while command options use option-* tokens. This allows different visual styling:
import { c } from 'tuiuiu.js/colors'
const cli = createCLI({
name: 'deploy',
options: {
verbose: { short: 'v', type: 'boolean', description: 'Verbose output' }
},
commands: {
prod: {
description: 'Deploy to production',
options: {
force: { short: 'f', type: 'boolean', description: 'Force deployment' }
}
}
},
formatter: {
// Command options in green
'option-flag': s => c.green(s),
'option-description': s => c.white(s),
// Global options in cyan (dimmer)
'global-option-flag': s => c.cyan(s),
'global-option-description': s => c.dim(s),
},
help: {
includeGlobalOptionsInCommands: true // Show global options in command help
}
})
Including Global Options in Command Help:
By default, command help only shows command-specific options. Enable help.includeGlobalOptionsInCommands to also show global options:
# Without includeGlobalOptionsInCommands (default)
$ deploy prod --help
deploy prod
Deploy to production
Usage: deploy prod [options]
Options:
-f, --force Force deployment
# With includeGlobalOptionsInCommands: true
$ deploy prod --help
deploy prod
Deploy to production
Usage: deploy prod [options]
Options:
-f, --force Force deployment
Global Options:
-v, --verbose Verbose output
-c, --config Config file
const parser = createParser({
options: {
apiKey: {
type: 'string',
env: 'API_KEY', // Falls back to $API_KEY
required: true
},
debug: {
type: 'boolean',
env: 'DEBUG',
default: false
}
}
})
Priority: CLI arg > Environment variable > Default value
Capture all remaining arguments:
const parser = createParser({
positional: [
{ name: 'command', required: true },
{ name: 'files', variadic: true }
]
})
parser.parse(['build', 'src/a.ts', 'src/b.ts', 'src/c.ts'])
// positional: { command: 'build', files: ['src/a.ts', 'src/b.ts', 'src/c.ts'] }
Reject unknown options:
const parser = createParser({
strict: true,
options: { verbose: { type: 'boolean' } }
})
parser.parse(['--unknown'])
// errors: ['Unknown option: --unknown']
Negation works with aliases and normalizes to the canonical option name:
const parser = createParser({
options: {
verbose: {
type: 'boolean',
default: true,
aliases: ['debug']
}
}
})
parser.parse(['--no-debug'])
// options: { verbose: false }
// 'debug' is not set — normalized to canonical name 'verbose'
parse(args, options?)Basic parsing without schema validation.
import { parse } from 'cli-args-parser'
const result = parse(process.argv.slice(2), {
separators: { /* custom separators */ },
strict: false,
stopEarly: false,
excludePatterns: [/^https?:\/\//]
})
Returns: ParsedArgs
interface ParsedArgs {
positional: string[]
flags: Record<string, boolean>
options: Record<string, PrimitiveValue>
errors: string[]
// Dynamic categories based on separators:
// Category value type depends on separator config:
// - typed: false (default) → string
// - typed: true → TypedValue (string | number | boolean | null | array | object)
data: Record<string, TypedValue> // ':=' is typed by default
meta: Record<string, string> // ':' is not typed by default
// Note: if you configure ':' with { typed: true }, meta would be Record<string, TypedValue>
}
type PrimitiveValue = string | number | boolean | null
type TypedValue = PrimitiveValue | TypedValue[] | { [key: string]: TypedValue }
createEmptyResult(separators?)Create an empty result object with categories initialized.
import { createEmptyResult, DEFAULT_SEPARATORS } from 'cli-args-parser'
const empty = createEmptyResult()
// { positional: [], flags: {}, options: {}, errors: [], data: {}, meta: {} }
const custom = createEmptyResult({ '=': 'params', '@': 'files' })
// { positional: [], flags: {}, options: {}, errors: [], params: {}, files: {} }
mergeResults(base, overlay)Merge two parse results, with overlay values taking precedence.
import { parse, mergeResults } from 'cli-args-parser'
const defaults = parse(['name=default', '--verbose'])
const userArgs = parse(['name=custom', 'age:=30'])
const merged = mergeResults(defaults, userArgs)
// data: { name: 'custom', age: 30 }, flags: { verbose: true }
createParser(schema)Create a parser with validation, defaults, and help generation.
import { createParser } from 'cli-args-parser'
const parser = createParser({
positional: [/* ... */],
options: { /* ... */ },
separators: { /* ... */ },
strict: false,
stopEarly: false,
allowUnknown: true,
autoShort: false,
formatter: { /* token styling */ }
})
parser.parse(args) // Parse arguments
parser.help() // Generate help text
parser.schema // Access schema
Returns: Parser
interface Parser {
parse(args: string[]): SchemaParseResult
help(): string
schema: ParserSchema
}
createCLI(schema)Create a CLI with subcommands, handlers, and completion.
import { createCLI } from 'cli-args-parser'
const cli = createCLI({
name: 'myapp',
version: '1.0.0',
description: 'My awesome CLI',
options: { /* global options */ },
commands: { /* ... */ },
separators: { /* ... */ },
strict: false,
autoShort: false,
formatter: { /* token styling */ },
config: {
files: ['.myapprc', 'myapp.config.json'],
searchPlaces: ['cwd', 'home']
}
})
cli.parse(args) // Parse arguments
await cli.run(args) // Parse and execute handler
cli.version() // Version string (respects formatter)
cli.help(['config']) // Help for specific command
cli.completion('bash') // Shell completion script
cli.schema // Access schema
Returns: CLI
interface CLI {
parse(args: string[]): CommandParseResult
run(args: string[]): Promise<void>
version(): string
help(command?: string[]): string
completion(shell: 'bash' | 'zsh' | 'fish'): string
schema: CLISchema
}
import type {
// Core types
PrimitiveValue,
ParsedArgs,
Token,
TokenType,
Separators,
ParserOptions,
// Schema types
OptionType,
OptionDefinition,
PositionalDefinition,
ParserSchema,
SchemaParseResult,
Parser,
ValidateFn,
ValidationResult,
// CLI types
CommandDefinition,
CLISchema,
ConfigOptions,
CommandParseResult,
CLI,
// Internal types
ResolvedOption,
// Formatter types
Formatter,
FormatterFn,
HelpToken,
HelpTokenType,
ErrorToken,
ErrorTokenType
} from 'cli-args-parser'
import {
// Tokenizer
tokenize,
looksLikeValue,
groupTokens,
// Coercion
coercePrimitive,
coerceTyped,
coerceToType,
coerceToBoolean,
coerceToNumber,
stripQuotes,
isNumericString,
isBooleanString,
inferType,
// Help & Version
generateHelp,
generateCLIHelp,
generateHelpTokens,
generateCLIHelpTokens,
generateVersion,
generateVersionTokens,
wrapText,
// Formatter
renderTokens,
token,
tokens,
shouldUseColor,
getEffectiveFormatter,
// Completion
generateCompletion,
// Errors
ParseError,
MissingRequiredError,
MissingPositionalError,
InvalidValueError,
UnknownOptionError,
TypeCoercionError,
UnknownCommandError,
formatErrors,
formatErrorsTokens,
createErrorMessage,
createErrorTokens,
hasErrors,
throwIfErrors,
// Constants
DEFAULT_SEPARATORS,
URL_PROTOCOLS
} from 'cli-args-parser'
MIT
FAQs
Expressive CLI argument parser with schema validation
The npm package cli-args-parser receives a total of 2,006 weekly downloads. As such, cli-args-parser popularity was classified as popular.
We found that cli-args-parser demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

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.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.