npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kelceyp/clibuilder

v0.2.1

Published

Build CLIs quickly with consistent UX, typed params, and interactive prompting

Downloads

78

Readme

CLI Builder

⚠️ API Draft - Subject to Change

Build CLIs quickly with consistent UX, typed params, and interactive prompting.

Current Status

🎉 PROJECT COMPLETE - All 9 implementation bunches finished! 🎉

Final Metrics:

  • 943 tests passing (100%)
  • Sub-millisecond dispatch performance
  • 1000+ lines of user documentation
  • Full requirements compliance
  • Fully integrated help system
  • Production-ready for npm publish! 🚀

Track progress: See tracking/STATUS.md for current version status (v0.1 complete, v0.2 in progress)

Project Goals

Build command-line interfaces with:

  • Fast Development: Fluent builder API for quick CLI construction
  • Type Safety: TypeScript generics infer parameter types automatically
  • Consistent UX: Built-in colors, spinners, and error formatting
  • Flexible Resolution: Multi-source parameter resolution (argv → stdin → env → default → prompt)
  • Nested Commands: Composite pattern for arbitrary command nesting (mycli db migrations create)
  • Interactive Prompts: Auto-prompting with TTY detection (safe for CI/non-TTY)
  • Works Everywhere: Node ≥18 and Bun ≥1.1; bash/zsh/fish compatible

Full spec: @tracking/v0.1/requirements.md (v0.1 complete), @tracking/v0.2/requirements.md (v0.2 in progress)

Installation

# Placeholder - package not yet published
npm install @kelceyp/clibuilder
# or
bun add @kelceyp/clibuilder

Getting Started

Here's a comprehensive example showing the full builder chain:

import { create, createCommand, createCommandGroup, createParam } from '@kelceyp/clibuilder';

// Define a command with typed parameters
const backupCommand = createCommand('backup')
  .summary('Create a database backup')
  .description('Creates a compressed backup of the specified database')
  .aliases(['bak'])

  // Positional parameter (required)
  .param((p) =>
    p.name('database')
      .type('string')
      .positional(0)
      .required()
      .validate((value) => {
        if (value.length === 0) return 'Database name cannot be empty';
        return true;
      })
  )

  // Optional flag parameter with environment variable fallback
  .param((p) =>
    p.name('output')
      .type('string')
      .flag('output', 'o')
      .env('BACKUP_OUTPUT_DIR')
      .default('./backups')
      .optional()
  )

  // Boolean flag parameter
  .param((p) =>
    p.name('compress')
      .type('boolean')
      .flag('compress', 'c')
      .default(false)
  )

  // Array parameter
  .param((p) =>
    p.name('exclude')
      .type('string[]')
      .flag('exclude', 'e')
      .optional()
  )

  // Define the command handler with typed context
  .run(async (ctx) => {
    const { database, output, compress, exclude } = ctx.params;

    // ctx.params is fully typed based on the params defined above:
    // - database: string (required)
    // - output: string (has default, always present)
    // - compress: boolean (has default, always present)
    // - exclude: string[] | undefined (optional)

    // Access provenance to see where values came from
    const dbProvenance = ctx.provenance.database;
    if (database === 'prod' && dbProvenance.source === 'default') {
      throw new Error('Production database must be explicitly specified, not defaulted');
    }

    const spinner = ctx.spinner('Creating backup...');
    spinner.start();

    try {
      // Simulate backup operation
      await new Promise((resolve) => setTimeout(resolve, 2000));
      spinner.succeed(`Backed up ${database} to ${output}`);
    } catch (error) {
      spinner.fail('Backup failed');
      throw error;
    }
  })

  .onError({ exitCode: 1, showStack: 'auto' })
  .build();

// Define a migration command
const createMigrationCommand = createCommand('create')
  .summary('Create a new migration file')
  .param((p) =>
    p.name('name')
      .type('string')
      .positional(0)
      .required()
  )
  .run(async (ctx) => {
    ctx.logger.info(`Creating migration: ${ctx.params.name}`);
  })
  .build();

const runMigrationsCommand = createCommand('run')
  .summary('Run pending migrations')
  .param((p) =>
    p.name('target')
      .type('string')
      .flag('target', 't')
      .optional()
  )
  .run(async (ctx) => {
    ctx.logger.info('Running migrations...');
  })
  .build();

// Create a nested command group structure
const dbGroup = createCommandGroup('db')
  .summary('Database operations')
  .description('Commands for database management and maintenance')
  .command(backupCommand)
  .defaultChild('backup') // Run backup command when 'mycli db' is invoked alone

  // Nested command group for migrations
  .commandGroup('migrations')
    .summary('Migration management')
    .command(createMigrationCommand)
    .command(runMigrationsCommand)
    .defaultChild('run') // Run migrations when 'mycli db migrations' is invoked alone
    .build() // Returns to parent group

  .build();

// Create the top-level app and register commands/groups
const app = create()
  .name('mycli')
  .version('1.0.0')
  .description('My awesome CLI tool')
  .strict(true)
  .colorScheme({
    primary: '#00D9FF',
    accent: '#A855F7',
    warn: '#FBBF24',
    error: '#EF4444'
  })
  .spinner({ style: 'dots', color: 'cyan' })

  // Register the command group
  .group(dbGroup)

  // Register a top-level command
  .command(
    createCommand('init')
      .summary('Initialize configuration')
      .run(async (ctx) => {
        ctx.logger.info('Initializing...');
      })
      .build()
  );

// Run the CLI (in your CLI entry file)
// app.run(process.argv.slice(2));

Usage Examples

# Run a nested command with parameters
mycli db backup mydb --output ./backups --compress --exclude logs --exclude cache

# Use short flags
mycli db backup mydb -o ./backups -c

# Run nested migration commands
mycli db migrations create add_users_table
mycli db migrations run --target 20231215

# Use defaultChild - 'mycli db' automatically runs 'mycli db backup'
mycli db mydb  # Equivalent to: mycli db backup mydb

# Get help at any level
mycli --help
mycli db --help
mycli db migrations --help
mycli db backup --help

# Debug parameter resolution with --debug flag
mycli db backup mydb --output ./backups --debug

Key Features

Provenance Tracking

Every parameter value includes provenance metadata showing where it came from and the original raw input:

const cmd = createCommand('deploy')
  .param((p) => p.name('env').type('string').positional(0).required())
  .run(async (ctx) => {
    const env = ctx.params.env;

    // Access provenance to see where the value came from
    const envProvenance = ctx.provenance.env;

    console.log(`Environment: ${envProvenance.value}`);
    console.log(`Source: ${envProvenance.source}`); // 'argv' | 'env' | 'stdin' | 'default' | 'prompt'
    console.log(`Raw input: ${envProvenance.raw}`); // Original string before type coercion

    // Use provenance for custom validation
    if (env === 'production' && envProvenance.source === 'default') {
      throw new Error('Production environment must be explicitly specified');
    }
  })
  .build();

Provenance sources:

  • argv - From command-line arguments (flags or positional)
  • env - From environment variables
  • stdin - From piped/redirected input
  • default - From default values
  • prompt - From interactive prompts (TTY only)

Debug Output with --debug Flag

Add --debug to any command to see how parameters were resolved:

mycli db backup mydb --output ./backups --debug

Output:

📊 Parameter Resolution Debug Info

  ┌───────────┬───────────┬─────────┬───────────┐
  │ Parameter │ Value     │ Source  │ Raw       │
  ├───────────┼───────────┼─────────┼───────────┤
  │ database  │ mydb      │ argv    │ mydb      │
  │ output    │ ./backups │ argv    │ ./backups │
  │ compress  │ false     │ default │ -         │
  │ exclude   │ undefined │ default │ -         │
  └───────────┴───────────┴─────────┴───────────┘

The debug table shows:

  • Parameter: Parameter name
  • Value: Resolved value after type coercion
  • Source: Where the value came from (argv, env, stdin, default, prompt)
  • Raw: Original input string before coercion (or "-" for defaults)

Default Child for Command Groups

Command groups can specify a default child command to run when invoked without an explicit subcommand:

const dbGroup = createCommandGroup('db')
  .command(statusCommand)
  .command(backupCommand)
  .command(restoreCommand)
  .defaultChild('status') // Run status when 'mycli db' is invoked
  .build();

// Usage:
// mycli db          -> runs 'mycli db status' (via defaultChild)
// mycli db backup   -> runs 'mycli db backup' (explicit override)
// mycli db restore  -> runs 'mycli db restore' (explicit override)

Benefits:

  • Provides sensible defaults for common operations
  • Reduces typing for frequently-used commands
  • Explicit subcommands always override the default
  • Validated at build time (throws error if defaultChild references non-existent child)

Runtime Prompts

Ask for user confirmation or input during command execution with ctx.prompt():

const deployCommand = createCommand('deploy')
  .param((p) => p.name('env').type('string').positional(0).required())
  .run(async (ctx) => {
    const { env } = ctx.params;

    // Contextual confirmation prompt
    if (env === 'production') {
      const confirmed = await ctx.prompt({
        type: 'confirm',
        message: `Deploy to PRODUCTION? This will affect live users.`,
        default: false
      });

      if (!confirmed) {
        ctx.logger.info('Deployment cancelled');
        return;
      }
    }

    // Input prompt for additional data
    const tag = await ctx.prompt({
      type: 'input',
      message: 'Enter release tag:',
      default: `v${new Date().toISOString().split('T')[0]}`
    });

    ctx.logger.info(`Deploying to ${env} with tag ${tag}...`);
    // ... deployment logic
  })
  .build();

Non-interactive mode with --yes flag:

# Interactive mode (prompts user)
mycli deploy production

# Non-interactive mode (uses defaults, skips confirm prompts)
mycli deploy production --yes
mycli deploy production -y

In non-interactive mode:

  • confirm prompts return true
  • input prompts return their default value (or empty string if no default)
  • multiselect prompts return empty array
  • select prompts throw error if no default is provided

Prompt types:

  • confirm - Yes/no question (returns boolean)
  • input - Text input (returns string)
  • select - Single choice from list (returns string)
  • multiselect - Multiple choices from list (returns string[])

Runtime prompts automatically respect TTY detection and the --yes flag, making them safe for CI/CD environments.

Pre-Execution Validation Hooks

Validate parameters BEFORE prompting users, saving time when conditions aren't met:

const deployCommand = createCommand('deploy')
  .param((p) => p.name('env').type('string').positional(0).required())
  .param((p) => p.name('confirm').type('boolean').flag('confirm', 'c').optional())

  // Validate before prompting
  .preValidate((ctx) => {
    // Access raw argv, stdin, and environment
    const { flags, positionals } = ctx.argv;
    const { isCI } = ctx.env;

    // Require --confirm in CI mode
    if (isCI && !flags.confirm) {
      return 'Confirmation required in CI mode. Use --confirm flag.';
    }

    return true; // Validation passed
  })

  .run(async (ctx) => {
    // Only runs if pre-validation passes
    ctx.logger.info(`Deploying to ${ctx.params.env}...`);
  })
  .build();

Use cases:

  • Mutually exclusive flags
  • Stdin conflict detection
  • Environment-based requirements
  • Complex cross-parameter validation

Features:

  • Sync or async validation
  • Timeout protection (2s default)
  • Access to raw argv, stdin detection, environment metadata
  • Exit code 2 on validation failure
  • Help/version bypass validation

Tech Stack

  • Runtime: Node ≥18 and Bun ≥1.1
  • Language: TypeScript (strict mode)
  • Module System: ESM only
  • Testing: Bun's native test runner
  • Dependencies: Minimal (chalk, ora)

Quick Start

# Install dependencies
bun install

# Build
bun run build

# Test
bun run test

# Type check
bun run typecheck

# Lint
bun run lint

Note: This project uses Bun. While npm works, bun install is recommended for faster installs and consistency with the runtime.

Scripts Are Source of Truth

Automation lives in scripts/. Each script is executable and referenced in package.json.

Available scripts:

  • bun run buildscripts/build.ts - Compile TypeScript
  • bun run testscripts/test.ts - Run Bun tests
  • bun run lintscripts/lint.ts - Lint code
  • bun run typecheckscripts/typecheck.ts - Type check
  • bun run cleanscripts/clean.ts - Remove build artifacts

See: @docs/cartridges/runbooks/creating-scripts.md for script patterns

Project Structure

scripts/              Source of truth for automation
src/                  Implementation (types, builders, errors)
tests/                Test files (mirrors src/)
tracking/             Progress tracking, checklists, requirements
  ├── STATUS.md                Version status overview
  ├── v0.1/                    v0.1 artifacts (completed)
  │   ├── STATUS.md            v0.1 completion summary
  │   ├── requirements.md      v0.1 spec
  │   └── implementation-plan.md  v0.1 phases
  └── v0.2/                    v0.2 artifacts (in progress)
      ├── STATUS.md            Current v0.2 status
      └── requirements.md      v0.2 spec
docs/
  └── cartridges/     Reusable knowledge loaded when needed

Development Workflow

This project uses structured do/review loops with checklists tracking progress.

For details: @docs/cartridges/methodology/dev-runbook.md

Key points:

  • Work organized into "bunches" (phases)
  • Each bunch has a detailed checklist
  • Every change reviewed before proceeding
  • Checklists updated as work progresses

For AI Agents

Supervisors: Read @docs/cartridges/methodology/supervisor.md for orchestrating work

Workers: Check current checklist's "Required Reading" section before starting

Implementation Bunches

All 8 bunches complete:

  1. Foundation - Types, interfaces, builder skeletons
  2. Param System - Parsing, coercion, resolution pipeline
  3. Command System - Composite pattern, dispatch, routing
  4. Execution - Context, prompts, validation, run handlers
  5. Provenance - Parameter source tracking, debug output
  6. Corrections - Serializable specs, declarative validation
  7. UX - Help generation, colors, spinners, errors
  8. Polish & Integration - Signals, edge cases, examples, CI

See: tracking/v0.1/implementation-plan.md for v0.1 detailed breakdown See: tracking/v0.1/STATUS.md for v0.1 completion summary See: tracking/v0.2/STATUS.md for v0.2 current status

License

MIT