Skip to content
Back to skills

Stacks Cli

ASecurity

Use when building CLI commands or tools with Stacks - the @stacksjs/cli package for creating commands with argument parsing, option handling, colored output, tables, progress indicators, prompts, or integrating with the buddy command system. Covers @stacksjs/cli and app/Commands/.

  • 631 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added June 5, 2026
developmenttypescriptgobash

Works with

  • cli

Security analysis

A100/100

Scanned September 3, 2026

npx -y skills add stacksjs/stacks --skill stacks-cli --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Stacks Cli?

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

Security grade badge for Stacks Cli
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/stacksjs-stacks-cli/badge)](https://www.skillsdirectory.com/skills/stacksjs-stacks-cli)

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: stacks-cli
description: Use when building CLI commands or tools with Stacks - the @stacksjs/cli package for creating commands with argument parsing, option handling, colored output, tables, progress indicators, prompts, or integrating with the buddy command system. Covers @stacksjs/cli and app/Commands/.
license: MIT
compatibility: Bun >= 1.3.0, TypeScript
allowed-tools: Read Edit Write Bash Grep Glob
---

# Stacks CLI Framework

The `@stacksjs/cli` package provides the foundation for building CLI commands, used internally by the buddy CLI.

## Key Paths
- Core package: `storage/framework/core/cli/src/`
- CLI configuration: `config/cli.ts`
- Application commands: `app/Commands/`
- Optional registry: `app/Commands.ts` (not needed - see below)

## Creating Commands

Every `.ts` file in `app/Commands/` is a command. There is no registration step
and no generated registry file: drop the file in and it is live, nested
directories included (`app/Commands/Archive/Run.ts`).

### Declarative form (preferred)

`defineCommand()` infers the handler's `options` from the flags declared above
it, so there is no hand-written options interface to drift out of step.

```typescript
// app/Commands/Greet.ts
import { defineCommand, log } from '@stacksjs/cli'

export default defineCommand({
  name: 'greet <who>',
  description: 'Greet a user',
  aliases: ['g'],
  options: {
    '--loud, -l': { description: 'Shout the greeting', default: false },
    '--title <title>': 'Prefix the name',
    '--times <n>': { description: 'How often', default: 1, type: [Number] },
  },
  handle(options, who) {
    // options.loud   -> boolean
    // options.title  -> string | undefined
    // options.times  -> number
    const greeting = `Hello, ${options.title ?? ''}${who}!`

    for (let i = 0; i < options.times; i++)
      log.info(options.loud ? greeting.toUpperCase() : greeting)
  },
})
```

Flag to property: `--dry-run` -> `dryRun`, `--two, -t` -> `two`, `--no-cache`
-> `cache`. Value type: `<x>` -> `string`, `[x]` -> `string | true`, `<x...>`
-> `string[]`, no argument -> `boolean`. A `default` makes the property always
present; a `type: [Number]` makes it a `number`.

### Imperative form

For a file that registers several commands or needs `cli.on()`:

```typescript
import { defineCommand, log } from '@stacksjs/cli'

export default defineCommand((cli) => {
  cli.command('inspire', 'Inspire yourself').alias('insp').action(() => {})
  cli.command('inspire:two', 'Two quotes').action(() => {})

  cli.on('inspire:*', () => log.error('Invalid command'))
})
```

### Per-file configuration

Named exports beside the default one, so a command owns its own configuration:

```typescript
export const aliases = ['emails', 'mail'] // extra aliases
export const enabled = false              // keep the file, hide the command
```

### The optional registry (`app/Commands.ts`)

Only worth keeping when you want to control the order commands are listed in,
or to alias/disable a command without editing its file. A command file the
registry never mentions still loads.

```typescript
import { defineCommands } from '@stacksjs/cli'

export default defineCommands({
  'send-emails <type>': { file: 'SendEmails', aliases: ['emails'] },
  'legacy': { file: 'Legacy', enabled: false },
})
```

### Registration Methods
1. **Drop a file in `app/Commands/`** — auto-discovered, nothing else to do
2. **Event listeners** — CLI events in `app/Listeners/Console.ts`
3. **`app/Commands.ts`** — optional, for ordering / aliases / disabling

## CLI Event Listeners

```typescript
// app/Listeners/Console.ts
export default function(cli: CLI) {
  cli.on('custom:command', () => {
    console.log('Custom command executed!')
  })

  cli.on('my:*', () => {
    // Wildcard — matches my:anything
  })

  cli.on('unknown:!', () => {
    // Default/fallback handler
  })
}
```

## Output Formatting

Via `@stacksjs/utils` color functions (available in CLI context):
```typescript
import { bold, green, red, yellow, dim, underline } from '@stacksjs/utils'

console.log(green('✓ Success'))
console.log(red('✗ Error'))
console.log(yellow('⚠ Warning'))
console.log(bold('Important'))
console.log(dim('Subtle info'))
```

## config/cli.ts (BinaryConfig)

```typescript
{
  name: 'My Custom CLI',
  command: 'mycli',
  description: 'My custom CLI tool',
  deploy: true
}
```

## CLI Commands
- `buddy make:command [name]` — scaffold a new command

## Compiled Binaries

```bash
buddy build:cli                    # build CLI binary
# Compiles for: linux-x64, linux-arm64, windows-x64, darwin-x64, darwin-arm64
```

## Gotchas
- Application commands go in `app/Commands/`, framework commands in `storage/framework/core/buddy/src/commands/`
- Commands are auto-discovered from `app/Commands/`; `app/Commands.ts` is optional and additive
- CLI events support wildcards (`*`) and default handlers (`!`)
- The buddy CLI lazy-loads commands — not all load at startup
- Output formatting uses ANSI colors from `@stacksjs/utils`
- Custom CLIs can be compiled to platform-specific binaries
- The CLI framework is separate from buddy — buddy uses it internally

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.