Commands
Define commands in one file or split them into modules.
Create a CLI with new Crust(name), define what it does with .action(), and call .execute() to run it.
import { Crust } from "@crustjs/core";
const app = new Crust("hello").action(({ stdout }) => {
stdout("Hello!");
});
await app.execute();
$ bun src/cli.ts
Hello!Choose how to add subcommands based on your app's size and how you want to organize and reuse its commands:
- Define inline with
.command()for small CLIs or short subcommands used only by this app. - Use
defineCommand()with.add()when a growing CLI needs separate command files or reusable definitions.
You can use both approaches in the same app.
Grow in one file
Keep small subcommands alongside the root with .command(). Its callback defines the subcommand's flags and action.
import { Crust } from "@crustjs/core";
const tool = new Crust("tool").command("build", (command) =>
command
.flags({ name: "minify", type: "boolean" })
.action(({ flags, stdout }) => stdout(`minify: ${flags.minify ?? false}`)),
);
await tool.execute();
$ tool build --minify
minify: trueHere, tool build --minify runs the build action with flags.minify set to true. Add more .command() calls before .execute() as your CLI grows.
Split across files
src/
├── commands/greet.ts
├── app.ts
└── cli.tsMove a subcommand into its own file with defineCommand(), then attach it with .add(). Keep shared app setup in app.ts and the single .execute() call in cli.ts.
Define a command
import { defineCommand } from "@crustjs/core";
export const greetCommand = defineCommand("greet", { description: "Greet someone" }, (command) =>
command
.args({ name: "name", type: "string", default: "world" })
.flags({ name: "greeting", type: "string", default: "Hello", short: "g" })
.action(({ args, flags, stdout }) => {
stdout(`${flags.greeting}, ${args.name}!`);
}),
);
Importing a command definition does not run its action. The optional second argument supplies metadata, such as the description shown in help. See the defineCommand() options for aliases, custom usage, additional sections, and hiding commands from listings.
Define the root
import { Crust } from "@crustjs/core";
import { help } from "@crustjs/extensions";
export const app = new Crust("my-cli", {
description: "Print greetings",
}).extend(help());
Set the CLI's name and description here, along with shared Extensions such as help().
Compose and execute
import { app } from "./app.ts";
import { greetCommand } from "./commands/greet.ts";
await app.add(greetCommand).execute();
Add your commands before calling .execute().
$ my-cli greet Ada
Hello, Ada!Unknown commands
$ tool deploy
Error: Unknown command "deploy".
$ echo $?
1The single-file tool example defines build, not deploy. Because tool has subcommands but no action of its own, an unrecognized name produces an error and exit code 1.
If the root has its own .action(), matching subcommands still take priority. Unmatched words are parsed as the root's positional arguments instead.