Skip to content
Back to skills

Cli Tool Development

ASecurity

Build professional CLI tools with Node.js, commander, and Ink

  • 666 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added February 8, 2026
developmenttypescriptgoreactnodenodejs

Works with

  • cli

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill cli-tool-development --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Cli Tool Development?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Cli Tool Development
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-cli-tool-development/badge)](https://www.skillsdirectory.com/skills/majiayu000-cli-tool-development)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: cli-tool-development
description: Build professional CLI tools with Node.js, commander, and Ink
license: MIT
compatibility: nodejs 18+, typescript 5+
allowed-tools: read_file write_file apply_patch run_command search_with_context
---

# 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

```typescript
#!/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

```typescript
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

```typescript
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

```tsx
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

```typescript
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

```typescript
// 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

```json
{
  "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

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.