Crust logoCrust

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 unchanged

Plugins 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

On this page