CLI Forge vs. clipanion

Clipanion is a class-based CLI library created by the author of Yarn. It powers Yarn Berry.

CLI Forge strengths

clipanion strengths

  • State machine parser — Commands compile into an optimized state machine, enabling command overloading and path overlaps disambiguated by required options.
  • Command proxying — Option.Proxy() captures remaining args transparently without --. Useful for wrapper commands.
  • Typanion validation — Tight integration with typanion for composable runtime validation.
  • Battle-tested — Powers Yarn Berry, one of the most complex CLIs in the JavaScript ecosystem.

Side-by-side example

The same CLI — a greet command with hello and goodbye subcommands — implemented in both libraries.

import { Builtins, Cli, Command, Option } from 'clipanion';

class HelloCommand extends Command {
  static override paths = [['hello']];

  static override usage = Command.Usage({
    description: 'Say hello to someone',
  });

  name = Option.String('--name', 'World', {
    description: 'Name to greet',
  });

  uppercase = Option.Boolean('--uppercase', false, {
    description: 'Print greeting in uppercase',
  });

  async execute() {
    const msg = `Hello, ${this.name}!`;
    this.context.stdout.write(
      (this.uppercase ? msg.toUpperCase() : msg) + '\n'
    );
  }
}

class GoodbyeCommand extends Command {
  static override paths = [['goodbye']];

  static override usage = Command.Usage({
    description: 'Say goodbye to someone',
  });

  name = Option.String('--name', 'World', {
    description: 'Name to bid farewell',
  });

  formal = Option.Boolean('--formal', false, {
    description: 'Use formal farewell',
  });

  async execute() {
    const msg = this.formal
      ? `Farewell, ${this.name}.`
      : `Bye, ${this.name}!`;
    this.context.stdout.write(msg + '\n');
  }
}

const cli = new Cli({ binaryName: 'greet' });
cli.register(HelloCommand);
cli.register(GoodbyeCommand);
cli.register(Builtins.HelpCommand);
cli.runExit(process.argv.slice(2));
import { cli } from 'cli-forge';

cli('greet')
  .command('hello', {
    description: 'Say hello to someone',
    builder: (args) =>
      args
        .option('name', {
          type: 'string',
          description: 'Name to greet',
          default: 'World',
        })
        .option('uppercase', {
          type: 'boolean',
          description: 'Print greeting in uppercase',
          default: false,
        }),
    handler: (args) => {
      const msg = `Hello, ${args.name}!`;
      console.log(args.uppercase ? msg.toUpperCase() : msg);
    },
  })
  .command('goodbye', {
    description: 'Say goodbye to someone',
    builder: (args) =>
      args
        .option('name', {
          type: 'string',
          description: 'Name to bid farewell',
          default: 'World',
        })
        .option('formal', {
          type: 'boolean',
          description: 'Use formal farewell',
          default: false,
        }),
    handler: (args) => {
      console.log(
        args.formal ? `Farewell, ${args.name}.` : `Bye, ${args.name}!`
      );
    },
  })
  .forge();

Back to comparison overview · View all framework examples