mikkelkrogsholm/skills · Archived

bunli

Build type-safe CLIs with Bun. Use when: (1) Creating a new CLI application with Bun, (2) Adding commands with defineCommand/option(), (3) Building a plugin system, (4) Using prompt/spinner APIs for interactive CLIs, (5) Building TUI components, (6) Publishing CLI to npm. For advanced TUI (custom components, animations, full layout control), use the 'opentui' skill. Covers: defineCommand, option() APIs, plugin architecture, bunli CLI commands (dev/build/generate/test/release/init/doctor), creat…

First seen Jun 22, 2026

Installation

$ npx skills add mikkelkrogsholm/skills --skill bunli

Summary

  • Build type-safe CLIs with Bun.
  • Use when: (1) Creating a new CLI application with Bun, (2) Adding commands with defineCommand/option(), (3) Building a plugin system, (4) Using prompt/spinner APIs for interactive CLIs, (5) Building TUI components, (6) Publishing CLI to npm.
  • For advanced TUI (custom components, animations, full layout control), use the 'opentui' skill.
  • Covers: defineCommand, option() APIs, plugin architecture, bunli CLI commands (dev/build/generate/test/release/init/doctor), create-bunli scaffolding, and utilities.

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from mikkelkrogsholm/skills · top by installs.

npx skills add mikkelkrogsholm/skills

Browse all from mikkelkrogsholm/skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 64
License LICENSE
Default branch main
Open issues 32
Status Archived

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 10,001 B
  • docs SUMMARY.md 547 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

Bunli Skill

Build type-safe CLIs with Bun. See references/ for detailed documentation.

Quick Start

# Create new CLI
bunli init my-cli

# Or manually:
mkdir my-cli && cd my-cli
npm init -y
bun add @bunli/core @bunli/utils @bunli/tui
bun add -d bunli typescript

Then create your entry point:

// cli.ts
import { createCLI } from "@bunli/core"
import { defineCommand, option } from "@bunli/core"
import { z } from "zod"

const hello = defineCommand({
  name: "hello",
  description: "Print a greeting",
  options: {
    name: option(z.string().default("World"), { short: "n", description: "Name to greet" }),
    count: option(z.coerce.number().default(1), { short: "c", description: "Number of times" })
  },
  handler: ({ flags, signal }) => {
    if (signal.aborted) return
    for (let i = 0; i < flags.count; i++) {
      console.log(`Hello, ${flags.name}!`)
    }
  }
})

const cli = await createCLI({
  name: "my-cli",
  version: "0.1.0",
})

cli.command(hello)
await cli.run()

Run with: bun run cli.ts

Package Versions

Find current versions:

npm view @bunli/core      # Latest version
npm view @bunli/utils    # Latest version
npm view @bunli/tui      # Latest version
npm view bunli           # CLI version

Quick Decision Trees

"I need to define a command"

Define Command?
├─ Basic command → defineCommand({ name: "mycmd", description: "...", handler: ({ flags }) => {...} })
├─ With options → Add options: { options: { debug: option(z.coerce.boolean(), { short: "d" }) } }
├─ Nested commands → Use defineGroup({ name: "group", description: "...", commands: [...] })
├─ With alias → Add alias: "m" for "mycmd"
└─ With TUI → Add render (optionally keep handler; render commands run without TUI flags)

"I need to add options"

Add Options?
├─ Boolean flag → option(z.coerce.boolean(), { short: "d", description: "Debug output" })
├─ String input → option(z.string(), { description: "Name" })
├─ Number input → option(z.coerce.number(), { description: "Port" })
├─ Enum/choice → option(z.enum(["dev", "prod"]), { description: "Environment" })
├─ With default → option(z.string().default("default"))
└─ Required → option(z.string().min(1))

Important: Use z.coerce.number() and z.coerce.boolean() for numeric and boolean flags because CLI args are strings. Enums should use z.enum(...).

-v is reserved globally for --version, so avoid short: "v" for command-local options.

"I need to create a plugin"

Create Plugin?
├─ Direct plugin → createPlugin({ name: "my-plugin", setup(ctx) {...} })
├─ Factory → createPlugin((options) => ({ name: "my-plugin", ... }))
├─ With store → Add store: { counter: 0 }, access via context.store
└─ Lifecycle hooks → setup, configResolved, beforeCommand, afterCommand

"I need to use prompts"

Use Prompt?
├─ Text input → prompt("Enter name")
├─ Yes/No → confirm("Continue?")
├─ Single choice → select("Select framework", { options: [...] })
├─ Multiple choice → multiselect("Select tools", { options: [...] })
└─ Password → password("Enter password")

"I keep hitting validation errors"

Validation Errors?
├─ Numbers not working → Use z.coerce.number() instead of z.number()
├─ Booleans not working → Use z.coerce.boolean(); pass --flag=true/false for explicit values
├─ Enums not working → Use z.enum([...]), not z.coerce.enum(...)
├─ createCLI returns Promise → Use await createCLI() or .then()
└─ Commands not registering → Use cli.command(cmd), not commands: [cmd]

"I need to build for production"

Build?
├─ Development → bunli dev (hot reload)
├─ Production build → bunli build
├─ Cross-compile → bunli build --targets darwin-arm64,linux-x64
├─ With bytecode → bunli build --bytecode
├─ Generate types → bunli generate
└─ Release → bunli release

"I need advanced TUI (beyond basic forms)"

Advanced TUI?
├─ Custom components beyond Bunli's built-in form/layout set → Use opentui skill
├─ Advanced animations (timeline, keyframes) → Use opentui skill
├─ Full Flexbox layout control → Use opentui skill
├─ Custom renderables → Use opentui skill
└─ Just need Bunli components/forms/charts → Use @bunli/tui (covered here)

"I need predictable TUI buffer behavior"

Buffer Mode?
├─ Global default policy → standard buffer mode
├─ Use fullscreen alternate buffer → set tui.renderer.bufferMode = "alternate" in defineConfig
├─ Per-command override → set command.tui.renderer.bufferMode
└─ Keep standard-buffer output behavior → set bufferMode = "standard" explicitly

Product Index

Core

Topic Reference
Command definition references/core/commands.md
Type safety patterns references/core/types.md

Plugin System

Topic Reference
Plugin architecture references/plugin/system.md
Built-in plugins references/plugin/built-ins.md

CLI Commands

Topic Reference
CLI commands references/cli/commands.md
Project scaffolding references/cli/scaffold.md

Utilities

Topic Reference
Prompts references/utils/prompts.md
Spinners references/utils/spinner.md
Colors references/utils/colors.md

TUI

Topic Reference
Components references/tui/components.md
OpenTUI (advanced) Use opentui skill

OpenTUI Integration

Topic Reference
When to use OpenTUI See "Relationship with OpenTUI" below

Core Concepts

CLI Structure

// my-cli/src/commands/hello.ts
import { defineCommand, option } from "@bunli/core"
import { z } from "zod"

export const hello = defineCommand({
  name: "hello",
  description: "Greet someone",
  options: {
    name: option(z.string().default("World"), {
      short: "n",
      description: "Name to greet"
    })
  },
  handler: ({ flags }) => {
    console.log(`Hello, ${flags.name}!`)
  }
})

Plugin Creation

import { createPlugin } from "@bunli/core/plugin"

const myPlugin = createPlugin({
  name: "my-plugin",
  store: { count: 0 },
  setup(context) {
    context.registerCommand(myCommand)
  },
  beforeCommand(ctx) {
    ctx.store.count++
  }
})

Using Prompts

handler: async ({ prompt }) => {
  const name = await prompt("What is your name?")
  const proceed = await prompt.confirm("Continue?")
  const framework = await prompt.select("Choose framework", {
    options: [
      { label: "React", value: "react" },
      { label: "Vue", value: "vue" },
      { label: "Svelte", value: "svelte" }
    ]
  })
}

Key Packages

Package Purpose
@bunli/core CLI framework (defineCommand, option, createCLI)
@bunli/utils Colors and validation utilities
@bunli/runtime/prompt Prompt and spinner APIs
@bunli/tui Terminal UI components
bunli CLI for building CLIs
create-bunli Project scaffolding
@bunli/generator Type generation from commands

Built-in Plugins

  • @bunli/plugin-ai-detect - Detects AI coding assistants
  • @bunli/plugin-completions - Shell completions (bash, zsh, fish)
  • @bunli/plugin-config - Config loading from multiple sources
  • @bunli/plugin-mcp - MCP tools to CLI commands

Relationship with OpenTUI

Bunli uses OpenTUI as its terminal rendering engine. Understanding when to use each:

Use Bunli (this skill) for:

  • Building CLI applications with commands and options
  • Plugin architecture (auth, config, completions)
  • Type-safe CLI with Zod validation
  • Interactive prompts via @bunli/runtime/prompt
  • Bunli TUI components (Form, SchemaForm, DataTable, ProgressBar, and more)
  • Publishing CLI to npm

Use OpenTUI skill when:

  • Building standalone terminal applications (not a CLI with subcommands)
  • Need advanced animation with timeline, keyframes, easing functions
  • Want full Flexbox/Yoga layout control
  • Creating custom renderable components
  • Need low-level buffer/terminal control
  • Building terminal games or complex interactive UIs

How They Connect

// Bunli TUI uses OpenTUI under the hood
import { Form, SchemaForm } from "@bunli/tui"      // Bunli's React components
// prompt is provided via handler args by Bunli         // Prompt + spinner runtime
import { useTimeline } from "@bunli/tui"          // Re-exported from opentui

// Drop down to OpenTUI for advanced control
import { h, Box, Text, instantiate } from "@opentui/core"
import { createCliRenderer } from "@opentui/core"

Package relationship:

  • @bunli/tui wraps @opentui/react
  • Bunli auto-wires the OpenTUI renderer runtime for render commands
  • @bunli/runtime/prompt provides prompt + spinner APIs used in handlers
  • Bunli TUI hooks (useKeyboard, useTimeline) are re-exported from OpenTUI
  • Renderer options map to OpenTUI renderer settings

When to combine:

  1. Start with Bunli for CLI structure
  2. Use @bunli/tui for common patterns (forms, wizards)
  3. Drop to OpenTUI when you need custom components, advanced animation, or full control