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(({ , , 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
import { Crust } from "@crustjs/core";
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 = args.input;
const = args.format;
const = args.label;
stdout(`${input} -> ${format}${label ? ` (${label})` : ""}`);
});
await convert.execute();
$ 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
import { Crust } from "@crustjs/core";
const copy = new Crust("copy")
.args(
{ name: "destination", type: "path", required: true },
{ name: "files", type: "path", variadic: true },
)
.action(({ args, stdout }) => {
const = args.files;
stdout(`${files.length} files to ${args.destination}`);
});
await copy.execute();
$ 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
import { Crust } from "@crustjs/core";
const run = new Crust("run")
.args({ name: "runtime", type: "string", choices: ["bun", "node"] })
.action(({ args, stdout }) => {
const = args.runtime;
stdout(`runtime: ${runtime}`);
});
await run.execute();
$ 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, plus undefined here because the argument is neither required nor defaulted, and an invalid value fails before the action runs.
Pass through raw tokens
import { Crust } from "@crustjs/core";
const wrap = new Crust("wrap").action(({ rawArgs, stdout }) => stdout(rawArgs.join(" ")));
await wrap.execute();
$ 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(({ , 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.
In this example, neither flag has required, default, or an environment binding, so omitting it gives an action value of 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(({ , 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 a value must be supplied. This example has no environment binding, so 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(({ , 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. Without a delimiter, each occurrence contributes one value in order:
$ serve --target linux -t darwin
targets=linux,darwin runtime=bun tag=undefinedWithout a default or environment binding, omitting a repeated flag gives undefined. Repeat the flag for every value, or configure a delimiter. 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 $?
1Environment variables
import { Crust } from "@crustjs/core";
const publish = new Crust("publish")
.flags(
{ name: "token", type: "string", env: { name: "PUBLISH_TOKEN" }, required: true },
{ name: "registry", type: "string", env: { name: "PUBLISH_REGISTRY" }, default: "npm" },
{
name: "tag",
type: "string",
multiple: true,
delimiter: ",",
env: { name: "PUBLISH_TAGS", delimiter: ":" },
},
)
.action(({ flags, stdout }) => {
stdout(`publishing to ${flags.registry} tags=${flags.tag?.join(",") ?? "undefined"}`);
});
await publish.execute();
Set env: { name: "VARIABLE" } to read a flag from that environment variable when it is absent from argv. Precedence is argv, then env, then default; an environment value also satisfies required. Environment bindings are declarations: variables are read during terminal invocation, not when defining the flag.
$ PUBLISH_TOKEN=example publish
publishing to npm tags=undefined
$ PUBLISH_TOKEN=example PUBLISH_REGISTRY=github publish --registry jsr
publishing to jsr tags=undefinedAfter its own optional splitting, the environment value enters the same conversion and validation path as argv, so type conversion, choices, parse, and schemas apply unchanged. Boolean flags accept true or 1 as true; any other value is false, and a noNegate flag rejects a false value the way it rejects --no-<name>.
The two delimiters are independent: flag-level delimiter splits string argv occurrences, while env.delimiter splits environment text. Both require multiple: true, use nonempty literal separators, and drop empty segments. Neither inherits the other's setting; without env.delimiter, the entire environment value is one occurrence. Boolean argv switches remain --flag / --no-flag (not --flag=true,false); repeated booleans can use env: { name: "FEATURES", delimiter: "," } for environment text such as true,false.
The example above uses commas on argv and colons in PUBLISH_TAGS:
$ PUBLISH_TOKEN=example PUBLISH_TAGS=latest::beta publish
publishing to npm tags=latest,beta
$ PUBLISH_TOKEN=example publish --tag latest,beta --tag next
publishing to npm tags=latest,beta,nextAn explicit argv occurrence always wins over the environment, even when it splits to zero occurrences. Invalid argv values fail rather than trying env; invalid environment values fail rather than trying the default. Zero occurrences after splitting follow the existing default/required or schema rules. Help, man pages, and generated skills list the variable as [env: PUBLISH_TOKEN] and never print its value. Programmatic run() binds structured input directly: it does not read the environment and never splits arrays. Positional arguments have no env; use a flag.
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=-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.