YashKandalkar/shellish

A declarative CLI builder for creating command-line interfaces with ease

โ˜… 1Forks 0TypeScriptGitHub โ†—Compare

README

Shellish ๐Ÿš€

A declarative CLI builder for creating beautiful command-line interfaces with ease.

Shellish makes it simple to build robust, user-friendly command-line tools with a clean, declarative API. Perfect for creating CLI tools, scripts, and utilities with minimal boilerplate.

โœจ Features

  • ๐ŸŽฏ Declarative API - Define your CLI structure clearly and intuitively
  • ๐Ÿ”ง TypeScript Support - Full type safety and IntelliSense
  • ๐Ÿš€ Bun Optimized - Built for Bun but works with Node.js too
  • ๐Ÿ“‹ Automatic Help - Auto-generated help text and usage information
  • โœ… Validation - Built-in argument validation and error handling
  • ๐Ÿ”„ Async Callbacks - Support for asynchronous argument handlers
  • ๐ŸŽจ Flexible Arguments - Support for flags, single values, and multiple values
  • ๐Ÿ“ฆ Zero Dependencies - Lightweight with no external dependencies

๐Ÿ“ฆ Installation

# Using Bun (recommended)
bun add shellish

# Using npm
npm install shellish

# Using pnpm  
pnpm add shellish

๐Ÿš€ Quick Start

import { Shellish } from 'shellish';

// Create your CLI
const cli = new Shellish({
    name: 'my-tool',
    description: 'My awesome CLI tool',
    version: '1.0.0'
});

// Add arguments
cli.addArgument({
    longName: 'verbose',
    shortName: 'v', 
    description: 'Enable verbose output',
    callback: () => console.log('Verbose mode enabled!')
});

cli.addArgument({
    longName: 'file',
    shortName: 'f',
    description: 'Input file path',
    args: 1,
    required: true,
    callback: ([filePath]) => {
        console.log(`Processing file: ${filePath}`);
    }
});

// Run the CLI
cli.run();

๐Ÿ”จ Build

Once you've created your CLI, you can build it into a standalone executable using Bun's built-in compiler.

Basic Build

# Build for current platform
bun build --compile index.ts --outfile my-tool

Cross-Platform Builds

Build your CLI for different operating systems and architectures:

# macOS (Apple Silicon)
bun build --compile --target bun-darwin-arm64 index.ts --outfile my-tool-macos-arm64

# macOS (Intel)
bun build --compile --target bun-darwin-x64 index.ts --outfile my-tool-macos-x64

# Linux (x64)
bun build --compile --target bun-linux-x64 index.ts --outfile my-tool-linux-x64

# Linux (ARM64)
bun build --compile --target bun-linux-arm64 index.ts --outfile my-tool-linux-arm64

# Windows (x64)
bun build --compile --target bun-windows-x64 index.ts --outfile my-tool-windows-x64.exe

Build Script

Add these build commands to your package.json for convenience:

{
  "scripts": {
    "build": "bun build --compile index.ts --outfile my-tool",
    "build:all": "bun run build:macos && bun run build:linux && bun run build:windows",
    "build:macos": "bun build --compile --target bun-darwin-arm64 index.ts --outfile dist/my-tool-macos-arm64 && bun build --compile --target bun-darwin-x64 index.ts --outfile dist/my-tool-macos-x64",
    "build:linux": "bun build --compile --target bun-linux-x64 index.ts --outfile dist/my-tool-linux-x64 && bun build --compile --target bun-linux-arm64 index.ts --outfile dist/my-tool-linux-arm64",
    "build:windows": "bun build --compile --target bun-windows-x64 index.ts --outfile dist/my-tool-windows-x64.exe"
  }
}

Then run:

# Build for current platform
bun run build

# Build for all platforms
bun run build:all

Note: After building, a new executable called my-tool (or your specified output name) will be created in your project directory. To access your CLI globally from anywhere in your terminal, add the executable to your system's PATH or move it to a directory that's already in your PATH (like /usr/local/bin on macOS/Linux).

๐Ÿ“– Detailed Example

import { Shellish } from 'shellish';

const cli = new Shellish({
    name: 'file-processor',
    description: 'A powerful file processing tool',
    version: '2.1.0',
    author: 'Your Name',
    helpText: `
Examples:
  file-processor --input data.json --output result.json --verbose
  file-processor -i *.txt -o processed/ --format csv
`
});

// Simple flag
cli.addArgument({
    longName: 'verbose',
    shortName: 'v',
    description: 'Enable verbose logging',
    callback: () => {
        console.log('๐Ÿ” Verbose mode activated');
    }
});

// Required argument with validation
cli.addArgument({
    longName: 'input',
    shortName: 'i', 
    description: 'Input file or pattern',
    args: 1,
    required: true,
    callback: ([inputPath], context) => {
        if (!inputPath.endsWith('.json')) {
            throw new Error('Input must be a JSON file');
        }
        console.log(`๐Ÿ“ Processing: ${inputPath}`);
    }
});

// Optional argument with default
cli.addArgument({
    longName: 'output',
    shortName: 'o',
    description: 'Output directory', 
    args: 1,
    defaultValue: './output',
    callback: ([outputPath]) => {
        console.log(`๐Ÿ“ค Output directory: ${outputPath}`);
    }
});

// Multiple arguments
cli.addArgument({
    longName: 'exclude',
    description: 'Patterns to exclude',
    args: -1, // unlimited arguments
    callback: (patterns) => {
        console.log(`๐Ÿšซ Excluding: ${patterns.join(', ')}`);
    }
});

// Argument with choices validation
cli.addArgument({
    longName: 'format',
    description: 'Output format (json, csv, xml)',
    args: 1,
    defaultValue: 'json',
    callback: ([format]) => {
        const validFormats = ['json', 'csv', 'xml'];
        if (!validFormats.includes(format)) {
            throw new Error(`Invalid format. Choose: ${validFormats.join(', ')}`);
        }
        console.log(`๐Ÿ“„ Format: ${format}`);
    }
});

// Run the CLI
cli.run();

๐Ÿ”ง API Reference

new Shellish(options)

Create a new Shellish CLI instance.

Options:

  • name (string, required) - Name of your CLI tool
  • description (string, required) - Description of your tool
  • version (string, optional) - Version string
  • author (string, optional) - Author information
  • helpText (string, optional) - Custom help text to display

cli.addArgument(options)

Add a command-line argument.

Options:

  • longName (string, required) - Long form name (e.g., --verbose)
  • shortName (string, optional) - Short form name (e.g., -v)
  • description (string, required) - Help text description
  • args (number, optional) - Number of values this argument takes
    • 0 (default) - Flag argument, no values
    • 1 - Single value argument
    • 2+ - Multiple value argument
    • -1 - Unlimited values
  • required (boolean, optional) - Whether argument is required
  • defaultValue (any, optional) - Default value if not provided
  • callback (function, required) - Function called when argument is parsed
    • Receives (values: string[], context: ParsedContext)

cli.run(args?)

Parse and execute CLI with given arguments (defaults to process.argv).

cli.parse(args)

Parse arguments and return result without executing callbacks.

๐Ÿ›ก๏ธ Error Handling

Shellish provides comprehensive error handling:

// Validation errors
cli.addArgument({
    longName: 'port',
    args: 1,
    callback: ([port]) => {
        const portNum = parseInt(port);
        if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
            throw new Error('Port must be between 1 and 65535');
        }
    }
});

// Handle parsing errors
const result = await cli.parse(process.argv.slice(2));
if (!result.success) {
    console.error(`โŒ ${result.error}`);
    process.exit(1);
}

๐Ÿงช Testing

# Run tests
bun test

# Run specific test file  
bun test tests/shellish.test.ts

# Watch mode
bun test --watch

๐Ÿ“„ License

MIT ยฉ [Yash Kandalkar]

๐Ÿค Contributing

Contributions welcome! Please read our contributing guidelines and submit pull requests.


Built with โค๏ธ using Bun and TypeScript

Contributors

YashKandalkar

Issues