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 markdownThe 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/distUse 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, nodeUse 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 srcTokens 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.
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=undefinedThe 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
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 npmSet 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 $?
1Both defaulted and required values are non-optional in the action.
Aliases and negation
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=trueBoolean flags also accept --no-<name> and --no-<alias> unless noNegate: true:
$ serve --no-colour
color=falseSee the .flags() contract for all accepted spellings.
Repeated values
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=undefinedWithout 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=undefinedAn invalid value fails before the action runs:
$ serve --runtime deno
Error: Invalid value "deno" for --runtime. Expected one of: bun, node
$ echo $?
1Values 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=-nightlyReuse across commands
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 jsonValidation
Built-in conversion covers common values such as the number flag above. For custom parsers and Standard Schemas, see Values and validation.