CLI Forge vs. citty

citty is a zero-dependency CLI builder from the UnJS ecosystem, built on Node.js's native util.parseArgs.

CLI Forge strengths

Shared strengths

citty strengths

Side-by-side example

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

import { defineCommand, runMain } from 'citty';

const hello = defineCommand({
  meta: { name: 'hello', description: 'Say hello to someone' },
  args: {
    name: {
      type: 'string',
      description: 'Name to greet',
      default: 'World',
    },
    uppercase: {
      type: 'boolean',
      description: 'Print greeting in uppercase',
      default: false,
    },
  },
  run({ args }) {
    const msg = `Hello, ${args.name}!`;
    console.log(args.uppercase ? msg.toUpperCase() : msg);
  },
});

const goodbye = defineCommand({
  meta: { name: 'goodbye', description: 'Say goodbye to someone' },
  args: {
    name: {
      type: 'string',
      description: 'Name to bid farewell',
      default: 'World',
    },
    formal: {
      type: 'boolean',
      description: 'Use formal farewell',
      default: false,
    },
  },
  run({ args }) {
    console.log(
      args.formal ? `Farewell, ${args.name}.` : `Bye, ${args.name}!`
    );
  },
});

const main = defineCommand({
  meta: { name: 'greet', description: 'A greeting CLI' },
  subCommands: { hello, goodbye },
});

runMain(main);
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