SkillAgentSearch skills...

Brocli

Modern type-safe way of building CLIs

Install / Use

/learn @drizzle-team/Brocli
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Brocli 🥦

Modern type-safe way of building CLIs with TypeScript or JavaScript
by Drizzle Team

import { command, string, boolean, run } from "@drizzle-team/brocli";

const push = command({
  name: "push",
  options: {
    dialect: string().enum("postgresql", "mysql", "sqlite"),
    databaseSchema: string().required(),
    databaseUrl: string().required(),
    strict: boolean().default(false),
  },
  handler: (opts) => {
    ...
  },
});

run([push]); // parse shell arguments and run command

Why?

Brocli is meant to solve a list of challenges we've faced while building Drizzle ORM CLI companion for generating and running SQL schema migrations:

  • [x] Explicit, straightforward and discoverable API
  • [x] Typed options(arguments) with built in validation
  • [x] Ability to reuse options(or option sets) across commands
  • [x] Transformer hook to decouple runtime config consumption from command business logic
  • [x] --version, -v as either string or callback
  • [x] Command hooks to run common stuff before/after command
  • [x] Explicit global params passthrough
  • [x] Testability, the most important part for us to iterate without breaking
  • [x] Themes, simple API to style global/command helps
  • [x] Docs generation API to eliminate docs drifting

Learn by examples

If you need API referece - see here, this list of practical example is meant to a be a zero to hero walk through for you to learn Brocli 🚀

Simple echo command with positional argument:

import { run, command, positional } from "@drizzle-team/brocli";

const echo = command({
  name: "echo",
  options: {
    text: positional().desc("Text to echo").default("echo"),
  },
  handler: (opts) => {
    console.log(opts.text);
  },
});

run([echo])
~ bun run index.ts echo
echo

~ bun run index.ts echo text
text

Print version with --version -v:

...

run([echo], {
  version: "1.0.0",
);
~ bun run index.ts --version
1.0.0

Version accepts async callback for you to do any kind of io if necessary before printing cli version:

import { run, command, positional } from "@drizzle-team/brocli";

const version = async () => {
  // you can run async here, for example fetch version of runtime-dependend library

  const envVersion = process.env.CLI_VERSION;
  console.log(chalk.gray(envVersion), "\n");
};

const echo = command({ ... });

run([echo], {
  version: version,
);

API reference

command

options

run

Brocli command declaration has:
name - command name, will be listed in help
desc - optional description, will be listed in the command help
shortDesc - optional short description, will be listed in the all commands/all subcommands help
aliases - command name aliases
hidden - flag to hide command from help
help - command help text or a callback to print help text with dynamically provided config
options - typed list of shell arguments to be parsed and provided to transform or handler
transform - optional hook, will be called before handler to modify CLI params
handler - called with either typed options or transform params, place to run your command business logic
metadata - optional meta information for docs generation flow

name, desc, shortDesc and metadata are provided to docs generation step

import { command, string, boolean } from "@drizzle-team/brocli";



const push = command({
  name: "push",
  options: {
    dialect: string().enum("postgresql", "mysql", "sqlite"),
    databaseSchema: string().required(),
    databaseUrl: string().required(),
    strict: boolean().default(false),
  },
  transform: (opts) => {
  },
  handler: (opts) => {
    ...
  },
});
import { command } from "@drizzle-team/brocli";

const cmd = command({
  name: "cmd",
  options: {
    dialect: string().enum("postgresql", "mysql", "sqlite"),
    schema: string().required(),
    url: string().required(),
  },
  handler: (opts) => {
    ...
  },
});

Option builder

Initial builder functions:

  • string(name?: string) - defines option as a string-type option which requires data to be passed as --option=value or --option value

    • name - name by which option is passed in cli args
      If not specified, defaults to key of this option
      :warning: - must not contain = character, not be in --help,-h,--version,-v and be unique per each command
      :speech_balloon: - will be automatically prefixed with - if one character long, -- if longer
      If you wish to have only single hyphen as a prefix on multi character name - simply specify name with it: string('-longname')
  • number(name?: string) - defines option as a number-type option which requires data to be passed as --option=value or --option value

    • name - name by which option is passed in cli args
      If not specified, defaults to key of this option
      :warning: - must not contain = character, not be in --help,-h,--version,-v and be unique per each command
      :speech_balloon: - will be automatically prefixed with - if one character long, -- if longer
      If you wish to have only single hyphen as a prefix on multi character name - simply specify name with it: number('-longname')
  • boolean(name?: string) - defines option as a boolean-type option which requires data to be passed as --option

    • name - name by which option is passed in cli args
      If not specified, defaults to key of this option
      :warning: - must not contain = character, not be in --help,-h,--version,-v and be unique per each command
      :speech_balloon: - will be automatically prefixed with - if one character long, -- if longer
      If you wish to have only single hyphen as a prefix on multi character name - simply specify name with it: boolean('-longname')
  • positional(displayName?: string) - defines option as a positional-type option which requires data to be passed after a command as command value

    • displayName - name by which option is passed in cli args
      If not specified, defaults to key of this option
      :warning: - does not consume options and data that starts with

Extensions:

  • .alias(...aliases: string[]) - defines aliases for option

    • aliases - aliases by which option is passed in cli args
      :warning: - must not contain = character, not be in --help,-h,--version,-v and be unique per each command
      :speech_balloon: - will be automatically prefixed with - if one character long, -- if longer
      If you wish to have only single hyphen as a prefix on multi character alias - simply specify alias with it: .alias('-longname')
  • .desc(description: string) - defines description for option to be displayed in help command

  • .required() - sets option as required, which means that application will print an error if it is not present in cli args

  • .default(value: string | boolean) - sets default value for option which will be assigned to it in case it is not present in cli args

  • .hidden() - sets option as hidden - option will be omitted from being displayed in help command

  • .enum(values: [string, ...string[]]) - limits values of string to one of specified here

    • values - allowed enum values
  • .int() - ensures that number is an integer

  • .min(value: number) - specified minimal allowed value for numbers

    • value - minimal allowed value
      :warning: - does not limit defaults
  • .max(value: number) - specified maximal allowed value for numbers

    • value - maximal allowed value
      :warning: - does not limit defaults

Creating handlers

Normally, you can write handlers right in the command() function, however there might be cases where you'd want to define your handlers separately.
For such cases, you'd want to infer type of options that will be passes inside your handler.
You can do it using TypeOf type:

import { string, boolean, type TypeOf } from '@drizzle-team/brocli'

const commandOptions = {
    opt1: string(),
    opt2: boolean('flag').alias('f'),
    // And so on... 
}

export const commandHandler = (options: TypeOf<typeof commandOptions>) => {
    // Your logic goes here...
}

Or by using handler(options, myHandler () => {...})

import { string, boolean, handler } from '@drizzle-team/brocli'

const commandOptions = {
    opt1: string(),
    opt2: boolean('flag').alias('f'),
    // And so on... 
}

export const commandHandler = handler(commandOptions, (options) => {
    // Your logic goes here...
});

Defining commands

To define commands, use command() function:

import { command, type Command, string, boolean, type TypeOf } from '@drizzle-team/brocli'

const commandOptions = {
    opt1: string(),
    opt2: boolean('flag').alias('f'),
    // And so on... 
}

const commands: Command[] = []

commands.push(command({
    name: 'command', 
    aliases: ['c', 'c
View on GitHub
GitHub Stars446
CategoryDevelopment
Updated2d ago
Forks5

Languages

TypeScript

Security Score

95/100

Audited on Mar 22, 2026

No findings