Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

@black-flag/core

Xunnamius278MIT3.0.2TypeScript support: included

A declarative framework for building fluent, deeply hierarchical command line interfaces with yargs

cli, parse, command, line, interface, parameters, arguments, user, option, yargs, optstring

readme

A declarative wrapper around Yargs for building beautiful, fluent command line interfaces
$ black-pearl hoist the colors --black-flag


Black Lives Matter! Last commit timestamp Codecov Source license Uses Semantic Release!

NPM version Monthly Downloads


Black Flag 🏴

Black Flag is a fairly thin library that wraps yargs, extending its capabilities with several powerful declarative features. It can be used to create simple single-level CLIs or deeply nested sprawling interfaces alike.

Black Flag was built as a drop-in replacement for vanilla Yargs, specifically for users of the yargs::commandDir() (which has its issues). Its features include:


Black Flag is tested on Ubuntu and Windows 10, and like Yargs tracks Node.js LTS versions. Also comes with first-class support for both CJS and ESM source.


‌ ‌ ‌ ‌❖ ‌ ‌ Quick start\ ‌ ‌ ‌ ‌‌❖ ‌ ‌ Step-by-step getting started guide\ ‌ ‌ ‌ ‌‌❖ ‌ ‌ Black Flag versus vanilla Yargs\ ‌ ‌ ‌ ‌‌❖ ‌ ‌ Simple demo CLI project (or npx -p @black-flag/demo myctl --help)\ ‌ ‌ ‌ ‌‌❖ ‌ ‌ Black Flag recipes for solving common CLI design problems\ ‌ ‌ ‌ ‌‌❖ ‌ ‌ Black Flag's intro examples (which are just Yargs's intro examples rewritten with Black Flag)


‌ ‌ ‌ ‌‌❖ ‌ ‌ Builder API (essentially yargs::options's opt keys)\ ‌ ‌ ‌ ‌‌❖ ‌ ‌ Command module API\ ‌ ‌ ‌ ‌‌❖ ‌ ‌ Configuration hooks API\ ‌ ‌ ‌ ‌‌❖ ‌ ‌ BFE extended builder API\ ‌ ‌ ‌ ‌‌❖ ‌ ‌ All @black-flag/core exports\ ‌ ‌ ‌ ‌‌❖ ‌ ‌ All @black-flag/core/util exports\ ‌ ‌ ‌ ‌‌❖ ‌ ‌ All @black-flag/extensions exports\ ‌ ‌ ‌ ‌‌❖ ‌ ‌ All @black-flag/checks exports


[!TIP]

If you find yourself a fan of Black Flag's more declarative DX and want to go all the way, check out Black Flag Extensions (BFE). BFE is a collection of surprisingly simple set-theoretic APIs that build on yargs::options() for a fully declarative developer experience. BFE also protects you from a couple Yargs footguns that Black Flag by itself cannot.

You may also be interested in Black Flag Checks (BFC), which offers several pluggable yargs::check functions—like checkIsNotNegative and checkArrayNotEmpty—built to work with BFE.



Install

To install:

npm install @black-flag/core

And if you're ready to go all in on Black Flag's declarative API, check out Black Flag Extensions:

npm install @black-flag/extensions

Quick Start

Install Black Flag:

npm install @black-flag/core

Create the file that will run your CLI, perhaps at ./cli.js:

[!TIP]

Both CJS and ESM source is acceptable!

#!/usr/bin/env node

import { runProgram } from '@black-flag/core';
export default runProgram(import.meta.resolve('./commands'));

Then create the root command, perhaps at ./commands/index.js:

export const name = 'pirate-parser';
export const usage = 'Usage: $0 <cmd> [args]';

Finally, create a subcommand, perhaps at ./commands/hello.js:

export const command = '$0 [name]';

export const description =
  'Welcome ter black flag, a declarative wrapper around yargs!';

export function builder(blackFlag, helpOrVersionSet, argv) {
  blackFlag.positional('name', {
    type: 'string',
    default: 'Cambi',
    describe: 'The name to say hello to'
  });

  // A special --attention flag only available when greeting the captain!
  if (helpOrVersionSet || argv?.name === 'CAPTAIN') {
    return {
      attention: {
        boolean: true,
        description: `Alert the watch${
          helpOrVersionSet ? ' (only available when greeting captain)' : ''
        }`
      }
    };
  }
}

export async function handler(argv) {
  if (argv.attention) {
    console.log('-!- Captain is on the bridge -!-');
  }

  console.log(`Hello ${argv.name}, welcome to Black Flag!`);
}

[!TIP]

This example demonstrates a multi-level or "nested" command, i.e. a root command with a subcommand. If instead we wanted to make a simple single-level CLI with no subcommands at all, we could merge ./commands/hello.js's exports (handler, builder, etc) into ./commands/index.js.

How you design your CLI is up to you!

Then run it:

node cli.js --help
Usage: pirate-parser <cmd> [args]

Commands:
  pirate-parser hello  Welcome ter black flag, a declarative wrapper around yargs!

Options:
  --help     Show help text                                                 [boolean]
  --version  Show version number                                            [boolean]

node cli.js hello --help
Usage: pirate-parser hello [name]

Welcome ter black flag, a declarative wrapper around yargs!

Positionals:
  name  The name to say hello to                          [string] [default: "Cambi"]

Options:
  --help       Show help text                                               [boolean]
  --attention  Alert the watch (only available when greeting captain)       [boolean]

node cli.js hello Parrot
Hello Parrot, welcome to Black Flag!

node cli.js hello CAPTAIN
Hello CAPTAIN, welcome to Black Flag!

node cli.js hello Parrot --attention
Usage: pirate-parser hello [name]

Positionals:
  name  The name to say hello to                          [string] [default: "Cambi"]

Options:
  --help  Show help text                                                    [boolean]

Unknown argument: attention

node cli.js hello CAPTAIN --attention
-!- Captain is on the bridge -!-
Hello CAPTAIN, welcome to Black Flag!

[!TIP]

Not sure what makes Black Flag "more declarative" than Yargs? Compare this quick start example to the vanilla Yargs version.

Next steps:

Appendix 🏴

Further documentation can be found under docs/ and docs/api/. Common CLI design "recipes" can be found under examples/.

Terminology

Term Description
command A "command" is a functional unit associated with a configuration file and represented internally as a trio of programs: effector, helper, and router. Further, each command is classified as one of: "pure parent" (root and parent), "parent-child" (parent and child), or "pure child" (child).
program A "program" is a Yargs instance wrapped in a Proxy granting the instance an expanded set of features. Programs are represented internally by the Program type.
root The tippy top command in your hierarchy of commands and the entry point for any Black Flag application. Also referred to as the "root command".
default command A "default command" is Yargs parlance for the CLI entry point. Technically there is no concept of a "default command" at the Black Flag level, though there is the root command.

Inspiration

<summary>Expand details</summary>

I love Yargs 💕 Yargs is the greatest! I've made dozens of CLI tools with Yargs, each with drastically different interfaces and requirements. Some help manage critical systems.

As I was copying-and-pasting some configs from past projects for yet another tool, I realized the (irritatingly disparate 😖) structures of my CLI projects up until this point were converging on a set of personal conventions around Yargs. And, as I'm always eager to "optimize" my workflows, I wondered how much common functionality could be abstracted away.

The goal: make my CLIs more stable upon release, much faster to build, and more pleasant to test. And also avoid Yargs's most egregious footguns. But perhaps most important: I wanted CLIs that would remain simple and consistent to maintain.

Throw in a re-watch of the PotC series and Black Flag was born! 🏴‍☠🍾

Published Package Details

This is a CJS2 package with statically-analyzable exports built by Babel for use in Node.js versions that are not end-of-life. For TypeScript users, this package supports both "Node10" and "Node16" module resolution strategies.

<summary>Expand details</summary>

That means both CJS2 (via require(...)) and ESM (via import { ... } from ... or await import(...)) source will load this package from the same entry points when using Node. This has several benefits, the foremost being: less code shipped/smaller package size, avoiding dual package hazard entirely, distributables are not packed/bundled/uglified, a drastically less complex build process, and CJS consumers aren't shafted.

Each entry point (i.e. ENTRY) in package.json's exports[ENTRY] object includes one or more export conditions. These entries may or may not include: an exports[ENTRY].types condition pointing to a type declaration file for TypeScript and IDEs, a exports[ENTRY].module condition pointing to (usually ESM) source for Webpack/Rollup, a exports[ENTRY].node and/or exports[ENTRY].default condition pointing to (usually CJS2) source for Node.js require/import and for browsers and other environments, and other conditions not enumerated here. Check the package.json file to see which export conditions are supported.

Note that, regardless of the { "type": "..." } specified in package.json, any JavaScript files written in ESM syntax (including distributables) will always have the .mjs extension. Note also that package.json may include the sideEffects key, which is almost always false for optimal tree shaking where appropriate.

License

See LICENSE.

Contributing and Support

New issues and pull requests are always welcome and greatly appreciated! 🤩 Just as well, you can star 🌟 this project to let me know you found it useful! ✊🏿 Or buy me a beer, I'd appreciate it. Thank you!

See CONTRIBUTING.md and SUPPORT.md for more information.

Contributors

All Contributors

Thanks goes to these wonderful people (emoji key):

Bernard
Bernard

🚇 💻 📖 🚧 ⚠️ 👀
Add your contributions

This project follows the all-contributors specification. Contributions of any kind welcome!

changelog

Changelog

All notable changes to this project will be documented in this auto-generated file. The format is based on Conventional Commits; this project adheres to Semantic Versioning.


@black-flag/core@3.0.0 (2025-05-28)

💥 BREAKING CHANGES 💥

  • Minimum supported node version is now 20.18.0

🪄 Fixes

  • src: make GracefulEarlyExitError call signature consistent with CliError (b60bc29)

⚙️ Build System

  • Bump yargs from 17.7.2 to 18.0.0 and adjust usage accordingly (379d98d)
  • deps: bump core-js from 3.41.0 to 3.42.0 (870c143)
  • deps: bump type-fest from 4.38.0 to 4.41.0 (f88034e)
  • package: bump experimental yargs-parser library version to 22.x (d6b35b9)
  • package: drop support for node@18 (7a70c7e)

🏗️ Patch @black-flag/core@3.0.2 (2025-06-14)

⚙️ Build System

  • deps: bump @-xun/fs from 1.0.0 to 2.0.0 (c8a8693)
  • deps: bump @-xun/js from 1.1.1 to 2.0.0 (134f715)
  • deps: bump core-js from 3.42.0 to 3.43.0 (cafae4b)
  • deps: bump rejoinder from 1.2.5 to 2.0.1 (85cbf9d)

🏗️ Patch @black-flag/core@3.0.1 (2025-05-31)

🪄 Fixes

  • Account for ConfigurationHooks in contravariant position in runProgram params (19fa7dc)

@black-flag/core@2.0.0 (2025-03-14)

💥 BREAKING CHANGES 💥

  • All instances in source where commandModulePath appeared have been replaced by commandModulesPath. This includes the call signatures of functions like makeRunner.

    The fix is simple: find-and-replace all instances of commandModulePath with commandModulesPath.

  • Along with implementing the errorHandlingBehavior DX improvement in makeRunner, this update also addresses several small discrepancies in the behavior of configureProgram, runProgram, and makeRunner. These functions should now behave identically where appropriate (i.e. as described in their documentation), including consistently triggering the same error handling behavior at the same points for the same reasons given the same inputs.

    Additionally, non-graceful errors that are not handled by ConfigureErrorHandlingEpilogue will be consistently presented to the user as framework errors (assertion failures). As such, runProgram (and the low-order function returned by makeRunner) should no longer throw in some edge cases, such as when being passed a rejected promise or when a hook that is evaluated early throws.

  • Better help text output for dynamic options

    With this change, Black Flag lets Yargs fully parse argv and run through the builder twice before bailing to print help text when --help (or the equivalent option) is given.

    This allows input that triggers dynamic options like my-command --flag-1 --flag-2 --help to show help text specific to the final resolved builder configurations of --flag-1 --flag-2 rather than always showing the most generic help text, which was the behavior of older Black Flag versions. See documentation for details.

  • Do not output entire help text when a command fails

    Skip all but the first line of usage text in output by default.

  • Positionals are now available to builders alongside all other flags

    This is how vanilla Yargs does it. In earlier versions, builders' argv param had all positionals dumped into argv._ due to a suboptimal parsing extension.

  • Show any available child commands in ALL error text

    This includes when trying to use a command that is not found.

  • Surface new CliError::showHelp parameter values

    • "short"/true, which will print the command help text without all but the first line of usage text.

    • "default", which will defer to ExecutionContext::state.showHelpOnFail.

    • "full", which will force the old functionality.

    • false, which ensures help text is never printed with respect to the current error instance.

  • Upgrade ExecutionContext::state.showHelpOnFail to allow configuration of help text output style

    New output style options include "short" (first line of usage only, this is now the default) and "full" (full usage string). Also allows configuration of which error kinds trigger help text output and which do not. See documentation for details.

  • $executionContext and $artificiallyInvoked symbols are now drawn from the global symbol registry. They will not match symbols from previous versions!

  • ErrorMessage export was renamed to BfErrorMessage

✨ Features

  • src: export getDeepestErrorCause under /util (bf001c4)
  • src: implement errorHandlingBehavior option in makeRunner (5e4eb3d)
  • src: make expectedHelpTextRegExp utility a public export (8f2cb13)
  • src: make positionals available to builders normally (no longer in argv._) (42367ce)
  • src: output help text with greater fidelity (02a497f) see #172
  • Support Windows-style paths (cd288c5)

🪄 Fixes

  • Fix Windows interop issues (b3abf95) see #174
  • package: add @types/yargs as production dependency (8d50a56)
  • src: ensure ESM file bare exports are seen by black flag (389a2dc)
  • src: fix node@18 interop issue (c1b5f61) see #173
  • src: improve isX type assertion exports (b980544)
  • src: improve intellisense across various exports (d6b1e73)
  • src: more consistently handle various errors; improve output fidelity (9412aa6)
  • src: throw upon encountering a command export with invalid yargs DSL (7312b8d)
  • src: throw upon encountering an async builder export (78eb0a2)

⚙️ Build System

  • deps: bump type-fest from 4.35.0 to 4.36.0 (33b2099)
  • deps: bump type-fest from 4.36.0 to 4.37.0 (cdd8f61)
  • husky: force lint-staged (via husky) to only use global config file (5d3f2cc)
  • package: add @-xun/symbiote dev dependency (074a930)
  • package: prune unnecessary dependencies (1b5cdbf)
  • Throw in runProgram when an incompatible Node.js runtime version is detected (cb56f8d)

🧙🏿 Refactored

  • Make exported symbols cross-realm (af78a8f)
  • Rename and restructure exports for better docs generation (8303ba7)
  • src: ErrorMessage export is now BfErrorMessage (3918a29)

🔥 Reverted

  • "build(deps): bump core-js from 3.40.0 to 3.41.0" (488206d)

🏗️ Patch @black-flag/core@2.0.5 (2025-03-28)

🪄 Fixes

  • assets/transformers: improve error output when handling internal yargs error instances (269046f)
  • src: loosen builder function form return type (f15e922)

⚙️ Build System

  • readme: improve documentation (5231dd4)

🏗️ Patch @black-flag/core@2.0.4 (2025-03-25)

🪄 Fixes

  • src: allow Configuration::builder function value to explicitly accept void return type (2676cbe)

⚙️ Build System

  • deps: bump type-fest from 4.37.0 to 4.38.0 (9e25b0c)

🏗️ Patch @black-flag/core@2.0.2 (2025-03-19)

🪄 Fixes

  • packages/extensions: improve safeDeepClone to account for own symbol properties (119919e)
  • readme: update quick start example (bdafbf8)
  • readme: use latest interface to extract positional in quick start example (33895e7)
  • src: do not make context descriptors unenumerable (2e6c05b)

⚙️ Build System

  • deps: bump @-xun/js from 1.0.0 to 1.1.0 (0adf956)
  • deps: bump @-xun/js from 1.1.0 to 1.1.1 (f3f1f74)

🏗️ Patch @black-flag/core@2.0.1 (2025-03-17)

🪄 Fixes

⚙️ Build System

  • deps: bump core-js from 3.40.0 to 3.41.0 (9371719)

@black-flag/core@1.3.0 (2024-07-12)

✨ Features

  • clierror: add dangerouslyFatal option, update cause option handling (1c369fb)

🏗️ Patch @black-flag/core@1.3.2 (2024-07-12)

🪄 Fixes

  • src: tweak error handling debug verboseness in certain edge cases (96ce293)

🏗️ Patch @black-flag/core@1.3.1 (2024-07-12)

🪄 Fixes

  • src: add trap door to alert developer when erroneously re-entering top-level error handler (99e2b3a)

@black-flag/core@1.2.0 (2024-03-18)

✨ Features

  • Add showHelp option to CliError (b5a1e58)

🏗️ Patch @black-flag/core@1.2.7 (2024-06-30)


🏗️ Patch @black-flag/core@1.2.6 (2024-06-02)

🪄 Fixes

  • src: ignore --help and --version if they occur after -- in argv (35f66cc)

🏗️ Patch @black-flag/core@1.2.5 (2024-05-30)

🪄 Fixes

  • src: permanently fix --version support regression in node@22 (c201c2f)

⚙️ Build System

  • package: append node@22 to supported node versions (98815d1)

🏗️ Patch @black-flag/core@1.2.4 (2024-03-27)

🪄 Fixes

  • src: explicitly ignore .d.ts files within command dirs (d6618d3)

🏗️ Patch @black-flag/core@1.2.3 (2024-03-27)

🪄 Fixes

  • package: bump minimum node support to 20 LTS (4b8c975)
  • src: ignore unknown file extension errors when discovering commands (4babf12)

🏗️ Patch @black-flag/core@1.2.2 (2024-03-21)

🪄 Fixes

  • No longer include default command when listing subcommands (be2960a)

⚙️ Build System

  • husky: update to latest hooks (75d5c66)
  • src: do not filter to own methods when proxying and rebinding (8bb0254)

🏗️ Patch @black-flag/core@1.2.1 (2024-03-19)


@black-flag/core@1.1.0 (2023-12-31)

✨ Features

  • src: support file://-style URLs (0e5067e)

🪄 Fixes

  • src: ensure --version is included in help text output when relevant (4f159dc)

🏗️ Patch @black-flag/core@1.1.4 (2024-03-16)


🏗️ Patch @black-flag/core@1.1.3 (2024-03-15)

🪄 Fixes

  • Add support for export default X syntax (bad391d)
  • Ensure demandOption is properly supported (2f205c1)

🏗️ Patch @black-flag/core@1.1.2 (2023-12-31)

🪄 Fixes

  • readme: move parserConfiguration call out of dead zone in example (f79c114)

🏗️ Patch @black-flag/core@1.1.1 (2023-12-31)

🪄 Fixes

  • Fix Node10 type resolution failure (b6178c9)

@black-flag/core@1.0.0 (2023-12-29)

✨ Features

  • src: disallow creating commands with conflicting names/aliases (78bf8ff)

🪄 Fixes

  • Rename package from "black-flag" to "@black-flag/core" npm typosquat workaround (292ead5)

⚙️ Build System

  • tsconfig: fix internal path resolution (fbe3a69)
  • tsconfig: upgrade to NodeNext (d3a499e)