Template do pack (backend/11-cli-typescript-commander.md). Orienta o agente em APIs, servicos e arquitetura backend alinhado a esse contexto.
Scanned 9/8/2026
Install to Claude Code
npx -y skills add artubss/SKILLS-CLAUDE-CODE --skill tpl-backend-cli-typescript-commander --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Tpl Backend Cli Typescript Commander?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/artubss-tpl-backend-cli-typescript-commander)More formats (shields.io, HTML) on the badges page.
---
name: tpl-backend-cli-typescript-commander
description: Template do pack (backend/11-cli-typescript-commander.md). Orienta o agente em APIs, servicos e arquitetura backend alinhado a esse contexto.
metadata:
version: 1.0.0
source_template: backend/11-cli-typescript-commander.md
generated_by: install_pack_templates_as_claude_skills
---
# PROJECT: CLI Tool — TypeScript + Commander.js
Skill gerado a partir do pack `templates-claude-code`. Arquivo de origem: `backend/11-cli-typescript-commander.md`. Use como baseline e adapte ao projeto antes de mudancas grandes.
## Conteudo do template
## STACK
- **Runtime:** Node.js 22 LTS
- **Language:** TypeScript 5.x (strict)
- **CLI Framework:** Commander.js v12
- **Prompts:** Inquirer.js v9 (ESM)
- **Styling:** chalk v5 + ora (spinners)
- **File ops:** fs-extra v11
- **Config:** cosmiconfig v9 (~/.appname)
- **Testing:** Vitest + memfs (virtual FS)
- **Distribution:** npm publish (ESM + CJS dual build)
---
## PROJECT STRUCTURE
```
src/
├── bin/
│ └── cli.ts # Entry: shebang + program bootstrap
├── commands/
│ ├── init.ts # init command
│ ├── generate.ts # generate command
│ └── config.ts # config get/set commands
├── lib/
│ ├── config-manager.ts # ~/.appname config read/write
│ ├── file-ops.ts # fs-extra wrappers
│ ├── logger.ts # chalk-based logger
│ └── spinner.ts # ora wrapper
├── prompts/
│ ├── init-prompts.ts
│ └── generate-prompts.ts
└── types.ts # CLI options + config types
tests/
├── commands/
│ └── init.test.ts
└── lib/
└── config-manager.test.ts
package.json
tsconfig.json
tsup.config.ts # Build config (tsup)
```
---
## ARCHITECTURE RULES
1. **`bin/cli.ts` only bootstraps** — no logic; it imports `program` and calls `program.parse(argv)`.
2. **Each command is a function that takes `program: Command`** — register subcommands via `program.addCommand(...)`.
3. **Config lives in `~/.appname/config.json`** — use `cosmiconfig` for project-level config, XDG dirs for user config.
4. **Never `process.exit()` inside a command handler** — throw a `CLIError`; let the bootstrap handle exit codes.
5. **stdin/stdout/stderr separation** — stdout: machine-readable output; stderr: logs, spinners, errors.
6. **All prompts gated by `--yes` flag** — prompts must be skippable for CI environments.
7. **Dual ESM + CJS build** — use `tsup` with `format: ['esm', 'cjs']`.
---
## COMMAND STRUCTURE
```typescript
// src/bin/cli.ts
#!/usr/bin/env node
import { Command } from 'commander'
import { registerInit } from '../commands/init.js'
import { registerGenerate } from '../commands/generate.js'
import { registerConfig } from '../commands/config.js'
import { version } from '../../package.json' assert { type: 'json' }
const program = new Command()
.name('mytool')
.description('Production-ready CLI tool')
.version(version)
registerInit(program)
registerGenerate(program)
registerConfig(program)
program.parseAsync(process.argv).catch((err: Error) => {
process.stderr.write(`\nError: ${err.message}\n`)
process.exit(1)
})
```
---
## COMMAND IMPLEMENTATION
```typescript
// src/commands/init.ts
import { Command } from 'commander'
import inquirer from 'inquirer'
import { logger } from '../lib/logger.js'
import { spinner } from '../lib/spinner.js'
import { writeConfig } from '../lib/config-manager.js'
import fs from 'fs-extra'
import path from 'node:path'
interface InitOptions {
name?: string
yes: boolean
output?: string
}
export function registerInit(program: Command): void {
program
.command('init')
.description('Initialize a new project configuration')
.option('-n, --name <name>', 'Project name')
.option('-o, --output <dir>', 'Output directory', '.')
.option('-y, --yes', 'Skip prompts, use defaults', false)
.action(async (opts: InitOptions) => {
const answers = opts.yes
? { name: opts.name ?? 'my-project', template: 'default' }
: await inquirer.prompt([
{ type: 'input', name: 'name', message: 'Project name:', default: opts.name ?? 'my-project' },
{ type: 'list', name: 'template', message: 'Template:', choices: ['default', 'minimal', 'full'] },
])
const spin = spinner('Initializing project...').start()
try {
const outDir = path.resolve(opts.output ?? '.')
await fs.ensureDir(outDir)
await writeConfig(outDir, answers)
spin.succeed(`Project "${answers.name}" initialized`)
logger.info(` cd ${path.basename(outDir)} && mytool generate`)
} catch (err) {
spin.fail('Initialization failed')
throw err
}
})
}
```
---
## CONFIG FILE HANDLING (~/.appname)
```typescript
// src/lib/config-manager.ts
import os from 'node:os'
import path from 'node:path'
import fs from 'fs-extra'
import { cosmiconfigSync } from 'cosmiconfig'
export interface UserConfig {
apiKey?: string
defaultOrg?: string
telemetry: boolean
}
const APP_NAME = 'mytool'
const CONFIG_DIR = path.join(os.homedir(), `.${APP_NAME}`)
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json')
const DEFAULT_CONFIG: UserConfig = { telemetry: true }
export async function readUserConfig(): Promise<UserConfig> {
const raw = await fs.readJson(CONFIG_FILE).catch(() => ({}))
return { ...DEFAULT_CONFIG, ...raw }
}
export async function writeUserConfig(patch: Partial<UserConfig>): Promise<void> {
await fs.ensureDir(CONFIG_DIR)
const current = await readUserConfig()
await fs.writeJson(CONFIG_FILE, { ...current, ...patch }, { spaces: 2 })
}
export async function writeConfig(dir: string, data: unknown): Promise<void> {
await fs.writeJson(path.join(dir, `.${APP_NAME}rc.json`), data, { spaces: 2 })
}
// Read project-level config (walks up directory tree)
export function readProjectConfig(): ReturnType<typeof cosmiconfigSync> {
return cosmiconfigSync(APP_NAME).search()
}
```
---
## LOGGER + SPINNER
```typescript
// src/lib/logger.ts
import chalk from 'chalk'
export const logger = {
info: (msg: string) => process.stderr.write(`${chalk.cyan('ℹ')} ${msg}\n`),
success: (msg: string) => process.stderr.write(`${chalk.green('✔')} ${msg}\n`),
warn: (msg: string) => process.stderr.write(`${chalk.yellow('⚠')} ${msg}\n`),
error: (msg: string) => process.stderr.write(`${chalk.red('✖')} ${msg}\n`),
// Machine-readable output to stdout
output: (data: unknown) => process.stdout.write(JSON.stringify(data) + '\n'),
}
// src/lib/spinner.ts
import ora from 'ora'
export const spinner = (text: string) => ora({ text, stream: process.stderr })
```
---
## ROUTING TABLE (Command → Action)
| Command | Options | stdin | Action |
|---------|---------|-------|--------|
| `mytool init` | `--name`, `--yes`, `--output` | — | Scaffold project config |
| `mytool generate <type>` | `--dry-run`, `--yes` | — | Generate files from template |
| `mytool config get <key>` | — | — | Print config value to stdout |
| `mytool config set <key> <val>` | — | — | Write to `~/.mytool/config.json` |
| `mytool config list` | `--json` | — | List all config as JSON/table |
| `mytool login` | `--token` | token (if no flag) | Authenticate with API |
| `mytool logout` | — | — | Remove stored credentials |
| `mytool run <script>` | `--watch` | — | Execute project script |
| `mytool publish` | `--dry-run`, `--tag` | — | Build + publish artifact |
---
## QUALITY GATES
- [ ] `tsc --noEmit` — zero errors
- [ ] `--yes` flag skips all `inquirer` prompts (CI-safe)
- [ ] Spinners + logs go to `stderr`; data output goes to `stdout`
- [ ] `vitest run` passes all tests with mocked `fs`
- [ ] `npm pack --dry-run` outputs expected files only
- [ ] `node dist/bin/cli.js --help` renders usage without errors
- [ ] Config file created with correct permissions (0o600 for secrets)
- [ ] No `process.exit()` inside command action handlers
---
## FORBIDDEN
- ❌ `console.log` — use `logger.info` or `logger.output`
- ❌ `process.exit()` inside command handlers — throw errors
- ❌ Synchronous `fs.readFileSync` in async commands
- ❌ Prompts without `--yes` bypass — breaks CI
- ❌ Hardcoded file paths — use `os.homedir()`, `path.resolve()`
- ❌ Mutating `process.argv` directly
- ❌ Shipping `node_modules` in npm package — use `bundledDependencies` carefully
- ❌ Storing API tokens in plain project `.env` files — always `~/.appname/config.json`
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!