Elegant CLI Builder
- Zero dependency, fast and lightweight (based on native
util.parseArgs) - Smart value parsing with typecast and boolean shortcuts
- Nested sub-commands with lazy and async loading
- Pluggable and composable API with auto generated usage
npx nypm add -D cittyimport{defineCommand,runMain}from"citty";constmain=defineCommand({meta: {name: "hello",version: "1.0.0",description: "My Awesome CLI App",},args: {name: {type: "positional",description: "Your name",required: true,},friendly: {type: "boolean",description: "Use friendly greeting",},},setup({ args }){console.log(`now setup ${args.command}`);},cleanup({ args }){console.log(`now cleanup ${args.command}`);},run({ args }){console.log(`${args.friendly ? "Hi" : "Greetings"}${args.name}!`);},});runMain(main);node index.mjs john
# Greetings john!Sub commands can be nested recursively. Use lazy imports for large CLIs to avoid loading all commands at once.
import{defineCommand,runMain}from"citty";constsub=defineCommand({meta: {name: "sub",description: "Sub command"},args: {name: {type: "positional",description: "Your name",required: true},},run({ args }){console.log(`Hello ${args.name}!`);},});constmain=defineCommand({meta: {name: "hello",version: "1.0.0",description: "My Awesome CLI App"},subCommands: { sub },});runMain(main);Subcommands support meta.alias (e.g., ["i", "add"]) and meta.hidden: true to hide from help output.
For large CLIs, lazy load sub commands so only the executed command is imported:
constmain=defineCommand({meta: {name: "hello",version: "1.0.0",description: "My Awesome CLI App"},subCommands: {sub: ()=>import("./sub.mjs").then((m)=>m.default),},});meta, args, and subCommands all accept Resolvable<T> values — a value, Promise, function, or async function — enabling lazy and dynamic resolution.
Commands support setup and cleanup functions called before and after run(). Only the executed command's hooks run. cleanup always runs, even if run() throws.
constmain=defineCommand({meta: {name: "hello",version: "1.0.0",description: "My Awesome CLI App"},setup(){console.log("Setting up...");},cleanup(){console.log("Cleaning up...");},run(){console.log("Hello World!");},});Plugins extend commands with reusable setup and cleanup hooks:
import{defineCommand,defineCittyPlugin,runMain}from"citty";constlogger=defineCittyPlugin({name: "logger",setup({ args }){console.log("Logger setup, args:",args);},cleanup(){console.log("Logger cleanup");},});constmain=defineCommand({meta: {name: "hello",description: "My CLI App"},plugins: [logger],run(){console.log("Hello!");},});runMain(main);Plugin setup hooks run before the command's setup (in order), cleanup hooks run after (in reverse). Plugins can be async or factory functions.
| Type | Description | Example |
|---|---|---|
positional | Unnamed positional args | cli <name> |
string | Named string options | --name value |
boolean | Boolean flags, supports --no- negation | --verbose |
enum | Constrained to options array | --level=info|warn|error |
| Option | Description |
|---|---|
description | Help text shown in usage output |
required | Whether the argument is required |
default | Default value when not provided |
alias | Short aliases (e.g., ["f"]). Not for positional |
valueHint | Display hint in help (e.g., "host" renders --name=<host>) |
constmain=defineCommand({args: {name: {type: "positional",description: "Your name",required: true},friendly: {type: "boolean",description: "Use friendly greeting",alias: ["f"]},greeting: {type: "string",description: "Custom greeting",default: "Hello"},level: {type: "enum",description: "Log level",options: ["debug","info","warn","error"],default: "info",},},run({ args }){console.log(`${args.greeting}${args.name}! (level: ${args.level})`);},});Boolean args support --no- prefix. The negative variant appears in help when default: true or negativeDescription is set.
Kebab-case args can be accessed as camelCase: args["output-dir"] and args.outputDir both work.
--help / -h and --version / -v are handled automatically. Disabled if your command defines args with the same names or aliases.
| Function | Description |
|---|---|
defineCommand(def) | Type helper for defining commands |
runMain(cmd, opts?) | Run a command with usage support and graceful error handling |
createMain(cmd) | Create a wrapper that calls runMain when invoked |
runCommand(cmd, opts) | Parse args and run command/sub-commands; access result from return value |
parseArgs(rawArgs, argsDef) | Parse input arguments and apply defaults |
renderUsage(cmd, parent?) | Render command usage to a string |
showUsage(cmd, parent?) | Render usage and print to console |
defineCittyPlugin(def) | Type helper for defining plugins |
- Clone this repository
- Install latest LTS version of Node.js
- Enable Corepack using
corepack enable - Install dependencies using
pnpm install - Run interactive tests using
pnpm dev
Made with 💛 Published under MIT License.