Function signature
packages/core/src/types.ts:189-193
export function defineCommand < TOptions extends Options = Options , TStore = {}, TName extends string = string >(
command : RunnableCommand < TOptions , TStore > & { name : TName }
) : RunnableCommand < TOptions , TStore > & { name : TName } {
return command
}
A type-safe helper function for defining CLI commands. It provides full TypeScript inference for command options, flags, and handler arguments.
Parameters
command
RunnableCommand<TOptions, TStore> & { name: TName }
required
A command object with all required properties. Command name (used for invocation)
Human-readable command description
Command-specific options/flags (use option() helper)
Command execution function (required if render is not provided)
render
RenderFunction<TFlags, TStore>
TUI render function (required if handler is not provided)
TUI-specific configuration
Return value
RunnableCommand
RunnableCommand<TOptions, TStore>
The same command object passed in, with full type inference preserved.
Command structure
A command must have either a handler, a render function, or both:
Handler-based command
Commands with a handler function execute in the terminal and have access to utilities like prompts, spinners, and shell commands.
import { defineCommand , option } from '@bunli/core'
import { z } from 'zod'
export const greetCommand = defineCommand ({
name: 'greet' ,
description: 'Greet a user' ,
options: {
name: option ( z . string (), {
description: 'Name to greet' ,
short: 'n'
}),
loud: option ( z . boolean (). default ( false ), {
description: 'Use uppercase' ,
short: 'l'
})
},
async handler ({ flags , colors , prompt }) {
const name = flags . name || await prompt . text ({
message: 'What is your name?'
})
const greeting = flags . loud
? `HELLO, ${ name . toUpperCase () } !`
: `Hello, ${ name } !`
console . log ( colors . green ( greeting ))
}
})
Render-based command
Commands with a render function use the OpenTUI renderer for interactive terminal UIs.
import { defineCommand , option } from '@bunli/core'
import { Box , Text } from '@bunli/tui'
import { z } from 'zod'
export const dashboardCommand = defineCommand ({
name: 'dashboard' ,
description: 'Show interactive dashboard' ,
options: {
refresh: option ( z . number (). default ( 1000 ), {
description: 'Refresh interval in ms'
})
},
render ({ flags }) {
return (
< Box flexDirection = "column" padding = { 1 } >
< Text bold > Dashboard </ Text >
< Text > Refresh : { flags . refresh } ms </ Text >
</ Box >
)
}
})
Hybrid command
Commands can provide both handler and render. The CLI automatically selects the render function in interactive terminals and falls back to the handler in non-interactive environments (CI, pipes, etc.).
export const buildCommand = defineCommand ({
name: 'build' ,
description: 'Build the project' ,
options: {
watch: option ( z . boolean (). default ( false ), {
description: 'Watch for changes'
})
},
async handler ({ flags , shell }) {
// Non-interactive build
await shell `tsc --build`
},
render ({ flags }) {
// Interactive build with progress UI
return < BuildProgress watch = { flags . watch } />
}
})
Handler arguments
The handler and render functions receive a comprehensive context object:
Parsed and validated command options
Positional arguments after the command name
Bun shell for executing commands
env
typeof process.env
required
Environment variables
Current working directory
Prompt utilities for user input
spinner
PromptSpinnerFactory
required
Spinner factory for loading states
Color utilities for terminal output
Plugin context with shared store (if plugins are loaded)
Terminal capabilities and dimensions
Runtime information (start time, args, command name)
Cooperative cancellation signal for interrupt handling
Command groups
For non-executable command groups (parent commands with subcommands), use defineGroup instead:
import { defineGroup , defineCommand } from '@bunli/core'
export const gitGroup = defineGroup ({
name: 'git' ,
description: 'Git-related commands' ,
commands: [
defineCommand ({
name: 'status' ,
description: 'Show git status' ,
handler ({ shell }) {
return shell `git status`
}
}),
defineCommand ({
name: 'commit' ,
description: 'Create a commit' ,
options: {
message: option ( z . string (), {
description: 'Commit message' ,
short: 'm'
})
},
handler ({ flags , shell }) {
return shell `git commit -m ${ flags . message } `
}
})
]
})
Type inference
The defineCommand function preserves full type information:
Options are inferred from the options object
Flags in the handler are typed based on option schemas
Command names are preserved as literal types for type-safe execution
const cmd = defineCommand ({
name: 'example' ,
description: 'Example command' ,
options: {
count: option ( z . number (). default ( 0 )),
name: option ( z . string ())
},
handler ({ flags }) {
// flags.count is typed as number
// flags.name is typed as string
}
})
option - Define command options with schemas
createCLI - Create a CLI instance to register commands