Commands
Define CLI commands with full type inference using the Crust builder.
Commands are the building blocks of every Crust CLI. You define them using the chainable Crust builder, and Crust handles parsing, validation, and execution.
Defining a Command
Use the Crust class to create a command:
import { Crust } from "@crustjs/core";
const serve = new Crust("serve")
.meta({ description: "Start the development server" })
.run(() => {
console.log("Server started!");
});The command name is set via the constructor. Use .meta() to set description and usage — the description is optional but recommended as it appears in help output.
Command Metadata
Set metadata with .meta():
new Crust("serve")
.meta({
description: "Start the dev server", // Shown in help text
usage: "serve [port] [options]", // Overrides auto-generated usage
})See the Crust builder reference for the full list of metadata fields.
The .run() Handler
The .run() method sets the command's main logic. It receives a CrustCommandContext with parsed args, flags, and rawArgs:
new Crust("greet")
.args([
{ name: "name", type: "string", required: true },
])
.flags({
loud: { type: "boolean", short: "l" },
})
.run(({ args, flags, rawArgs }) => {
// args.name is typed as `string` (required)
// flags.loud is typed as `boolean | undefined`
// rawArgs contains everything after `--`
console.log(`Hello, ${args.name}!`);
})The handler can be async:
new Crust("fetch")
.run(async () => {
const data = await fetch("https://api.example.com/data");
console.log(await data.json());
})Running Commands
Call .execute() to run the CLI. It parses process.argv, resolves subcommands, runs plugins and middleware, and executes the matched handler:
import { Crust } from "@crustjs/core";
import { helpPlugin } from "@crustjs/plugins";
const main = new Crust("my-cli")
.use(helpPlugin())
.run(() => {
console.log("Running!");
});
await main.execute();.execute() catches all errors, prints them to stderr, and sets process.exitCode = 1. It never throws. For testing, pass a custom argv:
await main.execute({ argv: ["--help"] });Immutable Builder
Each method on the Crust builder returns a new instance — the original builder is never mutated. This makes commands safe to share and compose:
const base = new Crust("test")
.flags({ verbose: { type: "boolean" } });
const a = base.run(() => console.log("A"));
const b = base.run(() => console.log("B"));
// a and b are independent — base is unchangedPlugins can still inject flags during the setup phase via the addFlag action. This is the only sanctioned way to modify a command's flags after creation.
Type Inference
Crust infers the types of your args and flags from their definitions. No manual type annotations needed:
const cmd = new Crust("example")
.args([
{ name: "port", type: "number", default: 3000 },
{ name: "host", type: "string", required: true },
])
.flags({
verbose: { type: "boolean" },
output: { type: "string", default: "dist" },
count: { type: "number", required: true },
})
.run(({ args, flags }) => {
args.port; // number (has default -> guaranteed)
args.host; // string (required -> guaranteed)
flags.verbose; // boolean | undefined
flags.output; // string (has default -> guaranteed)
flags.count; // number (required -> guaranteed)
});Next Steps
- Arguments — positional argument definitions
- Flags — named flag definitions and flag inheritance
- Subcommands — nested command trees