Get the FREE Ultimate OpenClaw Setup Guide →

cli-tool-development

npx machina-cli add skill autohandai/community-skills/cli-tool-development --openclaw
Files (1)
SKILL.md
4.1 KB

CLI Tool Development

Project Structure

src/
  index.ts          # Entry point with shebang
  cli.ts            # Commander setup
  commands/         # Command handlers
  ui/               # Ink components (if interactive)
  utils/            # Helpers
  types.ts          # Type definitions

Commander.js Setup

#!/usr/bin/env node
import { Command } from 'commander';
import packageJson from '../package.json' with { type: 'json' };

const program = new Command();

program
  .name('mytool')
  .description('My awesome CLI tool')
  .version(packageJson.version);

program
  .command('init')
  .description('Initialize a new project')
  .option('-t, --template <name>', 'Template to use', 'default')
  .option('-f, --force', 'Overwrite existing files', false)
  .action(async (options) => {
    await initCommand(options);
  });

program.parseAsync();

User Feedback with Chalk & Ora

import chalk from 'chalk';
import ora from 'ora';

// Status messages
console.log(chalk.green('✓') + ' Operation complete');
console.log(chalk.red('✗') + ' Operation failed');
console.log(chalk.yellow('⚠') + ' Warning message');

// Progress spinner
const spinner = ora('Loading...').start();
try {
  await longOperation();
  spinner.succeed('Done!');
} catch (error) {
  spinner.fail('Failed');
}

Interactive Prompts with Enquirer

import enquirer from 'enquirer';

const { name } = await enquirer.prompt<{ name: string }>({
  type: 'input',
  name: 'name',
  message: 'Project name:',
  validate: (v) => v.length > 0 || 'Name required',
});

const { confirm } = await enquirer.prompt<{ confirm: boolean }>({
  type: 'confirm',
  name: 'confirm',
  message: 'Continue?',
  initial: true,
});

Ink for Rich TUI

import React, { useState } from 'react';
import { render, Box, Text, useInput } from 'ink';

function App() {
  const [selected, setSelected] = useState(0);
  const items = ['Option 1', 'Option 2', 'Option 3'];

  useInput((input, key) => {
    if (key.downArrow) setSelected(s => Math.min(s + 1, items.length - 1));
    if (key.upArrow) setSelected(s => Math.max(s - 1, 0));
    if (key.return) process.exit(0);
  });

  return (
    <Box flexDirection="column">
      {items.map((item, i) => (
        <Text key={i} color={i === selected ? 'cyan' : undefined}>
          {i === selected ? '>' : ' '} {item}
        </Text>
      ))}
    </Box>
  );
}

render(<App />);

Configuration Management

import fs from 'fs-extra';
import path from 'node:path';
import os from 'node:os';

const CONFIG_DIR = path.join(os.homedir(), '.mytool');
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');

async function loadConfig(): Promise<Config> {
  await fs.ensureDir(CONFIG_DIR);
  if (await fs.pathExists(CONFIG_FILE)) {
    return fs.readJson(CONFIG_FILE);
  }
  return getDefaultConfig();
}

async function saveConfig(config: Config): Promise<void> {
  await fs.writeJson(CONFIG_FILE, config, { spaces: 2 });
}

Error Handling

// Graceful exit handling
process.on('SIGINT', () => {
  console.log('\nCancelled');
  process.exit(0);
});

// Top-level error handler
try {
  await program.parseAsync();
} catch (error) {
  console.error(chalk.red('Error:'), error.message);
  process.exit(1);
}

Package.json Setup

{
  "bin": {
    "mytool": "./dist/index.js"
  },
  "files": ["dist"],
  "type": "module",
  "scripts": {
    "build": "tsup src/index.ts --format esm --dts",
    "dev": "tsx src/index.ts"
  }
}

Best Practices

  1. Add #!/usr/bin/env node shebang to entry file
  2. Support both flags (-f) and options (--force)
  3. Provide helpful error messages with suggestions
  4. Support --help and --version flags
  5. Use exit codes: 0 for success, 1 for error
  6. Support piping and stdin when appropriate
  7. Respect NO_COLOR environment variable

Source

git clone https://github.com/autohandai/community-skills/blob/main/cli-tool-development/SKILL.mdView on GitHub

Overview

Learn how to create scalable CLI apps using Node.js, Commander, and Ink. This skill covers project structure, interactive UIs, prompts, configuration, and robust packaging.

How This Skill Works

Start with a Commander-based entry (src/index.ts), wire commands in a cli.ts and separate handlers in commands/. Use Ink for rich TUIs and Enquirer and Chalk/Ora for prompts and status feedback. Persist user configuration in a config.json in the user home directory and coordinate error handling and packaging with a bin entry in package.json.

When to Use It

  • You're building a multi-command CLI tool (init, deploy, config) and need a clean structure.
  • You want interactive prompts to gather user input during setup or configuration.
  • You require a rich terminal UI for menus or live status using Ink.
  • You need to persist user preferences or settings in a simple home directory config.
  • You aim to deliver a polished tool with helpful errors and a robust --help experience.

Quick Start

  1. Step 1: Scaffold project structure (src/index.ts, cli.ts, commands/, ui/, utils/).
  2. Step 2: Create a Commander-based CLI entry and a sample init command with options.
  3. Step 3: Add interactive prompts (Enquirer), status feedback (Chalk & Ora), and an Ink-based UI; wire a simple config load/save.

Best Practices

  • Add a shebang (#!/usr/bin/env node) to the entry file.
  • Support both flags (-f) and long options (--force) for flexibility.
  • Provide helpful error messages with actionable suggestions.
  • Ensure --help is comprehensive and commands are well-documented.
  • Package as an npm bin with a dist output and proper module type in package.json.

Example Use Cases

  • A tool that initializes a project with a selectable template via -t/--template.
  • A multi-command CLI with commands like init, build, and deploy.
  • A config manager that reads/writes ~/.mytool/config.json.
  • An interactive menu built with Ink to navigate options and exit cleanly.
  • A UI that uses Enquirer for prompts and Chalk/Ora for real-time feedback.

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers