A Deno-first, composable command-line framework for building modern CLIs with subcommands, typed options, nested flags, events, and rich help output.
@denoboot/commandline is designed to feel familiar if you’ve used tools like commander, but with a few strong opinions:
- Native Deno support
- Explicit command + option modeling
- Built-in subcommands, aliases, and global options
- Dot-notation options (
--config.db.host) - Variadic and required args (
<arg>,[arg],[...args]) - Event-driven command lifecycle
- Zero Node.js shims
- 🧱 Subcommands with aliases
- 🌍 Global options shared across commands
- 🧠 Required, optional, and variadic arguments
- 🪜 Dot-nested options (
--env.prod.url) - 🔁 Option value transforms (typed arrays)
- 🧾 Automatic help & version output
- 🧩 Extensible help sections
- 📡 EventEmitter-based command hooks
- 🚫 Unknown option detection (configurable)
- 🦕 Pure Deno, no Node dependencies
deno add jsr:@denoboot/commandlineimport CommandLine from "@denoboot/commandline/mod.ts";import CommandLine from "@denoboot/commandline/mod.ts";
const cli = new CommandLine("mycli");
cli
.help()
.version("1.0.0");
cli.parse(Deno.args);Running:
$ mycli --help
$ mycli --versionCommands are defined using a concise syntax that also declares arguments.
cli.command("build <entry> [outDir]", "Build the project")
.option("-m, --minify", "Minify output")
.action((entry, outDir, options) => {
console.log({ entry, outDir, options });
});| Syntax | Meaning |
|---|---|
<arg> |
Required argument |
[arg] |
Optional argument |
[...args] |
Variadic arguments |
.option("-f, --force", "Force overwrite").option("-o, --output <dir>", "Output directory").option("--port <number>", "Server port", {
default: 3000,
});If type is an array, the option will always be coerced into an array.
.option("--tag <tag>", "Add tag", {
type: [String],
});--tag a --tag b
# => options.tag = ["a", "b"]Dot notation automatically builds nested objects.
.option("--db.host <host>", "Database host")
.option("--db.port <port>", "Database port")--db.host localhost --db.port 5432options.db.host // "localhost"
options.db.port // 5432You can also use wildcards:
.option("--env.* <value>", "Environment variables")Global options apply to all commands.
cli.option("-d, --debug", "Enable debug logging");These are merged automatically into each command’s options.
cli.help();Adds -h, --help automatically.
cli.version("1.2.3");Outputs:
mycli/1.2.3 darwin arm64You can intercept and modify help output.
cli.help((sections) => {
sections.push({
title: "Environment",
body: "MYCLI_DEBUG=1 Enable debug mode",
});
return sections;
});Add examples that show up in --help.
cli.example((bin) => {
return `$ ${bin} build src/index.ts dist/`;
});Or static examples:
cli.example("$ mycli build app.ts");cli.command("serve", "Start server")
.alias("s")
.action(() => {});mycli sA command with an empty name acts as the default.
cli.command("", "Default command")
.action(() => {
console.log("No subcommand provided");
});By default, unknown options throw an error.
You can opt out per command:
cli.command("run", "Run task")
.allowUnknownOptions()
.action(() => {});cli.command("test", "Run tests")
.ignoreOptionDefaultValue();This prevents default values from being injected automatically.
The CLI emits events during parsing:
cli.on("command:build", () => {
console.log("Build command matched");
});
cli.on("command:*", () => {
console.log("Unknown command");
});const { args, options } = cli.parse(Deno.args, {
run: false,
});All CLI errors throw CommandLineError.
import { CommandLineError } from "@denoboot/commandline/mod.ts";You can catch and format them yourself if needed.
- Explicit over implicit
- Commands are data
- Help output is first-class
- No Node compatibility hacks
- Composable with other denoboot packages
This package is intended to be a foundational building block for larger tooling ecosystems, not just one-off scripts.
@denoboot/argparse@denoboot/events