Citty: Elegant, zero-dependency CLI builder for Node.js.
Important: Keep AGENTS.md updated with project information.
src/
βββ index.ts # Public API re-exports
βββ types.ts # All type definitions (ArgDef, CommandDef, ParsedArgs, etc.)
βββ command.ts # defineCommand(), runCommand(), resolveSubCommand()
βββ main.ts # runMain(), createMain() β CLI entry point with --help/--version
βββ args.ts # parseArgs() β argument parsing and validation
βββ usage.ts # renderUsage(), showUsage() β help text generation
βββ _parser.ts # Low-level parser wrapping node:util.parseArgs (internal)
βββ _utils.ts # toArray, formatLineColumns, resolveValue, CLIError (internal)
βββ _color.ts # ANSI color helpers with NO_COLOR support (internal)
test/ # Vitest tests (args, parser, main, usage, utils)
playground/ # Example CLI apps (run with `pnpm play`)
Internal files use _ prefix and are not exported from index.ts.
// Core
defineCommand(def) // Define a typed command
runCommand(cmd, opts) // Execute a command programmatically
runMain(cmd, opts?) // CLI entry point (handles --help, --version, process.exit)
createMain(cmd) // Returns a function wrapping runMain()
// Utilities
parseArgs(rawArgs, argsDef) // Parse CLI arguments against definitions
renderUsage(cmd, parent?) // Generate help text string
showUsage(cmd, parent?) // Print help to consolepositionalβ unnamed args (cli <name>)stringβ named string options (--name value)booleanβ flags (--verbose,--no-verbosefor negation)enumβ constrained tooptionsarray (--level=info|warn|error)
Arguments are case-agnostic β --user-name and --userName resolve to the same value via auto-generated aliases (uses scule for case conversion) and a Proxy-based accessor.
setup() β resolve subcommand or run() β cleanup() (always runs in finally)
Resolvable<T> = T | Promise<T> | (() => T) | (() => Promise<T>) β used for meta, args, and subCommands to support dynamic imports.
Custom CLIError class with error codes: EARG, E_UNKNOWN_COMMAND, E_NO_COMMAND, E_NO_VERSION. Usage is auto-shown before error messages in runMain.
Zero runtime dependencies. Only scule is used from source code (bundled at build time).
| Command | Description |
|---|---|
pnpm dev |
Vitest watch mode |
pnpm test |
Lint + typecheck + vitest with coverage |
pnpm test:types |
Type checking via tsgo --noEmit |
pnpm lint |
oxlint . && oxfmt --check |
pnpm fmt |
oxlint . --fix && oxfmt |
pnpm build |
Build with obuild β dist/ |
pnpm play |
Run playground CLI |
- Build: obuild (single entry
src/index.tsβdist/index.mjs) - Test: Vitest with
@vitest/coverage-v8, typecheck enabled - Lint: oxlint (plugins: unicorn, typescript, oxc)
- Format: oxfmt
- TypeScript: strict mode,
nodenextmodule,verbatimModuleSyntax
- ESM with explicit
.tsextensions in imports - Internal files prefixed with
_(not exported) - Tests use inline snapshots for usage output verification
- Colors respect
NO_COLOR,TERM=dumb,TEST,CIenv vars --no-flagnegation requiresdefault: trueornegativeDescriptionon the arg def