CLI Forge vs. cac

cac is a zero-dependency, single-file CLI library used by Vite and Vitest.

CLI Forge strengths

cac strengths

  • Zero dependencies, single file — Extremely lightweight (~3.5KB minified).
  • Dot-notation — --env.API_SECRET foo automatically creates nested objects. CLI Forge uses explicit type: 'object' with properties.
  • Deno support — Available via JSR.
  • Event system — Listen for command events (command:*) for custom routing logic.
  • Proven at scale — Powers Vite, Vitest, and other popular tools.

Side-by-side example

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

import cac from 'cac';

const cli = cac('greet');

cli
  .command('hello', 'Say hello to someone')
  .option('--name <name>', 'Name to greet', { default: 'World' })
  .option('--uppercase', 'Print greeting in uppercase', { default: false })
  .action((opts) => {
    const msg = `Hello, ${opts.name}!`;
    console.log(opts.uppercase ? msg.toUpperCase() : msg);
  });

cli
  .command('goodbye', 'Say goodbye to someone')
  .option('--name <name>', 'Name to bid farewell', { default: 'World' })
  .option('--formal', 'Use formal farewell', { default: false })
  .action((opts) => {
    console.log(
      opts.formal ? `Farewell, ${opts.name}.` : `Bye, ${opts.name}!`
    );
  });

cli.help();
cli.parse();
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