Crust logoCrust

Types

Public command definitions, snapshots, Command Action contexts, Contexts, and Extensions.

All types on this page are exported from @crustjs/core. Tables summarize object properties; the readable signature sketches do not reproduce every generic constraint or overload.

Definition types

Exact declarations.

ValueType

type ValueType = "string" | "number" | "boolean" | "url" | "path" | "json";
ValueRuntime value
stringstring
numbernumber
booleanboolean
urlURL
pathAbsolute string, with ~ expanded
jsonunknown from JSON.parse()

ArgDef

type ArgsDef = readonly ArgDef[];

Core-value argument definitions share these fields:

Prop

Type

A core variadic value is always an array. Required core variadics without defaults accept and resolve to nonempty [T, ...T[]] tuples after validation; optional and default-backed variadics remain T[]. parse runs once per element for a variadic string argument.

const args = [
  { name: "target", type: "string", choices: ["browser", "node"] },
  { name: "files", type: "path", variadic: true },
] as const satisfies ArgsDef;

A schema-backed argument has name, optional description and variadic, and a Standard Schema:

const args = [{ name: "port", schema: z.coerce.number().int().min(1) }] as const satisfies ArgsDef;

The schema receives string | undefined, or string[] for a variadic argument. It exclusively owns coercion, defaults, requiredness, choices, and validation, so type, required, default, choices, and parse are not available in this variant. The schema's output type reaches the Command Action.

FlagDef

type FlagsDef = Record<string, FlagDef>;

Core-value flag definitions support:

Prop

Type

const flags = {
  verbose: { type: "boolean", short: "v" },
  include: { type: "string", multiple: true },
  port: { type: "number", default: 3000 },
} satisfies FlagsDef;

Schema-backed flags support type: "string" | "boolean", schema, and the syntax fields description, short, aliases, and multiple. Boolean schema flags also support noNegate. type only declares token consumption: a string flag consumes a token; a boolean flag is a toggle.

const flags = {
  port: { type: "string", schema: z.coerce.number().int().min(1) },
  enabled: { type: "boolean", schema: z.boolean().default(false) },
} satisfies FlagsDef;

The schema receives the raw string | undefined or boolean | undefined. With multiple: true, it receives string[] or boolean[] when the flag appears at least once, and undefined when omitted. Variadic arguments, by contrast, always supply an array.

required, default, choices, and parse cannot be mixed into schema mode.

MergeFlags

type MergeFlags<Base extends FlagsDef, Override extends FlagsDef> = Base & Override;

The intersection of two flag definition maps.

NamedFlagDef

type NamedFlagDef = FlagDef & { readonly name: string };

The public authoring shape: a flag definition carrying its name. .flags(...defs), Context-owned flags, and defineFlag() all use it.

defineFlag and defineArg

Use these const-generic helpers when a definition is declared separately from .flags() or .args(). They preserve literal fields without as const:

import { defineArg, defineFlag } from "@crustjs/core";

const verbose = defineFlag("verbose", { type: "boolean", short: "v" });
const target = defineArg("target", { type: "string", required: true });

defineFlag(name, def) returns a named flag definition; defineArg(name, def) returns a named argument definition. Literal fields retain inference and provably invalid names/defaults/parsers fail typecheck; an invalid literal member of a name union is rejected even beside an open template member. Broad names and dynamic definitions use the same helpers and are checked automatically at consumption.

Helpers own readonly structural arrays, without cloning payloads; local proof does not certify another destination. UnnamedArgDef is the argument helper input without its name.

CommandMeta

Prop

Type

version is the application version declared through new Crust(name, { version }); it is available on the root CommandSnapshot.meta for Extensions and tooling. sections is an ordered list of plain-text { title, body } entries, optionally scoped to renderer identities with only or except audiences. At consumption, audience filtering runs first, then entries with the same exact, case-sensitive title merge at their first position with bodies joined by a single newline.

Declare sections in the metadata passed to new Crust() or defineCommand(); help and man render them after their built-in sections. hidden removes a command from user-facing help, completion, man-page, typo-suggestion, and skill listings without disabling direct routing.

SectionConsumer, SectionAudience, CommandSectionInput, and CommandSection

type SectionConsumer = ExtensionId | { readonly id: ExtensionId };

type SectionAudience =
  | { readonly only: readonly [SectionConsumer, ...SectionConsumer[]]; readonly except?: never }
  | { readonly except: readonly [SectionConsumer, ...SectionConsumer[]]; readonly only?: never }
  | { readonly only?: never; readonly except?: never };

type CommandSectionInput = { readonly title: string; readonly body: string } & SectionAudience;

SectionConsumer identifies a renderer: a branded ExtensionId, or any value exposing one at .id (such as the help, man, and skills Extension factory values). SectionAudience is the optional only/except scoping accepted on authored sections, and CommandSectionInput is the ordinary command-authoring shape. Extension section callbacks retain their consumption-time validation for dynamic contributions.

CommandSection is the validated section stored on snapshot meta.sections, with its audience consumers resolved to nonempty readonly ExtensionId tuples. Metadata accepts typed dynamic audience arrays and checks nonemptiness when consumed.

Command Snapshots

Command Snapshots are readonly, serializable descriptions. They omit Command Actions, parsers, schemas, and Context functions. Core freezes the snapshot structure at public boundaries, but does not clone or freeze object-valued JSON defaults.

CommandSnapshot

Prop

Type

flags contains effective local and Context-owned flags. meta.sections contains the individual frozen authored and Extension-contributed sections for that command; same-title merging happens only when a renderer consumes them. Command Actions, Extension hooks, and COMMAND_NOT_FOUND details receive snapshots rather than mutable runtime internals.

ArgSnapshot

Prop

Type

FlagSnapshot

Prop

Type

Snapshots omit schemas and parse functions. Every flag snapshot includes negatable, derived from the parser's spelling policy. URL defaults become strings, and array defaults are recursively copied and frozen.

Object-valued JSON defaults, including objects inside arrays, remain shared references; treat them as immutable.

Typed invocation

Exact declarations.

Types inferred from the application definition that back run(path, input?, io?) and the @crustjs/testing helpers. CommandTree is the compile-time tree accumulated by .add(); CommandPath enumerates every valid path through it (including the root path []); CommandShapeAt resolves the CommandShape a path selects; and RunInput is the structured args/flags/raw input for that shape, built from the pre-parse InputArgs and InputFlags value types. When calling run(), inputs declared with type: "json" accept JSON primitives, structurally JSON-compatible object types (including named interfaces), and mutable or readonly arrays/tuples (RunInput itself and the testing helpers constrain leaves to JsonValue); variadic JSON arguments and multiple JSON flags use a mutable array whose items are JSON values.

Inputs declared with type: "url" accept URL; Date, bigint, functions, and other non-JSON values are excluded from JSON inputs. RunInputArguments and RunArguments are the tuple types that make input optional when the selected command requires nothing; RunOutcome is the status-discriminated value run() resolves to. CommandHandle<Shape> is the path-bound invoker returned by at(path): a readonly path plus run(input?, io?) typed from Shape. AnyCrust accepts any fully-built application.

import type { CommandPath, CommandShapeAt, RunInput } from "@crustjs/core";

type Tree = (typeof app)["_types"]["tree"];
type Path = CommandPath<Tree>; // e.g. readonly [] | readonly ["greet"]
type GreetInput = RunInput<CommandShapeAt<(typeof app)["_types"]["shape"], readonly ["greet"]>>;

InputArgs requires a supplied prefix: defaults do not fill gaps, and required trailing arguments require preceding positions. Known choices, required fields, and paths remain strict. Conditional definitions retain conservative obligations; fully erased/open shapes accept broad structured input while preserving independently known facts.

See run() input.

Crust defaults its argument state to []. Prefer inferred authoring builders.

AnyCrust is for completed-app inspection, execute(), and broad run() with an unknown action result; it has no authoring methods and cannot be assigned back to an empty root. Crust<Flags, ArgsDef> intentionally describes an open argument state.

Crust._types is a supported type-level seam for accessing an application's inferred flags, arguments, Contexts, command tree, command shape, and authored root metadata (rootMeta); it has no runtime value.

RunOutcome<Result>

type RunOutcome<Result> = {
  readonly stdout: string;
  readonly stderr: string;
} & (
  | { readonly status: "completed"; readonly result: Result }
  | { readonly status: "finished"; readonly by: ExtensionId }
  | { readonly status: "failed"; readonly error: unknown }
);

Core run() is quiet, captures output, and returns invocation failures instead of rejecting them. Narrow status before reading the branch-specific field. See capture semantics for live sinks, output boundaries, and memory cost.

This differs from the internal hook-facing InvocationOutcome, which does not carry captured output or action results.

Parser boundary types

ParseResult<A, F> is the syntax-parsed result before required-value and Standard Schema validation. Required core variadics still have ordinary array types here, because binding can produce an empty array before a finishing hook skips validation. Its generic args and flags preserve definition-specific raw value types; ParsedArgValue and ParsedFlagValue are the runtime-erased fallbacks for widened definitions.

Its excessArgs holds positionals before -- that no declared argument consumed (validation rejects them); its rawArgs holds tokens after --. ValidatedInput<A, F> is the validated boundary passed onward to Command Actions, with InferArgs<A> and InferFlags<F> applied.

Most application authors consume the inferred Action context rather than naming these types directly. They are exported for parser integrations and tooling that need to distinguish syntax parsing from schema-validated values.

Invocation I/O

InvocationIO

Prop

Type

The same injectable callbacks are passed to the Command Action and every Context setup constructed for that invocation.

Command Action context

CrustCommandContext<A, F, Ctx>

Prop

Type

Ctx (default {}) is the inferred Context registry for the command path; it backs the lazy ctx bag as ContextBag<Ctx>. rootCommand is the readonly serializable snapshot of the application root, including Extension contributions. InferArgs and InferFlags are internal type plumbing, not root exports.

.args(), .flags(), and .provide() infer these fields without explicit generic arguments.

Context types

Exact declarations.

ContextFactory

interface ContextFactory<Name extends string, Options, Value, OF, Deps> {
  (options: Options): ContextInstance<Name, Value, OF, Deps>;
  readonly contextName: Name;
  of(value: Value): ContextInstance<Name, Value, OF, {}>;
}

defineContext(name, setup) and defineContext(name, config, setup) always return a factory. Invoke it even when Options is void. contextName is the bag property name.

.of(value) creates a precomputed instance with no dependencies while retaining owned flags.

ContextConfig, ContextSetup, and ContextBag

Prop

Type

Prop

Type

type ContextBag<Deps> = {
  readonly [K in keyof Deps]: Promise<Deps[K]>;
};

flags declares propagating flags. uses declares Context dependencies and determines the exact lazy bag exposed to setup. Reading a property starts memoized construction; destructuring therefore starts construction immediately.

Setup also receives the invocation's stdout and stderr callbacks, and defer(cleanup). Deferred callbacks run after post-run hooks in reverse registration order, even when setup later throws; call defer right after acquiring each resource. defer throws a DEFINITION error once setup has settled.

ContextInstance

Prop

Type

Attach helper-created instances with .provide(), or .provide(...instances) for dynamic collections. Owned-flag/dependency state cannot be erased into an explicitly empty ContextInstance holder. The default open instantiation is not an existential upper bound; prefer inference or a generic identity helper.

Construction stays lazy.

FactoryValueOf<F>

type FactoryValueOf<F extends AnyContextFactory> = /* awaited Context value */;

Extracts the awaited value type a Context factory produces: FactoryValueOf<typeof config> is the value await ctx.config resolves to. Useful when typing helpers that consume a Context value outside an inferred bag.

ContextMap

type ContextMap = object;

The upper bound for the name/value registry accumulated by .provide() and Extension provides. It is a phantom constraint: the builder infers the concrete property map, so consumer-defined context shapes do not need a string index signature. Command actions receive ContextBag<Ctx> for that inferred registry.

Reusable command definitions and Extensions expose only Contexts declared through .use() (command definitions) or the uses config field (Extensions).

MergeContext

type MergeContext<A, B> = A & B;

The intersection of two Context maps.

Extension types

Exact declarations.

ExtensionConfig

Prop

Type

commands entries are defineCommand() definitions contributed at the application root. sections(snapshot) returns an array of plain-text sections targeted by canonical command paths relative to that root; contributions append to each target's meta.sections in Extension registration order. Renderers filter audiences and merge exact same-title entries while preserving this body order.

build(ctx) runs in Extension registration order when invoked by build tooling and may be async. It returns BuildArtifacts, the files for the tooling to write; its errors propagate to the caller. See Extensions.

ExtensionSectionContribution

type ExtensionSectionContribution = RuntimeCommandSectionInput & {
  readonly command: readonly string[];
};

The entry shape inside the collection returned by ExtensionConfig.sections(snapshot). RuntimeCommandSectionInput accepts typed audience arrays whose nonemptiness is checked at consumption: a plain-text section plus the required canonical command path it targets, relative to the application root ([] targets the root command).

ExtensionBuildContext

Prop

Type

The build context contains the frozen root Command Snapshot. It deliberately carries no output directory: build tooling owns that tree, so a hook's only way to ship a file is to return it, and the BuildReport lists every shipped file. Its optional MetaKeys type parameter refines required snapshot metadata fields.

BuildFile

Prop

Type

One file a build hook produces: a path relative to the build output directory and its string or Uint8Array content. Build tooling normalizes separators, rejects absolute or escaping paths, and rejects a path that collides with one already produced, in this or an earlier hook: equal paths, compared case-insensitively so the tree stays valid on case-insensitive filesystems, and paths nested under or above an existing file, since a file and a directory cannot share a name.

BuildArtifacts

Prop

Type

The files returned by one build hook; an empty array means the hook ships nothing.

BuildReport

Prop

Type

One entry per executed build hook, in execution order, listing exactly the files build tooling wrote for it. crust build records this per bin entry under build in .crust/manifest.json.

Extension

Prop

Type

defineExtension(id, config?) returns a frozen helper-owned value with private defining data; omitted config defaults to {}. Mint the branded ExtensionId with defineExtensionId(), which requires a non-empty, already-trimmed string.

ExtensionFactory

type ExtensionFactory<
  Args extends readonly unknown[] = [],
  Deps extends ContextMap = {},
  Provides extends readonly AnyContextInstance[] = [],
  Defs extends readonly NamedExtensionFlagDef[] = [],
  Commands extends readonly CommandDefinition<any, any, any, any>[] = [],
  MetaKeys extends RootMetaKey = never,
  HookDeps extends ContextMap = Deps,
> = ((...args: Args) => Extension<Deps, Provides, Defs, Commands, MetaKeys, HookDeps>) & {
  readonly id: ExtensionId;
};

defineExtension(id, factory) wraps a config-returning callback, preserving its parameters and inferring its flags, dependencies, providers, and commands. Each call follows the object form's normalization and freezing path. Contribution namespaces are closed by default.

Preserve literal tuples in published annotations for precise typed invocation. To keep a namespace open, explicitly use ContextMap, readonly ContextInstance[], readonly NamedExtensionFlagDef[], or readonly CommandDefinition<any, any, any, any>[] for its parameter. MetaKeys defaults to never, meaning no required metadata.

HookDeps defaults to Deps. An annotation for a metadata-requiring factory must preserve its metadata keys. See authoring an Extension.

RootMetaKey and DefineExtensionWith

RootMetaKey is keyof RootCommandMeta. defineExtension<MetaKeys>() returns DefineExtensionWith<MetaKeys>, whose config and factory overloads infer the remaining types. Required fields retain their schema types and become non-optional in root snapshots passed to hooks, build, and sections.

.extend() checks that the root's inferred metadata supplies them.

Use the curried declaration to require keys without losing flag, Context, or factory-argument inference. These requirements are compile-time-only.

ExtensionFlagDef

type ExtensionFlagDef = FlagDef & { readonly recursive?: boolean };

An Extension owns contributed flags. recursive defaults to true; false contributes the flag only to the root.

NamedExtensionFlagDef

type NamedExtensionFlagDef = NamedFlagDef & { readonly recursive?: boolean };

The public authoring shape accepted by ExtensionConfig.flags: an Extension flag definition carrying its name and optional recursive scope.

InferExtensionFlags<Defs>

Infers the syntax-parsed values from an Extension's named-definition array and exposes them to its hooks. A flag with recursive: false may be undefined when a descendant command is invoked because that flag is contributed only to the root.

ExtensionContext

Prop

Type

args and flags are syntax-parsed values. Extension hooks do not alter application Command Action types.

ExtensionHooks

Prop

Type

preRun runs in .extend() order after routing and input binding (argv parsing for execute(), structured binding for typed run()), before validation. Its declared Context bag is lazy, but accessing a flag-owning Context directly or transitively rejects before validation; a later post-validation access re-executes that Context's setup from the start. Return ctx.finish() to end successfully.

onError is used by .execute() only. For failures raised during dispatch before postRun, it settles before postRun; return true after rendering to stop the chain, or a falsy value to continue to Core's renderer. postRun is the reverse-.extend()-order finally hook: it can pull or reuse Contexts, observes any failed-outcome attribution from onError, and runs for every Extension after a completed, finished, or failed invocation.

Invocation Contexts remain available through postRun, and disposal follows. An onError call for a post-run or disposal failure runs after cleanup, with Context pulls unavailable. Routing and parse failures use a fallback context with no available Contexts.

See failure handling.

Finished

Prop

Type

ctx.finish() returns this opaque token. Return that exact value from preRun; do not construct one yourself.

InvocationOutcome

Prop

Type

postRun receives completed after a Command Action completes, finished with the short-circuiting Extension's by id, or failed with the thrown value. A failed outcome's optional by identifies the Extension whose onError returned true; it is absent when Core's fallback renderer handled the failure.

Error types

See Errors for CrustError, CrustErrorCode, CrustErrorDetails, CrustErrorDetailsMap, CrustErrorJson (the serializable shape returned by .toJSON()), and the four detail interfaces.

On this page