Crust logoCrust

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.

src/cli.ts
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.

src/cli.ts
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: true

Here, 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.ts

Move 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

src/commands/greet.ts
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

src/app.ts
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

src/cli.ts
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 $?
1

The 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.

On this page