Crust logoCrust

Arguments and Flags

Choose and define typed positional and named command inputs.

Use a positional argument for the thing a command acts on, and a flag for how it should act.

import { Crust } from "@crustjs/core";

const app = new Crust("convert")
	.args({ name: "input", type: "string", required: true })
	.flags({ name: "format", type: "string", default: "html" })
	.action(({ args, flags, stdout }) => stdout(`Converting ${args.input} as ${flags.format}`));

await app.execute();
$ convert report.md --format markdown
Converting report.md as markdown

The Command Action receives both values under the names in their definitions.

Arguments

const convert = new Crust("convert")
	.args(
		{ name: "input", type: "string", required: true },
		{ name: "format", type: "string", default: "json" },
		{ name: "label", type: "string" },
	)
	.action(({ args, stdout }) => {
		const input = args.input; // string
		const format = args.format; // string
		const label = args.label; // string | undefined
		stdout(`${input} -> ${format}${label ? ` (${label})` : ""}`);
	});
$ convert report.md
report.md -> json
$ convert report.md yaml draft
report.md -> yaml (draft)

Positional values fill definitions in order. Set required: true when omission should fail, use default for a fallback, and leave both out for an optional value. These definitions infer string, string, and string | undefined in the Command Action; see ArgDef for the full definition.

Variadic arguments

const copy = new Crust("copy")
	.args(
		{ name: "destination", type: "path", required: true },
		{ name: "files", type: "path", variadic: true },
	)
	.action(({ args, stdout }) => stdout(`${args.files.length} files to ${args.destination}`));
$ copy dist a.ts b.ts
2 files to /home/me/project/dist

Use a final variadic argument when the command accepts any number of values in that position. The Command Action receives those values as an array; type: "path" resolves each value against the working directory.

Choices

const run = new Crust("run")
	.args({ name: "runtime", type: "string", choices: ["bun", "node"] })
	.action(({ args, stdout }) => stdout(`runtime: ${args.runtime}`));
$ run deno
Error: Invalid value "deno" for <runtime>. Expected one of: bun, node

Use choices when the accepted strings are known in advance. The Command Action gets their string union, and an invalid value fails before the action runs.

Pass through raw tokens

const wrap = new Crust("wrap").action(({ rawArgs, stdout }) => stdout(rawArgs.join(" ")));
$ wrap -- --watch src
--watch src

Tokens after -- bypass argument and flag parsing and reach the Command Action as rawArgs. Use this for wrapper commands that pass options to another tool.

Flags

Use boolean flags for switches and value-taking flags for named settings.

src/cli.ts
import { Crust } from "@crustjs/core";

const serve = new Crust("serve")
	.flags({ name: "color", type: "boolean" }, { name: "port", type: "number" })
	.action(({ flags, stdout }) => {
		stdout(`color=${String(flags.color)} port=${String(flags.port)}`);
	});

await serve.execute();
$ serve --color --port 3000
color=true port=3000
$ serve
color=undefined port=undefined

The definitions use name: "color" and name: "port" without dashes. A user types --color --port 3000, and the action reads flags.color and the converted number in flags.port.

Without required or default, either flag may be omitted and its action value is undefined. When supplied, a value-taking flag needs one value, so serve --port fails. Built-in types convert CLI text and reject invalid input before the action runs:

$ serve --port nope
Error: Expected number for --port, got "nope"

Required and defaulted values

src/publish.ts
import { Crust } from "@crustjs/core";

const publish = new Crust("publish")
	.flags(
		{ name: "token", type: "string", required: true },
		{ name: "registry", type: "string", default: "npm" },
	)
	.action(({ flags, stdout }) => {
		stdout(`publishing to ${flags.registry}`);
	});

await publish.execute();

Use default when omission should choose a value. This success case omits --registry, so the action receives its npm default. The action does not print the token:

$ publish --token example
publishing to npm

Set required: true when the user must supply a value. A missing required flag fails before the action runs:

$ publish
Error: Missing required flag "--token"
$ echo $?
1

Both defaulted and required values are non-optional in the action.

Aliases and negation

src/serve.ts
import { Crust } from "@crustjs/core";

const serve = new Crust("serve")
	.flags({ name: "color", type: "boolean", short: "c", aliases: ["colour"] })
	.action(({ flags, stdout }) => {
		stdout(`color=${String(flags.color)}`);
	});

await serve.execute();

Set short for a one-character alias and aliases for long aliases. Every spelling writes to the property named by name:

$ serve --colour
color=true
$ serve -c
color=true

Boolean flags also accept --no-<name> and --no-<alias> unless noNegate: true:

$ serve --no-colour
color=false

See the .flags() contract for all accepted spellings.

Repeated values

src/serve-values.ts
import { Crust } from "@crustjs/core";

const serve = new Crust("serve")
	.flags(
		{ name: "target", type: "string", multiple: true, short: "t" },
		{ name: "runtime", type: "string", choices: ["bun", "node"], default: "bun" },
		{ name: "tag", type: "string" },
	)
	.action(({ flags, stdout }) => {
		stdout(
			`targets=${flags.target?.join(",") ?? "undefined"} runtime=${flags.runtime} tag=${String(flags.tag)}`,
		);
	});

await serve.execute();

Set multiple: true when a flag may occur more than once. Each occurrence contributes one value in order:

$ serve --target linux -t darwin
targets=linux,darwin runtime=bun tag=undefined

Without a default, omitting a repeated flag gives undefined. Repeat the flag for every value. In --target linux darwin, darwin is a positional argument, not another target.

Flag choices

Use choices for a fixed set of strings. An accepted value reaches the action:

$ serve --runtime node
targets=undefined runtime=node tag=undefined

An invalid value fails before the action runs:

$ serve --runtime deno
Error: Invalid value "deno" for --runtime. Expected one of: bun, node
$ echo $?
1

Values that start with -

For a value-taking flag, attach a dash-leading value with = so it is not mistaken for another flag:

$ serve --tag=-nightly
targets=undefined runtime=bun tag=-nightly

Reuse across commands

src/tools.ts
import { Crust, defineCommand, defineFlag } from "@crustjs/core";

const format = defineFlag("format", { type: "string", default: "json" });
const print = defineCommand("print", (command) =>
	command.flags(format).action(({ flags, stdout }) => stdout(`print as ${flags.format}`)),
);
const inspect = defineCommand("inspect", (command) =>
	command.flags(format).action(({ flags, stdout }) => stdout(`inspect as ${flags.format}`)),
);

await new Crust("tools").add(print, inspect).execute();

Use defineFlag() when several commands need the same definition. Attach it to every command that should parse the flag. Both commands above consume format:

$ tools print --format yaml
print as yaml
$ tools inspect
inspect as json

Validation

Built-in conversion covers common values such as the number flag above. For custom parsers and Standard Schemas, see Values and validation.

On this page