@crustjs/core 0.0.19 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,1250 +1,1244 @@
1
+ import { A as CollisionBrand, B as UnionToIntersection, C as ValidatedInput, D as CommandSnapshot, E as ArgSnapshot, F as IsStaticTuple, H as defineExtensionId, I as IsUnion, L as LocalValueBrand, M as EmptyLiteralNameBrand, N as HasClosedNames, O as FlagSnapshot, P as IsClosedName, R as MergeContext, S as SectionConsumer, T as RunInputPayload, U as JsonCompatible, V as ExtensionId, W as JsonValue, _ as ParseResult, a as CommandSectionInput, b as RuntimeCommandSectionInput, c as FlagsDef, d as InputArgs, f as InputFlags, g as NamedFlagsRecord, h as NamedFlagDef, i as CommandSection, j as DefName, k as Awaitable, l as InferArgs, m as MergeFlags, n as ArgsDef, p as InvocationIO, r as CommandMeta, s as FlagDef, t as ArgDef, u as InferFlags, v as ParsedArgValue, w as ValueType, x as SectionAudience, y as ParsedFlagValue, z as MergeProviders } from "./types-DjMHz7M6.js";
2
+ //#region src/validation/args.brands.d.ts
3
+ type ArgNames<A extends readonly object[]> = DefName<A[number]>;
4
+ type DuplicateArgBrand<A, Existing extends string> = CollisionBrand<DefName<A>, Existing, "FIX_DUPLICATE_ARG", "Argument name ", " is already defined">;
5
+ type EmptyArgNameError = {
6
+ readonly FIX_EMPTY_NAME: "Argument names must be non-empty";
7
+ };
8
+ /** Reject empty argument names, including empty members of a name union. */
9
+ type EmptyArgNameBrand<Name extends string> = EmptyLiteralNameBrand<Name, EmptyArgNameError>;
10
+ type EmptyArgDefinitionNameBrand<A> = "" extends DefName<A> ? EmptyArgNameError : {};
11
+ type ArgChecks<A, Existing extends string> = A & DuplicateArgBrand<A, Existing> & LocalValueBrand<A> & EmptyArgDefinitionNameBrand<A>;
1
12
  /**
2
- * The result of resolving a command from an argv array.
3
- *
4
- * Contains the resolved (sub)command and argv after subcommand
5
- * resolution, and the full command path for help text rendering.
6
- */
7
- interface CommandRoute {
8
- /** The routed command (may be a subcommand of the original) */
9
- command: CommandNode;
10
- /** The argv after subcommand names have been consumed */
11
- argv: string[];
12
- /** The command path for help text (e.g. ["crust", "generate", "command"]) */
13
- commandPath: string[];
14
- }
13
+ * Per-arg validation tuple type. Resolves to `A` when the constraints are
14
+ * satisfied: only the last arg is variadic, names are unique, and custom
15
+ * parsers are synchronous. Invalid definitions receive a branded property.
16
+ *
17
+ * Generalized to work with any ordered tuple of object-typed definitions.
18
+ * Uses `readonly object[]` to avoid TypeScript's weak type detection
19
+ * (all-optional constraint rejection).
20
+ *
21
+ * ```
22
+ * Property 'FIX_VARIADIC_POSITION' is missing in type '{ name: "files"; ... variadic: true }'
23
+ * but required in type
24
+ * '{ readonly FIX_VARIADIC_POSITION: "Only the last positional argument can be variadic" }'.
25
+ * ```
26
+ */
27
+ type ValidateVariadicArgs<A extends readonly object[], Existing extends string = never> = A extends readonly [infer Head, ...infer Tail extends readonly object[]] ? Tail extends readonly [unknown, ...unknown[]] ? Head extends {
28
+ variadic: true;
29
+ } ? readonly [ArgChecks<Head, Existing> & {
30
+ readonly FIX_VARIADIC_POSITION: "Only the last positional argument can be variadic";
31
+ }, ...ValidateVariadicArgs<Tail, Existing | DefName<Head>>] : readonly [ArgChecks<Head, Existing>, ...ValidateVariadicArgs<Tail, Existing | DefName<Head>>] : readonly [ArgChecks<Head, Existing>] : { [I in keyof A]: ArgChecks<A[I], Existing>; };
32
+ type BrandVariadicPosition<A extends readonly object[]> = { [I in keyof A]: A[I] & {
33
+ readonly FIX_VARIADIC_POSITION: "Only the last positional argument can be variadic";
34
+ }; };
35
+ type AppendArgsChecks<A extends ArgsDef, NewA extends ArgsDef> = A extends readonly [...unknown[], infer Last] ? Last extends {
36
+ variadic: true;
37
+ } ? BrandVariadicPosition<ValidateVariadicArgs<NewA, ArgNames<A>>> : ValidateVariadicArgs<NewA, ArgNames<A>> : ValidateVariadicArgs<NewA>;
38
+ /** Conditional collections and uncertain canonical identities cannot promise every alternative output key. */
39
+ type AttachedArgs<A extends ArgsDef> = HasClosedNames<A> extends true ? A : ArgsDef;
40
+ //#endregion
41
+ //#region src/validation/commands.brands.d.ts
42
+ /** Preserve configured aliases; only a genuinely absent field proves an empty set. */
43
+ type AliasesOf<C> = C extends {
44
+ readonly aliases: infer A extends readonly string[];
45
+ } ? A : "aliases" extends keyof C ? readonly string[] : readonly [];
46
+ type NarrowAliases<A extends readonly string[]> = IsClosedName<A[number]> extends true ? A[number] : never;
47
+ type AliasShapeError<Name extends string, Alias extends string> = Alias extends "" ? `Subcommand "${Name}" has an invalid alias: must be a non-empty string` : Alias extends `${string} ${string}` | `${string}\t${string}` | `${string}\n${string}` | `${string}\r${string}` | `${string}\v${string}` | `${string}\f${string}` ? `Subcommand "${Name}" alias "${Alias}" must not contain whitespace` : Alias extends `-${string}` ? `Subcommand "${Name}" alias "${Alias}" must not start with "-" (reserved for flags)` : string extends Name ? never : Alias extends Name ? `Subcommand "${Name}" alias "${Alias}" must not equal its own canonical name` : never;
48
+ type AliasShapeErrors<Name extends string, C> = NarrowAliases<AliasesOf<C>> extends (infer Alias) ? Alias extends string ? AliasShapeError<Name, Alias> : never : never;
49
+ type AliasShapeBrand<Name extends string, C> = [AliasShapeErrors<Name, C>] extends [never] ? {} : {
50
+ readonly FIX_ALIAS_SHAPE: AliasShapeErrors<Name, C>;
51
+ };
52
+ type RootVersionBrand<C> = "version" extends keyof C ? {
53
+ readonly FIX_ROOT_VERSION: "Command config \"version\" belongs on the root Crust constructor";
54
+ } : {};
55
+ /** Brand command config containing statically known invalid metadata. */
56
+ type ValidateCommandConfig<Name extends string, C> = AliasShapeBrand<Name, C> & RootVersionBrand<C>;
57
+ type EmptyNameError = {
58
+ readonly FIX_EMPTY_NAME: "Command name must be a non-empty string";
59
+ };
60
+ type TrimWhitespace = " " | "\t" | "\n" | "\r" | "\v" | "\f" | " " | " " | " " | " " | " " | " " | " " | " " | " " | " " | " " | " " | " " | "
" | "
" | " " | " " | " " | "";
61
+ type BlankName<Name extends string> = Name extends `${TrimWhitespace}${infer Tail}` ? BlankName<Tail> : Name extends "" ? true : false;
62
+ /** Runtime checks own open names; provably invalid literal members remain errors. */
63
+ type CommandNameBrand<Name extends string> = IsClosedName<Name> extends false ? {} : true extends BlankName<Name> ? EmptyNameError : "__proto__" extends Name ? {
64
+ readonly FIX_RESERVED_NAME: "Command name \"__proto__\" is reserved";
65
+ } : {};
66
+ type DefinitionAliases<D> = CommandDefinitionData<D> extends {
67
+ readonly _aliases?: infer A extends readonly string[];
68
+ } ? A : readonly string[];
69
+ /** All statically known canonical and alias spellings carried by a command definition. */
70
+ type CommandDefinitionSpellings<D> = D extends unknown ? D extends {
71
+ name: infer N extends string;
72
+ } ? IsUnion<N> extends true ? never : DefName<D> extends (infer Name extends string) ? [Name] extends [never] ? never : Name | NarrowAliases<DefinitionAliases<D>> : never : never : never;
73
+ type SelfAliasBrand<D> = DefName<D> & NarrowAliases<DefinitionAliases<D>> extends (infer Dup extends string) ? [Dup] extends [never] ? {} : {
74
+ readonly FIX_ALIAS_SHAPE: `Command "${Dup}" must not list its own canonical name as an alias`;
75
+ } : never;
76
+ type CommandCollisionBrand<Spellings extends string, Existing extends string> = CollisionBrand<Spellings, Existing, "FIX_COMMAND_COLLISION", "Command name or alias ", " collides with a sibling command">;
15
77
  /**
16
- * Resolve a command from an argv array by walking the subcommand tree.
17
- *
18
- * Subcommand matching happens BEFORE flag parsing, so:
19
- * `crust build --entry src/cli.ts` first resolves "build" as a subcommand,
20
- * then passes `["--entry", "src/cli.ts"]` to the build command's parser.
21
- *
22
- * Resolution rules:
23
- * 1. If `argv[0]` matches a subcommand key, recurse into that subcommand
24
- * 2. If `argv[0]` matches a sibling's `meta.aliases` entry, recurse into
25
- * that sibling and record the **canonical** name in `commandPath`
26
- * 3. If no match and the current command has `run()`, return it (args passed to parser)
27
- * 4. If no match and the current command has NO `run()`, it signals the caller
28
- * should show help (the `showHelp` flag is set in the result)
29
- * 5. Unknown subcommands produce a structured COMMAND_NOT_FOUND error whose
30
- * `details.available` lists the canonical sibling names (aliases are
31
- * discoverable via `details.parentCommand.subCommands[name].meta.aliases`)
32
- *
33
- * Implementation: linear scan over siblings on miss. Command trees are small
34
- * and resolution runs once per invocation, so the cost is negligible compared
35
- * to building/freezing a parallel alias→canonical map. The scan does NOT
36
- * mutate `CommandNode`.
37
- *
38
- * @param command - The root command to resolve from
39
- * @param argv - The argv array to resolve against
40
- * @returns The resolved command, argv, and the command path
41
- * @throws {CrustError} COMMAND_NOT_FOUND when an unknown subcommand is given and the parent has no run()
42
- */
43
- declare function resolveCommand(command: CommandNode, argv: string[]): CommandRoute;
44
- import { BaseValueType, ResolvePrimitive } from "@crustjs/utils";
78
+ * Validate definitions against existing siblings and definitions earlier in
79
+ * the same `.add()` call. Widened names opt out because their spellings are
80
+ * not statically knowable; their literal aliases opt out with them
81
+ * (see {@link CommandDefinitionSpellings}).
82
+ */
83
+ type ValidateCommandDefinitions<Ds extends readonly unknown[], Existing extends string = never> = Ds extends readonly [infer Head, ...infer Tail] ? CommandDefinitionSpellings<Head> extends (infer Spellings extends string) ? readonly [Head & CommandCollisionBrand<Spellings, Existing> & CommandNameBrand<DefName<Head>> & SelfAliasBrand<Head>, ...ValidateCommandDefinitions<Tail, Existing | Spellings>] : never : Ds;
84
+ type ExtensionCommandDefs<E> = [E] extends [never] ? readonly [] : DefiningOf<E> extends {
85
+ readonly commands?: infer Cs extends readonly unknown[];
86
+ } ? Cs : readonly [];
87
+ /** An uncertain command collection opens only the child namespace. */
88
+ type ExtensionCommandSpellings<E> = AttachedCommandSpellings<ExtensionCommandDefs<E>>;
89
+ type ExtensionsCommandSpellings<Es extends readonly unknown[]> = IsStaticTuple<Es> extends true ? { [I in keyof Es]: ExtensionCommandSpellings<Es[I]>; }[number] : ExtensionCommandDefs<Es[number]>[number] extends never ? never : string;
90
+ type ExtensionCommandCollisionBrand<E, Existing extends string> = CollisionBrand<ExtensionCommandSpellings<E>, Existing, "FIX_COMMAND_COLLISION", "Extension command ", " collides with an existing command">;
45
91
  /**
46
- * Supported type literals for args and flags.
47
- *
48
- * Extends `BaseValueType` (`"string" | "number" | "boolean"`) with three
49
- * formatted built-ins:
50
- *
51
- * - `"url"` — the raw value is parsed via `new URL()` into a {@link URL}
52
- * - `"path"` — the raw value is expanded (`~`) and resolved against
53
- * `process.cwd()` into an absolute `string`
54
- * - `"json"` — the raw value is parsed via `JSON.parse()` into `unknown`
55
- */
56
- type ValueType = BaseValueType | "url" | "path" | "json";
92
+ * Validate each Extension's contributed command spellings against existing
93
+ * root commands and against Extensions earlier in the same `.extend()` call.
94
+ * Runtime preparation resolves collisions last-write-wins, so a statically
95
+ * known collision would silently retype `run()` against a command that
96
+ * dispatch replaces.
97
+ */
98
+ type ValidateExtensionCommands<Es extends readonly unknown[], Existing extends string> = Es extends readonly [infer H, ...infer T extends readonly unknown[]] ? readonly [H & ExtensionCommandCollisionBrand<H, Existing>, ...ValidateExtensionCommands<T, Existing | ExtensionCommandSpellings<H>>] : Es;
99
+ /** Local metadata checks; unrelated description/version/usage text has no grammar. */
100
+ type SectionTextBrand<S> = S extends {
101
+ title: infer T extends string;
102
+ body: infer B extends string;
103
+ } ? true extends BlankName<T> | BlankName<B> ? {
104
+ readonly FIX_SECTION_TEXT: "Section title/body must be nonblank";
105
+ } : Extract<T, `${string}\r${string}` | `${string}\n${string}`> extends never ? {} : {
106
+ readonly FIX_SECTION_TEXT: "Section title must be a single line";
107
+ } : {};
108
+ type SectionAudienceBrand<S> = S extends {
109
+ only: readonly [];
110
+ } | {
111
+ except: readonly [];
112
+ } ? {
113
+ readonly FIX_SECTION_AUDIENCE: "Section audience must be nonempty";
114
+ } : {};
115
+ type LocalSectionsBrand<C> = "sections" extends keyof C ? C extends {
116
+ sections: infer S extends readonly unknown[];
117
+ } ? {
118
+ readonly sections: { [I in keyof S]: S[I] & UnionToIntersection<SectionTextBrand<S[I]>> & UnionToIntersection<SectionAudienceBrand<S[I]>>; };
119
+ } : {} : {};
120
+ type LocalCommandConfigBrand<N extends string, C> = ValidateCommandConfig<N, C> & LocalSectionsBrand<C>;
121
+ type AttachedCommandSpellings<Ds extends readonly unknown[]> = [Ds] extends [readonly []] ? never : IsStaticTuple<Ds> extends true ? HasClosedNames<Ds> extends true ? false extends { [I in keyof Ds]: IsClosedName<DefinitionAliases<Ds[I]>[number]>; }[number] ? string : CommandDefinitionSpellings<Ds[number]> : string : string;
122
+ //#endregion
123
+ //#region src/validation/contexts.brands.d.ts
124
+ /** Canonical names claimed by more than one instance in the same `.provide()` call. */
125
+ type DuplicateContextNames<Cs extends readonly AnyContextInstance[], Seen extends string = never> = Cs extends readonly [infer Head, ...infer Tail extends readonly AnyContextInstance[]] ? (DefName<Head> & Seen) | DuplicateContextNames<Tail, Seen | DefName<Head>> : never;
126
+ type DuplicateContextBrand<C, Existing extends string> = CollisionBrand<DefName<C>, Existing, "FIX_DUPLICATE_CONTEXT", "Context ", " is already provided on this command path">;
57
127
  /**
58
- * Resolve a {@link ValueType} literal to its runtime TypeScript type.
59
- *
60
- * Delegates to `ResolvePrimitive<T>` for the three base types; maps the
61
- * three formatted types (`"url"`, `"path"`, `"json"`) to `URL`, `string`,
62
- * and `unknown` respectively.
63
- */
64
- type Resolve<T extends ValueType> = T extends BaseValueType ? ResolvePrimitive<T> : T extends "url" ? URL : T extends "path" ? string : T extends "json" ? unknown : never;
128
+ * Brand instances whose name is already provided on this builder chain or
129
+ * repeated within the same `.provide()` call. The accumulated Context value
130
+ * map doubles as the name registry (its keys are the provided names), so no
131
+ * separate accumulator is needed. Wrappers generic over the builder type
132
+ * use `any`, which opts out via the
133
+ * `string extends keyof Ctx` guard instead of deferring. Widened names opt
134
+ * out via `DefName`; a parent-provided Context is not in the definition's
135
+ * `Ctx` and therefore cannot be checked at this call site.
136
+ */
137
+ type ValidateContextNames<Ctx extends ContextMap, Cs extends readonly AnyContextInstance[], Existing extends string = string extends keyof Ctx ? never : keyof Ctx & string, Dups extends string = DuplicateContextNames<Cs>> = { [I in keyof Cs]: Cs[I] & DuplicateContextBrand<Cs[I], Existing | Dups>; };
138
+ type InstanceNames<P extends readonly unknown[]> = P extends readonly [infer H, ...infer T extends readonly unknown[]] ? DefName<H> | InstanceNames<T> : never;
139
+ /** Statically known names of an Extension's provided Contexts; widened Extensions opt out. */
140
+ type ExtensionProvidedNames<E> = DefiningOf<E> extends {
141
+ readonly provides?: infer P extends readonly unknown[];
142
+ } ? InstanceNames<P> : never;
143
+ type ExtensionContextBrand<E, Existing extends string> = CollisionBrand<ExtensionProvidedNames<E>, Existing, "FIX_DUPLICATE_CONTEXT", "Extension-provided Context ", " is already provided on this command path">;
144
+ type ValidateExtensionProvidesWorker<Es extends readonly unknown[], Existing extends string> = Es extends readonly [infer H, ...infer T extends readonly unknown[]] ? readonly [H & ExtensionContextBrand<H, Existing>, ...ValidateExtensionProvidesWorker<T, Existing | ExtensionProvidedNames<H>>] : Es;
65
145
  /**
66
- * Resolve the inferred runtime type for a flag/arg definition.
67
- *
68
- * When the def declares a `parse` escape hatch (only allowed on `"string"`
69
- * variants), the inferred type is `ReturnType<typeof parse>`. Otherwise it
70
- * delegates to {@link Resolve} on the declared `type`.
71
- */
72
- type ResolveBaseType<F> = F extends {
73
- parse: (raw: string) => infer R;
74
- } ? R : F extends {
75
- type: infer T extends ValueType;
76
- } ? Resolve<T> : never;
77
- /** Shared fields present on every positional argument definition */
78
- interface ArgDefBase {
79
- /** The argument name (used as the key in the parsed result and in help text) */
80
- name: string;
81
- /** Human-readable description for help text */
82
- description?: string;
83
- /**
84
- * When `true`, the parser throws if the argument is not provided.
85
- *
86
- * For variadic args, this means the array cannot be empty — the runtime
87
- * value is still `T[]`, just rejected when it has length 0.
88
- */
89
- required?: true;
90
- /**
91
- * When `true`, collects all remaining positional values into an array.
92
- *
93
- * The inferred TypeScript type is always `T[]` — never `T[] | undefined` —
94
- * regardless of `required` or `default`. `required` only controls whether
95
- * an empty array fails validation; it does not change the runtime shape
96
- * or the inferred type.
97
- */
98
- variadic?: true;
99
- }
100
- /** A positional argument whose value is a string */
101
- interface StringArgDef extends ArgDefBase {
102
- type: "string";
103
- /** Default string value when the argument is not provided */
104
- default?: string;
105
- /**
106
- * Static enum of valid values for this argument.
107
- *
108
- * Validated at parse time before `parse` runs. Passing a value outside
109
- * `choices` throws `CrustError("PARSE", …)` before any `parse` transform
110
- * is applied. Also consumed by shell-completion plugins
111
- * (e.g. `@crustjs/plugins/completion`) to emit value candidates.
112
- *
113
- * Only available on string-typed args; not supported on number/boolean.
114
- *
115
- * @example
116
- * { name: "target", type: "string", choices: ["browser", "bun", "node"] }
117
- */
118
- choices?: readonly string[];
119
- /**
120
- * Custom synchronous parser for the raw argv string. Runs per element
121
- * for variadic args. See {@link StringFlagDef.parse} for full semantics.
122
- *
123
- * @example
124
- * { name: "port", type: "string", parse: (s) => Number(s) }
125
- */
126
- parse?: (raw: string) => unknown;
127
- }
128
- /** A positional argument whose value is a number */
129
- interface NumberArgDef extends ArgDefBase {
130
- type: "number";
131
- /** Default number value when the argument is not provided */
132
- default?: number;
133
- /** Not supported on number args — use `type: "string"` with `parse`. */
134
- parse?: never;
146
+ * Brand Extensions whose provided Context names replace one already on the
147
+ * command path (or one provided by an earlier Extension in the same call).
148
+ * The resolver is last-write-wins, so a silent replacement would hand actions
149
+ * bound before `.extend()` a value of a different static type.
150
+ */
151
+ type ValidateExtensionProvides<Es extends readonly unknown[], Ctx extends ContextMap> = ValidateExtensionProvidesWorker<Es, string extends keyof Ctx ? never : keyof Ctx & string>;
152
+ type ProvidedDepsOf<C> = IsAny<C> extends true ? {} : IsAny<ContextDepsOf<C>> extends true ? {} : string extends keyof ContextDepsOf<C> ? {} : ContextDepsOf<C>;
153
+ type MissingDependencyBrand<C, Known extends string> = Exclude<keyof ProvidedDepsOf<C> & string, Known> extends (infer Missing extends string) ? [Missing] extends [never] ? {} : {
154
+ readonly FIX_MISSING_DEPENDENCY: `Context "${DefName<C>}" uses Context "${Missing}" which is not provided on this command path`;
155
+ } : never;
156
+ type MismatchedDependencyNames<Deps, KnownValues> = keyof Deps extends never ? never : KnownValues extends unknown ? { [K in keyof Deps & keyof KnownValues & string]: IsAny<KnownValues[K]> extends true ? never : IsAny<Deps[K]> extends true ? never : [unknown, string] extends [KnownValues[K], keyof KnownValues] ? never : KnownValues[K] extends Deps[K] ? never : K; }[keyof Deps & keyof KnownValues & string] : never;
157
+ type MismatchedDependencyBrand<C, KnownValues> = MismatchedDependencyNames<ProvidedDepsOf<C>, KnownValues> extends (infer Mismatched extends string) ? [Mismatched] extends [never] ? {} : {
158
+ readonly FIX_DEPENDENCY_TYPE: `Context "${DefName<C>}" uses Context "${Mismatched}" whose provided value does not satisfy the declared dependency type`;
159
+ } : never;
160
+ /** Brand provided instances whose transitive dependency closure is unsatisfied. */
161
+ type ValidateContextDeps<Ctx extends ContextMap, Cs extends readonly AnyContextInstance[], Known extends string = (keyof Ctx & string) | DefName<Cs[number]>, KnownValues extends ContextMap = Ctx & ContextsOutput<Cs>> = { [I in keyof Cs]: Cs[I] & MissingDependencyBrand<Cs[I], Known> & MismatchedDependencyBrand<Cs[I], KnownValues>; };
162
+ /** Dependency closure carried by command definitions and Extensions. */
163
+ type IsAny<T> = 0 extends 1 & T ? true : false;
164
+ type DeclaredDepsOf<T> = IsAny<T> extends true ? Record<string, ContextValue> : CommandDefinitionData<DefiningOf<T>> extends {
165
+ readonly _deps?: infer D extends ContextMap;
166
+ } ? IsAny<D> extends true ? Record<string, ContextValue> : D : {};
167
+ /** Missing-dependency brand shared by `ValidateDeclaredDeps` and inline `.command()`. */
168
+ type MissingDeclaredDependencyBrand<T, Known extends string> = string extends keyof DeclaredDepsOf<T> ? {} : Exclude<keyof DeclaredDepsOf<T> & string, Known> extends (infer Missing extends string) ? [Missing] extends [never] ? {} : {
169
+ readonly FIX_MISSING_DEPENDENCY: `Uses Context "${Missing}" which is not provided`;
170
+ } : never;
171
+ /** Brand sealed units whose declared dependencies are absent at a composition site. */
172
+ type ValidateDeclaredDeps<Ctx extends ContextMap, Items extends readonly unknown[]> = { [I in keyof Items]: Items[I] & MissingDeclaredDependencyBrand<Items[I], keyof Ctx & string> & DeclaredDependencyValuesBrand<DeclaredDepsOf<Items[I]>, Ctx>; };
173
+ /** Callback values stay TypeScript-owned, including at dynamic composition. */
174
+ type DeclaredDependencyValuesBrand<Deps, Values> = MismatchedDependencyNames<Deps, Values> extends (infer Names extends string) ? [Names] extends [never] ? {} : {
175
+ readonly FIX_DEPENDENCY_TYPE: `Provided Context "${Names}" does not satisfy its declared value type`;
176
+ } : never;
177
+ /** Structural provider copies retain their defining name. */
178
+ type KnownContextInstances<Cs extends readonly AnyContextInstance[]> = { [I in keyof Cs]: Cs[I] & Pick<DefiningOf<Cs[I]>, "name">; };
179
+ //#endregion
180
+ //#region src/validation/flags.brands.d.ts
181
+ /** Brand an incoming definition when one of its spellings is already claimed. */
182
+ type ExistingFlagCollisionBrand<F, Existing extends string> = CollisionBrand<DefName<F> | ExtractAllAliases<F>, Existing, "FIX_ALIAS_COLLISION", "Flag spelling ", " collides with an existing flag">;
183
+ /** Reject `__proto__`, which mutates the prototype of plain-object flag registries. */
184
+ type ReservedSpellingBrand<F> = "__proto__" extends DefName<F> | ExtractAllAliases<F> ? {
185
+ readonly FIX_RESERVED_SPELLING: "Flag spelling \"__proto__\" is reserved";
186
+ } : {};
187
+ type EmptySpellingError = {
188
+ readonly FIX_EMPTY_SPELLING: "Flag names and aliases must be non-empty strings";
189
+ };
190
+ /** Reject empty flag names, including empty members of a name union. */
191
+ type EmptyFlagSpellingBrand<Name extends string> = EmptyLiteralNameBrand<Name, EmptySpellingError>;
192
+ /** Reject empty spellings: their CLI tokens (`--`, `-`) are unparseable, so the flag can never be supplied. */
193
+ type EmptySpellingBrand<F> = "" extends DefName<F> | ExtractAllAliases<F> ? EmptySpellingError : {};
194
+ type RepeatedAliases<Aliases extends readonly string[], Seen extends string> = Aliases extends readonly [infer Head extends string, ...infer Tail extends readonly string[]] ? (Head & Seen) | RepeatedAliases<Tail, Seen | Head> : never;
195
+ type OwnAliasesBrand<F> = F extends {
196
+ aliases: infer Aliases extends readonly string[];
197
+ } ? RepeatedAliases<Aliases, ExtractShort<F>> extends (infer Duplicate extends string) ? [Duplicate] extends [never] ? {} : {
198
+ readonly FIX_ALIAS_COLLISION: "Flag repeats one of its own spellings";
199
+ } : never : {};
200
+ type InvalidShort<S extends string> = S extends `${infer _First}${infer Rest}` ? Rest extends "" ? never : S : S;
201
+ type ShortLengthBrand<F> = F extends {
202
+ short: infer Short extends string;
203
+ } ? string extends Short ? {} : [InvalidShort<Short>] extends [never] ? {} : {
204
+ readonly FIX_SHORT_LENGTH: "Short flags must be one character";
205
+ } : {};
206
+ /**
207
+ * Extract the `short` alias literal from a flag definition.
208
+ * Resolves to `never` when the field is absent or its spelling domain is open.
209
+ */
210
+ type ExtractShort<F> = F extends {
211
+ short: infer S;
212
+ } ? S extends string ? IsClosedName<S> extends true ? S : never : never : never;
213
+ /**
214
+ * Extract alias string literals from the `aliases` array of a flag definition.
215
+ * Resolves to `never` when the field is absent or its element domain is open.
216
+ */
217
+ type ExtractLongAliases<F> = F extends {
218
+ aliases: infer A;
219
+ } ? A extends readonly string[] ? IsClosedName<A[number]> extends true ? A[number] : never : never : never;
220
+ /**
221
+ * Extract all alias identifiers (short + long) from a flag definition.
222
+ *
223
+ * Generalized to work with any shape; values without `short`/`aliases`
224
+ * fields resolve to `never`.
225
+ *
226
+ * Closed-name proof excludes open domains from literal collision evidence.
227
+ * Attachment separately keeps their spelling namespace open.
228
+ */
229
+ type ExtractAllAliases<F> = ExtractShort<F> | ExtractLongAliases<F>;
230
+ /** All narrowed canonical, short, and long-alias spellings in a flags record. */
231
+ type SpellingsOf<F extends FlagsDef> = string extends keyof F ? never : (keyof F & string) | { [K in keyof F & string]: ExtractAllAliases<F[K]>; }[keyof F & string];
232
+ type NoPrefixBrand<S extends string> = [Extract<S, `no-${string}`>] extends [never] ? {} : {
233
+ readonly FIX_NO_PREFIX: "Names must not start with no-";
234
+ };
235
+ type ContextOwnedFlags<C> = C extends unknown ? DefiningOf<C> extends {
236
+ readonly _ownedFlags?: infer OF extends FlagsDef;
237
+ } ? OF : {} : never;
238
+ type ContextFlagCollisionBrand<C, Existing extends string> = CollisionBrand<LocalSpellingsOf<ContextOwnedFlags<C>>, Existing, "FIX_ALIAS_COLLISION", "Flag spelling ", " collides with an existing flag">;
239
+ /** Declared flag literals carried by an Extension's `_flagDefs` phantom; widened Extensions opt out. */
240
+ type ExtensionFlagDefsOf<E> = [E] extends [never] ? readonly [] : DefiningOf<E> extends {
241
+ readonly _flagDefs?: infer D extends readonly NamedFlagDef[];
242
+ } ? D : readonly NamedFlagDef[];
243
+ /** All statically known owned-flag spellings across a tuple of Context instances. */
244
+ type ProvidedContextSpellings<P extends readonly unknown[]> = P extends readonly [infer H, ...infer T extends readonly unknown[]] ? LocalSpellingsOf<ContextOwnedFlags<H>> | ProvidedContextSpellings<T> : [P[number]] extends [never] ? never : LocalSpellingsOf<ContextOwnedFlags<P[number]>>;
245
+ /** All statically known flag spellings an Extension contributes: declared flags plus provided Context-owned flags. */
246
+ type ExtensionSpellings<E> = AttachedSpellings<ExtensionFlagDefsOf<E>> | ([E] extends [never] ? never : DefiningOf<E> extends {
247
+ readonly provides?: infer P extends readonly unknown[];
248
+ } ? ProvidedContextSpellings<P> : never);
249
+ type ExtensionFlagCollisionBrand<E, Existing extends string> = CollisionBrand<ExtensionSpellings<E>, Existing, "FIX_ALIAS_COLLISION", "Extension flag spelling ", " collides with an existing flag">;
250
+ /**
251
+ * Validate each Extension's contributed flag spellings against accumulated
252
+ * existing spellings and against Extensions earlier in the same `.extend()`
253
+ * call. Extensions must not override application flags: a silent overwrite
254
+ * would retype an already-bound action's flag at parse time.
255
+ */
256
+ type ValidateExtensionFlags<Es extends readonly unknown[], Existing extends string> = Es extends readonly [infer H, ...infer T extends readonly unknown[]] ? readonly [H & ExtensionFlagCollisionBrand<H, Existing>, ...ValidateExtensionFlags<T, Existing | ExtensionSpellings<H>>] : Es;
257
+ type ShapeSpellings<S> = 0 extends 1 & S ? string : S extends {
258
+ readonly flags: infer F extends FlagsDef;
259
+ readonly children: infer C;
260
+ } ? LocalSpellingsOf<F> | TreeSpellings<C> : never;
261
+ /** Every flag spelling reachable in a compile-time command tree (`Record<spelling, CommandShape>`), recursively. */
262
+ type TreeSpellings<Tree> = 0 extends 1 & Tree ? string : string extends keyof Tree ? string : Tree extends object ? { [K in keyof Tree]: ShapeSpellings<Tree[K]>; }[keyof Tree] : never;
263
+ type DefinitionSpellings<D> = CommandDefinitionData<D> extends {
264
+ readonly _shape?: infer S;
265
+ } ? ShapeSpellings<S> : never;
266
+ /** Flag spellings contributed by a tuple of command definitions. */
267
+ type DefinitionTreeSpellings<Ds extends readonly unknown[]> = DefinitionSpellings<Ds[number]>;
268
+ /**
269
+ * Extension-collision brand over a built command shape. Shared by `.add()`
270
+ * (via {@link ValidateDefinitionFlags}) and inline `.command()`, whose recipe
271
+ * builder exposes a shape instead of a definition tuple.
272
+ */
273
+ type ShapeFlagCollisionBrand<S, Ext extends string> = CollisionBrand<ShapeSpellings<S>, Ext, "FIX_ALIAS_COLLISION", "Flag spelling ", " collides with a registered Extension flag">;
274
+ type DefinitionFlagCollisionBrand<D, Ext extends string> = CommandDefinitionData<D> extends {
275
+ readonly _shape?: infer S;
276
+ } ? ShapeFlagCollisionBrand<S, Ext> : {};
277
+ /**
278
+ * Validate an added definition tree's flag spellings against already-registered
279
+ * Extension flags. Recursive Extension flags inject into every node at prepare
280
+ * time, so a colliding local flag would be silently retyped for its action.
281
+ */
282
+ type ValidateDefinitionFlags<Ds extends readonly unknown[], Ext extends string> = { [I in keyof Ds]: Ds[I] & DefinitionFlagCollisionBrand<Ds[I], Ext>; };
283
+ /** Union of every statically known flag spelling contributed by a tuple of Extensions. */
284
+ type ExtensionsSpellings<Es extends readonly unknown[]> = Es extends readonly [infer H, ...infer T extends readonly unknown[]] ? ExtensionSpellings<H> | ExtensionsSpellings<T> : ExtensionSpellings<Es[number]> extends never ? never : string;
285
+ /**
286
+ * Validate Context-owned flags against accumulated existing spellings and
287
+ * against instances earlier in the same `.provide(a(), b())` call. Without
288
+ * the batch check a same-call collision silently resolves last-write-wins,
289
+ * and a required flag shadowed by a peer's alias becomes impossible to supply.
290
+ */
291
+ type ProvideChecks<Sp extends string, Cs extends readonly unknown[]> = Cs extends readonly [infer H, ...infer T extends readonly unknown[]] ? readonly [H & ContextFlagCollisionBrand<H, Sp>, ...ProvideChecks<Sp | LocalSpellingsOf<ContextOwnedFlags<H>>, T>] : Cs;
292
+ /** Whether canonical and alias spellings form a closed, fixed local namespace. */
293
+ type HasClosedFlagSpellings<F> = F extends {
294
+ name: infer N extends string;
295
+ } ? false extends IsClosedName<N> | ("short" extends keyof F ? F extends {
296
+ short: infer S extends string;
297
+ } ? IsClosedName<S> : false : true) | ("aliases" extends keyof F ? F extends {
298
+ aliases: infer A extends readonly string[];
299
+ } ? false extends IsStaticTuple<A> | IsClosedName<A[number]> ? false : true : false : true) ? false : true : false;
300
+ type LocalFlagBrand<F> = UnionToIntersection<F extends unknown ? LocalFlagBranchBrand<F> : never>;
301
+ type LocalFlagBranchBrand<F> = LocalValueBrand<F> & OwnAliasesBrand<F> & ShortLengthBrand<F> & ReservedSpellingBrand<F> & EmptySpellingBrand<F> & NoPrefixBrand<DefName<F> | ExtractAllAliases<F>> & ([DefName<F> & ExtractAllAliases<F>] extends [never] ? {} : {
302
+ readonly FIX_ALIAS_COLLISION: "Flag repeats one of its own spellings";
303
+ });
304
+ /** Validate provable local fields and destination relations without inventing names for open inputs. */
305
+ type ValidateLocalFlagDefs<Defs extends readonly NamedFlagDef[], Existing extends string> = Defs & UnionToIntersection<LocalFlagTupleChecks<Defs, Existing>>;
306
+ type LocalFlagTupleChecks<Defs extends readonly NamedFlagDef[], Existing extends string, Errors = {}> = Defs extends readonly [infer Head extends NamedFlagDef, ...infer Tail extends readonly NamedFlagDef[]] ? LocalFlagTupleChecks<Tail, Existing | DefName<Head> | ExtractAllAliases<Head>, Errors & LocalFlagBrand<Head> & ExistingFlagCollisionBrand<Head, Existing>> : Defs extends readonly [] ? Errors : Errors & LocalFlagBrand<Defs[number]>;
307
+ /** An open collection is not an empty or guaranteed-present flag record. */
308
+ type KnownNamedFlag<D> = D extends NamedFlagDef ? HasClosedNames<readonly [D]> extends true ? D : never : never;
309
+ type AttachedFlags<Defs extends readonly NamedFlagDef[]> = HasClosedNames<Defs> extends true ? NamedFlagsRecord<Defs> : IsStaticTuple<Defs> extends true ? FlagsDef & NamedFlagsRecord<readonly KnownNamedFlag<Defs[number]>[]> : FlagsDef;
310
+ type AttachedSpellings<Defs extends readonly NamedFlagDef[]> = IsStaticTuple<Defs> extends true ? false extends { [I in keyof Defs]: HasClosedFlagSpellings<Defs[I]>; }[number] ? string : SpellingsOf<NamedFlagsRecord<Defs>> : string;
311
+ type LocalFlagNameBrand<N extends string> = EmptyFlagSpellingBrand<N> & ReservedSpellingBrand<{
312
+ name: N;
313
+ }> & NoPrefixBrand<N>;
314
+ /** Broad structural builder holders must not default their spelling state to empty. */
315
+ type LocalSpellingsOf<F extends FlagsDef> = string extends keyof F ? string : true extends { [K in keyof F]: HasClosedFlagSpellings<F[K] & {
316
+ name: K;
317
+ }> extends false ? true : false; }[keyof F] ? string : SpellingsOf<F>;
318
+ //#endregion
319
+ //#region src/command/crust.d.ts
320
+ /**
321
+ * The runtime context object passed to the Command Action defined with
322
+ * `.action()`.
323
+ *
324
+ * Generic parameters:
325
+ * - `A` — positional argument definitions tuple
326
+ * - `F` — the effective (Context-owned + local merged) flag definitions
327
+ */
328
+ interface CrustCommandContext<A extends ArgsDef = ArgsDef, F extends FlagsDef = FlagsDef, Ctx extends ContextMap = {}> extends InvocationIO {
329
+ /** Resolved positional arguments, keyed by arg name */
330
+ args: InferArgs<A>;
331
+ /** Resolved flags, keyed by flag name */
332
+ flags: InferFlags<F>;
333
+ /** Lazy Context values available on this command path. */
334
+ ctx: ContextBag<Ctx>;
335
+ /** Raw arguments that appeared after the `--` separator */
336
+ rawArgs: string[];
337
+ /** Readonly, serializable snapshot of the resolved command */
338
+ command: CommandSnapshot;
339
+ /** Readonly snapshot of the application root, including Extension contributions */
340
+ rootCommand: CommandSnapshot;
135
341
  }
136
- /** A positional argument whose value is a boolean */
137
- interface BooleanArgDef extends ArgDefBase {
138
- type: "boolean";
139
- /** Default boolean value when the argument is not provided */
140
- default?: boolean;
141
- /** Not supported on boolean args — use `type: "string"` with `parse`. */
142
- parse?: never;
342
+ declare const commandProviders: unique symbol;
343
+ /** Compile-time description of one command's programmatic input and action result. */
344
+ interface CommandShape<A extends ArgsDef = ArgsDef, F extends FlagsDef = FlagsDef, Children extends object = {}, Result = unknown, Providers extends Record<string, ContextValue> = Record<string, ContextValue>> {
345
+ readonly [commandProviders]?: Providers;
346
+ readonly args: A;
347
+ readonly flags: F;
348
+ readonly children: Children;
349
+ readonly result: Result;
143
350
  }
144
- /** A positional argument whose value is a {@link URL} */
145
- interface UrlArgDef extends ArgDefBase {
146
- type: "url";
147
- /** Default URL value when the argument is not provided */
148
- default?: URL;
149
- /** Not supported on url args — use `type: "string"` with `parse`. */
150
- parse?: never;
351
+ /** Captured invocation after lifecycle cleanup. */
352
+ type RunOutcome<Result> = {
353
+ readonly stdout: string;
354
+ readonly stderr: string;
355
+ } & ({
356
+ readonly status: "completed";
357
+ readonly result: Result;
358
+ } | {
359
+ readonly status: "finished";
360
+ readonly by: ExtensionId;
361
+ } | {
362
+ readonly status: "failed";
363
+ readonly error: unknown;
364
+ });
365
+ /** Compile-time command tree accumulated by `.add()`. */
366
+ type CommandTree = Record<string, CommandShape>;
367
+ /** Every valid path through a command tree, including the root path (`[]`). */
368
+ type CommandPath<Tree extends object, Depth extends readonly unknown[] = readonly []> = Depth["length"] extends 15 ? readonly string[] : string extends keyof Tree ? readonly string[] : readonly [] | { [K in keyof Tree & string]: Tree[K] extends CommandShape ? readonly [K, ...CommandPath<Tree[K]["children"], readonly [...Depth, unknown]>] : never; }[keyof Tree & string];
369
+ type KnownCommandPath<Path extends readonly string[], Tree> = string extends keyof Tree ? Path : IsStaticTuple<Path> extends true ? string extends Path[number] ? never : Path : never;
370
+ /** Resolve the command shape at a typed path. */
371
+ type CommandShapeAt<Shape extends CommandShape, Path extends readonly string[]> = Path extends readonly [infer Head, ...infer Tail extends readonly string[]] ? Head extends keyof Shape["children"] ? Shape["children"][Head] extends (infer Child extends CommandShape) ? CommandShapeAt<Child, Tail> : never : string extends Head ? CommandShape : never : Path extends readonly [] ? Shape : CommandShape;
372
+ type RunSection<Name extends string, Values> = keyof Values extends never ? { [K in Name]?: never; } : {} extends Values ? { [K in Name]?: Values; } : { [K in Name]: Values; };
373
+ /** Structured values bound directly against the selected command's definitions; no argv is produced. */
374
+ type RunInput<Shape extends CommandShape> = RunSection<"args", ArgsDef extends Shape["args"] ? NonNullable<RunInputPayload["args"]> : InputArgs<Shape["args"]>> & RunSection<"flags", FlagsDef extends Shape["flags"] ? NonNullable<RunInputPayload["flags"]> : InputFlags<Shape["flags"]>> & {
375
+ readonly raw?: readonly string[];
376
+ };
377
+ type CompatibleRunValue<Expected, Actual> = Actual extends Expected ? Actual extends object ? Expected extends unknown ? Actual extends Expected ? Actual & { [K in Exclude<keyof Actual, keyof Expected>]: never; } & { [K in keyof Actual & keyof Expected]: CompatibleRunValue<Expected[K], Actual[K]>; } : never : never : Actual : JsonValue extends Expected ? Actual extends JsonCompatible<Actual> ? Actual : never : Expected extends unknown ? CompatibleRunBranch<Expected, Actual> : never;
378
+ type CompatibleRunBranch<Expected, Actual> = Actual extends Expected ? Actual : Expected extends readonly (infer Item)[] ? JsonValue extends Item ? Actual extends (Expected extends readonly [unknown, ...unknown[]] ? readonly [unknown, ...unknown[]] : readonly unknown[]) ? Actual extends JsonCompatible<Actual> ? Actual : never : never : never : Actual extends object ? string extends keyof Expected ? { [K in keyof Actual]: CompatibleRunValue<Exclude<Expected[K & keyof Expected], undefined>, Actual[K]>; } : { [K in keyof Expected]: K extends keyof Actual ? CompatibleRunValue<Exclude<Expected[K], undefined>, Actual[K]> : Expected[K]; } & { [K in Exclude<keyof Actual, keyof Expected>]: never; } : never;
379
+ type CompatibleRunInput<Shape extends CommandShape, Input> = CompatibleRunValue<RunInput<Shape>, Input>;
380
+ type RunInputArguments<Shape extends CommandShape> = {} extends RunInput<Shape> ? readonly [input?: RunInput<Shape>] : readonly [input: RunInput<Shape>];
381
+ type RunArguments<Shape extends CommandShape> = readonly [...RunInputArguments<Shape>, io?: Partial<InvocationIO>];
382
+ /** Static configuration for a reusable command definition. */
383
+ interface CommandConfig extends Omit<CommandMeta, "name" | "sections" | "version"> {
384
+ /** Plain-text sections rendered after built-in command documentation. */
385
+ readonly sections?: readonly RuntimeCommandSectionInput[];
151
386
  }
152
- /** A positional argument whose value is an absolute filesystem path */
153
- interface PathArgDef extends ArgDefBase {
154
- type: "path";
155
- /** Default path string when the argument is not provided */
156
- default?: string;
157
- /** Not supported on path args — use `type: "string"` with `parse`. */
158
- parse?: never;
387
+ /** Static metadata accepted by the root command constructor. */
388
+ type RootCommandMeta = Pick<CommandMeta, "description" | "version" | "usage"> & {
389
+ /** Plain-text sections rendered after built-in command documentation. */
390
+ readonly sections?: readonly RuntimeCommandSectionInput[];
391
+ };
392
+ type AnyCommandDefinitionBuilder = Crust<any, any, any, any, any, any, any, any, any, any, any, "recipe">;
393
+ type CommandRecipe<Builder extends AnyCommandDefinitionBuilder = AnyCommandDefinitionBuilder> = (command: CommandDefinitionBuilder<{}, [], {}, never, never>) => Builder;
394
+ declare const commandDefinitionInternal: unique symbol;
395
+ interface CommandDefinitionInternal {
396
+ readonly name: string;
397
+ readonly recipe: (command: AnyCommandDefinitionBuilder) => AnyCommandDefinitionBuilder;
398
+ readonly meta: Omit<CommandMeta, "name">;
159
399
  }
160
- /** A positional argument whose value is JSON parsed to `unknown` */
161
- interface JsonArgDef extends ArgDefBase {
162
- type: "json";
163
- /** Default parsed JSON value when the argument is not provided */
164
- default?: unknown;
165
- /** Not supported on json args — use `type: "string"` with `parse`. */
166
- parse?: never;
400
+ type CommandInputShape<S extends CommandShape> = {
401
+ readonly args: S["args"];
402
+ readonly flags: S["flags"];
403
+ readonly providers: S[typeof commandProviders];
404
+ readonly children: { [K in keyof S["children"]]: S["children"][K] extends CommandShape ? CommandInputShape<S["children"][K]> : never; };
405
+ };
406
+ interface CommandDefinition<Name extends string = string, Aliases extends readonly string[] = readonly string[], Shape extends CommandShape = CommandShape, Deps extends ContextMap = {}> {
407
+ /** The subcommand name this definition is added under */
408
+ readonly name: Name;
409
+ /** The same definition under a different name; configured aliases travel with it. */
410
+ as<const N extends string>(name: N & CommandNameBrand<N> & ValidateCommandConfig<N, {
411
+ aliases: Aliases;
412
+ }>): CommandDefinition<N, Aliases, Shape, Deps>;
413
+ /** @internal */
414
+ readonly [commandDefinitionInternal]: CommandDefinitionInternal & {
415
+ readonly _aliases?: Aliases;
416
+ readonly _shape?: Shape;
417
+ readonly _deps?: Deps;
418
+ readonly proof?: [Shape] extends [never] ? unknown : string extends keyof Shape["flags"] | keyof Deps ? unknown : (state: [CommandInputShape<Shape>, Deps]) => void;
419
+ };
167
420
  }
421
+ /** @internal */
422
+ type CommandDefinitionData<D> = D extends {
423
+ readonly [commandDefinitionInternal]: infer Data;
424
+ } ? Data : D;
425
+ type AppendedArgs<A extends ArgsDef, NewA extends ArgsDef> = readonly [...A, ...AttachedArgs<NewA>];
426
+ /** Configure-only capability of {@link Crust} passed to command recipes. */
427
+ type CommandDefinitionBuilder<Flags extends FlagsDef = {}, A extends ArgsDef = ArgsDef, Ctx extends ContextMap = {}, Sibs extends string = never, Sp extends string = LocalSpellingsOf<Flags>, Tree extends object = {}, CtxFlags extends FlagsDef = {}, Result = void, Deps extends ContextMap = {}, Providers extends Record<string, ContextValue> = {}> = Crust<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSpellings<never, never, {}, never, Deps, Providers>, Result, {}, string, "recipe">;
428
+ type ShapeOfBuilder<B> = [B] extends [never] ? CommandShape<[], {}, {}, never, {}> : B extends {
429
+ readonly _recipeTypes: {
430
+ shape: infer S extends CommandShape;
431
+ };
432
+ } ? S : never;
433
+ type DepsOfBuilder<B> = UnionToIntersection<B extends {
434
+ readonly _recipeTypes: {
435
+ deps: infer Deps extends ContextMap;
436
+ };
437
+ } ? Deps : {}> extends (infer Merged extends ContextMap) ? Merged : {};
438
+ type DefinitionShapeForSpelling<D, Spelling extends string> = CommandDefinitionData<D> extends {
439
+ readonly _shape?: infer Shape extends CommandShape;
440
+ } ? Spelling extends CommandDefinitionSpellings<D> ? Shape : never : never;
441
+ type ShapeWithInheritedFlags<S, CF extends FlagsDef> = keyof CF extends never ? S : S extends CommandShape<infer SA, infer SF, infer SC, infer SR, infer P> ? CommandShape<SA, MergeFlags<CF, SF>, { [K in keyof SC]: ShapeWithInheritedFlags<SC[K], CF>; }, SR, P> : never;
442
+ type DefinitionsTree<Ds extends readonly CommandDefinition<any, any, any, any>[], CtxFlags extends FlagsDef = {}> = string extends AttachedCommandSpellings<Ds> ? Record<string, CommandShape> & KnownDefinitionTree<Ds, CtxFlags> : { [K in CommandDefinitionSpellings<Ds[number]>]: ShapeWithInheritedFlags<DefinitionShapeForSpelling<Ds[number], K>, CtxFlags>; };
443
+ type KnownDefinitionTree<Ds extends readonly unknown[], CF extends FlagsDef> = IsUnion<Ds> extends true ? {} : Ds extends readonly [infer H, ...infer T] ? (IsUnion<H> extends true ? {} : { [K in CommandDefinitionSpellings<H>]: ShapeWithInheritedFlags<DefinitionShapeForSpelling<H, K>, CF>; }) & KnownDefinitionTree<T, CF> : {};
444
+ type ExtensionCheckAt<Checks, I> = I extends keyof Checks ? Checks[I] : never;
445
+ type ExtensionCommands<Es extends readonly AnyExtension[]> = Es extends readonly [infer H, ...infer T extends readonly AnyExtension[]] ? readonly [...ExtensionCommandDefs<H>, ...ExtensionCommands<T>] : ExtensionCommandDefs<Es[number]>[number] extends never ? readonly [] : readonly CommandDefinition<any, any, any, any>[];
446
+ type ExtensionProviders<E> = [E] extends [never] ? [] : DefiningOf<E> extends {
447
+ provides?: infer P extends readonly AnyContextInstance[];
448
+ } ? P : [];
449
+ type ExtensionOwnDefs<E> = [E] extends [never] ? [] : DefiningOf<E> extends {
450
+ _flagDefs?: infer F extends readonly NamedFlagDef[];
451
+ } ? F : [];
452
+ type ExtensionFlags<Es extends readonly AnyExtension[]> = Es extends readonly [infer H, ...infer T extends readonly AnyExtension[]] ? AttachedFlags<ExtensionOwnDefs<H>> & ContextsOwnedFlags<ExtensionProviders<H>> & ExtensionFlags<T> : ExtensionOwnDefs<Es[number]>[number] extends never ? keyof ContextsOwnedFlags<ExtensionProviders<Es[number]>> extends never ? {} : FlagsDef : FlagsDef;
453
+ type RecursiveFlagsOf<E> = ExtensionOwnDefs<E>[number] extends never ? {} : true extends { [K in keyof ExtensionOwnDefs<E>]: ExtensionOwnDefs<E>[K] extends (infer D extends NamedFlagDef) ? D extends {
454
+ readonly recursive: false;
455
+ } ? false : IsClosedName<D["name"]> extends false ? true : IsUnion<D["name"]> extends true ? true : D extends {
456
+ recursive: infer R;
457
+ } ? boolean extends R ? true : false : false : false; }[number] ? FlagsDef : IsStaticTuple<ExtensionOwnDefs<E>> extends true ? { [D in ExtensionOwnDefs<E>[number] as D extends {
458
+ readonly recursive: infer R;
459
+ } ? [R] extends [false] ? never : D["name"] : D["name"]]: Omit<D, "name">; } : FlagsDef;
460
+ type RecursiveExtensionFlags<Es extends readonly AnyExtension[]> = Es extends readonly [infer H, ...infer T extends readonly AnyExtension[]] ? RecursiveFlagsOf<H> & ContextsOwnedFlags<ExtensionProviders<H>> & RecursiveExtensionFlags<T> : ExtensionFlags<Es>;
461
+ type TreeWithInheritedFlags<Tree, F extends FlagsDef> = { [K in keyof Tree]: ShapeWithInheritedFlags<Tree[K], F>; };
462
+ type ReplacementTree<Tree, D extends CommandDefinition<any, any, any, any>, CF extends FlagsDef> = CommandDefinitionData<D> extends {
463
+ readonly _aliases?: infer Aliases extends readonly string[];
464
+ } ? IsClosedName<Aliases[number]> extends false ? Record<string, CommandShape> & { [K in D["name"]]: ShapeWithInheritedFlags<DefinitionShapeForSpelling<D, K>, CF>; } : Omit<Tree, CommandDefinitionSpellings<D>> & { [K in CommandDefinitionSpellings<D>]: K extends D["name"] ? ShapeWithInheritedFlags<DefinitionShapeForSpelling<D, K>, CF> : K extends keyof Tree ? CommandShape : ShapeWithInheritedFlags<DefinitionShapeForSpelling<D, K>, CF>; } : Record<string, CommandShape>;
465
+ type ReplacedTree<Tree, Commands extends readonly unknown[], CF extends FlagsDef> = Commands extends readonly [infer H extends CommandDefinition<any, any, any, any>, ...infer T extends readonly unknown[]] ? ReplacedTree<IsClosedName<H["name"]> extends false ? Record<string, CommandShape> : true extends IsUnion<H> | IsUnion<H["name"]> ? Record<string, CommandShape> : ReplacementTree<Tree, H, CF>, T, CF> : number extends Commands["length"] ? Record<string, CommandShape> : Tree;
466
+ type ReplacedExtensionTree<Tree, Es extends readonly AnyExtension[], CF extends FlagsDef> = Es extends readonly [infer H extends AnyExtension, ...infer T extends readonly AnyExtension[]] ? ReplacedExtensionTree<ReplacedTree<Tree, ExtensionCommandDefs<H>, CF>, T, CF> : ExtensionCommandDefs<Es[number]>[number] extends never ? Tree : Record<string, CommandShape>;
467
+ type ExtendedTree<Tree, Es extends readonly AnyExtension[], RecursiveFlags extends FlagsDef, InheritedFlags extends FlagsDef> = keyof RecursiveFlags extends never ? ReplacedExtensionTree<Tree, Es, InheritedFlags> : TreeWithInheritedFlags<ReplacedExtensionTree<Tree, Es, InheritedFlags>, RecursiveFlags>;
168
468
  /**
169
- * Defines a single positional argument for a CLI command.
170
- *
171
- * Discriminated by `type` for type-safe `default` values. Boolean toggle
172
- * fields (`required`, `variadic`) only accept `true`.
173
- *
174
- * @example
175
- * ```ts
176
- * const args = [
177
- * { name: "port", type: "number", description: "Port number", default: 3000 },
178
- * { name: "name", type: "string", required: true },
179
- * { name: "files", type: "string", variadic: true },
180
- * ] as const satisfies ArgsDef;
181
- * ```
182
- */
183
- interface RawArgDef extends ArgDefBase {
184
- /** Optional parser hint. Omit for raw schema-backed validation. */
185
- type?: never;
186
- /** Raw default value when the argument is not provided */
187
- default?: unknown;
188
- choices?: readonly string[];
189
- /** Not supported on raw args — schema validators own the transform. */
190
- parse?: never;
191
- }
192
- type ArgDef = StringArgDef | NumberArgDef | BooleanArgDef | UrlArgDef | PathArgDef | JsonArgDef | RawArgDef;
193
- /** Ordered tuple of positional argument definitions */
194
- type ArgsDef = readonly ArgDef[];
195
- /** Shared fields present on every flag definition */
196
- interface FlagDefBase {
197
- /** Human-readable description for help text */
198
- description?: string;
199
- /** Single-character short alias (e.g. `"v"` → `-v`) */
200
- short?: string;
201
- /** Additional long aliases (e.g. `["out"]` → `--out`) */
202
- aliases?: string[];
203
- /** When `true`, the parser throws if the flag is not provided */
204
- required?: true;
205
- /** When `true`, the flag is inherited by subcommands */
206
- inherit?: true;
207
- }
208
- /** Base for single-value flags — `multiple` must be omitted */
209
- interface SingleFlagBase extends FlagDefBase {
210
- /** Must be omitted for single-value flags — set to `true` for multi-value */
211
- multiple?: never;
212
- }
213
- /** A single-value string flag */
214
- interface StringFlagDef extends SingleFlagBase {
215
- type: "string";
216
- /** Default string value */
217
- default?: string;
218
- /**
219
- * Static enum of valid values for this flag.
220
- *
221
- * Validated at parse time before `parse` runs. Passing a value outside
222
- * `choices` throws `CrustError("PARSE", …)` before any `parse` transform
223
- * is applied. Also consumed by shell-completion plugins
224
- * (e.g. `@crustjs/plugins/completion`) to emit value candidates.
225
- *
226
- * Only available on string-typed flags; not supported on number/boolean.
227
- *
228
- * @example
229
- * { type: "string", choices: ["browser", "bun", "node"] }
230
- */
231
- choices?: readonly string[];
232
- /**
233
- * Custom synchronous parser for the raw argv string.
234
- *
235
- * Receives the raw token as it appeared on the command line (after
236
- * `choices` validation, when present) and returns the resolved value
237
- * that flows to the `run` handler. The return type is inferred and
238
- * becomes the flag's runtime type.
239
- *
240
- * Constraints:
241
- * - Synchronous only. `async` parsers are rejected at command setup
242
- * with `CrustError("CONFIG", …)`.
243
- * - Only allowed on `type: "string"` (single + multi) and string args.
244
- * `parse?: never` on every non-string variant prevents misuse at
245
- * compile time.
246
- * - When `default` is set and argv is absent, `parse(String(default))`
247
- * runs so the runtime value matches the inferred type.
248
- *
249
- * @example
250
- * { type: "string", parse: (s) => Number(s) }
251
- */
252
- parse?: (raw: string) => unknown;
469
+ * Define a reusable, inert command under a required name.
470
+ *
471
+ * The recipe runs once per `.add()`, receiving a fresh builder.
472
+ *
473
+ * Static metadata belongs in `config`. Use `.as(name)`
474
+ * to add one definition under a different name; configured aliases travel with it.
475
+ */
476
+ export declare function defineCommand<const Name extends string, Builder extends AnyCommandDefinitionBuilder>(name: Name & CommandNameBrand<Name>, recipe: CommandRecipe<Builder>): CommandDefinition<Name, readonly [], ShapeOfBuilder<Builder>, DepsOfBuilder<Builder>>;
477
+ export declare function defineCommand<const Name extends string, const C extends CommandConfig, Builder extends AnyCommandDefinitionBuilder>(name: Name & CommandNameBrand<Name>, config: C & LocalCommandConfigBrand<Name, C>, recipe: CommandRecipe<Builder>): CommandDefinition<Name, AliasesOf<C>, ShapeOfBuilder<Builder>, DepsOfBuilder<Builder>>;
478
+ /**
479
+ * Chainable builder for defining CLI commands with full type inference.
480
+ *
481
+ * Generic parameters:
482
+ * - `Flags` — flags defined locally or installed by provided Contexts
483
+ * - `A` — positional argument definitions
484
+ * - `Ctx` — provided Context values
485
+ * - `Sibs` — sibling command names and aliases already registered
486
+ * - `Sp` — accumulated flag spellings used for collision checks
487
+ * - `Tree` — command shapes accumulated by `.add()` for typed `run()`
488
+ * - `CtxFlags` — Context-owned flags accumulated by `.provide()` and recursive
489
+ * Extension flags accumulated by `.extend()`, inherited by the shapes of
490
+ * definitions added afterwards
491
+ * - `Result` — awaited return type of this command's action
492
+ * - `Meta` — authored root metadata available to Extension requirements
493
+ * - `Caps` — root application or configure-only recipe capabilities
494
+ *
495
+ * @example
496
+ * ```ts
497
+ * const app = new Crust("my-cli")
498
+ * .flags({ name: "verbose", type: "boolean", short: "v" })
499
+ * .args({ name: "file", type: "string", required: true })
500
+ * .action(({ args, flags }) => {
501
+ * console.log(args.file, flags.verbose);
502
+ * });
503
+ * ```
504
+ */
505
+ type CollisionSpellings<Extensions extends string = never, Tree extends string = never, Demands extends ContextMap = {}, Pending extends string = never, RecipeDeps extends ContextMap = {}, Providers extends Record<string, ContextValue> = {}> = {
506
+ readonly pending: Pending;
507
+ readonly demands: Demands;
508
+ readonly extension: Extensions;
509
+ readonly tree: Tree;
510
+ readonly recipeDeps: RecipeDeps;
511
+ readonly providers: Providers;
512
+ };
513
+ type AnyCollisionSpellings = {
514
+ readonly pending: string;
515
+ readonly demands: ContextMap;
516
+ readonly extension: string;
517
+ readonly tree: string;
518
+ readonly recipeDeps?: ContextMap;
519
+ readonly providers?: Record<string, ContextValue>;
520
+ };
521
+ type RecipeDepsOf<S extends AnyCollisionSpellings> = S extends {
522
+ readonly recipeDeps: infer Deps extends ContextMap;
523
+ } ? Deps : {};
524
+ type ProvidersOf<S extends AnyCollisionSpellings> = S extends {
525
+ readonly providers: infer Providers extends Record<string, ContextValue>;
526
+ } ? Providers : {};
527
+ type AfterFlags<Flags extends FlagsDef, A extends ArgsDef, Ctx extends ContextMap, Sibs extends string, Sp extends string, Tree extends object, CtxFlags extends FlagsDef, CollisionSp extends AnyCollisionSpellings, Result, Defs extends readonly NamedFlagDef[], Meta extends RootCommandMeta | undefined, Caps extends "app" | "recipe"> = Crust<MergeFlags<Flags, AttachedFlags<Defs>>, A, Ctx, Sibs, Sp | AttachedSpellings<Defs>, Tree, CtxFlags, CollisionSp, Result, Meta, string, Caps>;
528
+ type AfterArgs<Flags extends FlagsDef, A extends ArgsDef, Ctx extends ContextMap, Sibs extends string, Sp extends string, Tree extends object, CtxFlags extends FlagsDef, CollisionSp extends AnyCollisionSpellings, Result, NewA extends ArgsDef, Meta extends RootCommandMeta | undefined, Caps extends "app" | "recipe"> = Crust<Flags, AppendedArgs<A, NewA>, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, Meta, string, Caps>;
529
+ type AfterUse<Flags extends FlagsDef, A extends ArgsDef, Ctx extends ContextMap, Sibs extends string, Sp extends string, Tree extends object, CtxFlags extends FlagsDef, CollisionSp extends AnyCollisionSpellings, Result, Fs extends readonly AnyContextFactory[], Meta extends RootCommandMeta | undefined> = Crust<Flags, A, MergeContext<Ctx, ContextDependencies<Fs>>, Sibs, Sp, Tree, CtxFlags, CollisionSpellings<CollisionSp["extension"], CollisionSp["tree"], CollisionSp["demands"], CollisionSp["pending"], MergeContext<RecipeDepsOf<CollisionSp>, ContextDependencies<Fs>>, ProvidersOf<CollisionSp>>, Result, Meta, string, "recipe">;
530
+ type AfterProvide<Flags extends FlagsDef, A extends ArgsDef, Ctx extends ContextMap, Sibs extends string, Sp extends string, Tree extends object, CtxFlags extends FlagsDef, CollisionSp extends AnyCollisionSpellings, Result, Cs extends readonly AnyContextInstance[], Meta extends RootCommandMeta | undefined, Caps extends "app" | "recipe"> = Crust<MergeFlags<Flags, ContextsOwnedFlags<Cs>>, A, MergeProviders<Ctx, ContextsOutput<Cs>>, Sibs, Sp | LocalSpellingsOf<ContextsOwnedFlags<Cs>>, Tree, MergeFlags<CtxFlags, ContextsOwnedFlags<Cs>>, CollisionSpellings<CollisionSp["extension"], CollisionSp["tree"], CollisionSp["demands"], CollisionSp["pending"], RecipeDepsOf<CollisionSp>, MergeProviders<ProvidersOf<CollisionSp>, ContextsOutput<Cs>>>, Result, Meta, string, Caps>;
531
+ type AfterAction<Flags extends FlagsDef, A extends ArgsDef, Ctx extends ContextMap, Sibs extends string, Sp extends string, Tree extends object, CtxFlags extends FlagsDef, CollisionSp extends AnyCollisionSpellings, R, Meta extends RootCommandMeta | undefined, Caps extends "app" | "recipe"> = Crust<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Awaited<R>, Meta, string, Caps>;
532
+ type AfterExtend<Flags extends FlagsDef, A extends ArgsDef, Ctx extends ContextMap, Sibs extends string, Sp extends string, Tree extends object, CtxFlags extends FlagsDef, CollisionSp extends AnyCollisionSpellings, Result, Es extends readonly AnyExtension[], Meta extends RootCommandMeta | undefined, Caps extends "app" | "recipe"> = Crust<MergeFlags<Flags, ExtensionFlags<Es>>, A, MergeProviders<Ctx, ExtensionsProvidesOutput<Es>>, Sibs | ExtensionsCommandSpellings<Es>, Sp | ExtensionsSpellings<Es>, ExtendedTree<Tree, Es, RecursiveExtensionFlags<Es>, CtxFlags>, MergeFlags<CtxFlags, RecursiveExtensionFlags<Es>>, CollisionSpellings<CollisionSp["extension"] | ExtensionsSpellings<Es>, CollisionSp["tree"] | DefinitionTreeSpellings<ExtensionCommands<Es>>, CollisionSp["demands"] & ExtensionDemandValues<Es>, CollisionSp["pending"] | DefinitionTreeSpellings<ExtensionCommands<Es>>, RecipeDepsOf<CollisionSp>, MergeProviders<ProvidersOf<CollisionSp>, ExtensionsProvidesOutput<Es>>>, Result, Meta, string, Caps>;
533
+ type AfterAdd<Flags extends FlagsDef, A extends ArgsDef, Ctx extends ContextMap, Sibs extends string, Sp extends string, Tree extends object, CtxFlags extends FlagsDef, CollisionSp extends AnyCollisionSpellings, Result, Ds extends readonly CommandDefinition<any, any, any, any>[], Meta extends RootCommandMeta | undefined, Caps extends "app" | "recipe"> = Crust<Flags, A, Ctx, Sibs | AttachedCommandSpellings<Ds>, Sp, Tree & DefinitionsTree<Ds, CtxFlags>, CtxFlags, CollisionSpellings<CollisionSp["extension"], CollisionSp["tree"] | DefinitionTreeSpellings<Ds>, CollisionSp["demands"], CollisionSp["pending"], RecipeDepsOf<CollisionSp>, ProvidersOf<CollisionSp>>, Result, Meta, string, Caps>;
534
+ type ExtensionDemandValues<Es extends readonly AnyExtension[]> = UnionToIntersection<DefiningOf<Es[number]> extends {
535
+ readonly _hookDeps?: infer H extends ContextMap;
536
+ } ? H : Record<string, ContextValue>> extends (infer D extends ContextMap) ? D : {};
537
+ type DescendantShapeValuesBrand<S, Deps> = S extends CommandShape ? DeclaredDependencyValuesBrand<Deps, NonNullable<S[typeof commandProviders]>> & DescendantValuesBrand<S["children"], Deps> : {};
538
+ type DescendantValuesBrand<Tree, Deps> = keyof Deps extends never ? {} : UnionToIntersection<Tree extends unknown ? keyof Tree extends never ? {} : { [K in keyof Tree]: DescendantShapeValuesBrand<Tree[K], Deps>; }[keyof Tree] : never>;
539
+ type DefinitionDescendantValuesBrand<Ds extends readonly CommandDefinition<any, any, any, any>[], Deps> = DescendantValuesBrand<DefinitionsTree<Ds>, Deps>;
540
+ type ValidateInlineCommandDeps<Ctx extends ContextMap, B> = MissingDeclaredDependencyBrand<{
541
+ readonly _deps?: DepsOfBuilder<B>;
542
+ }, keyof Ctx & string> & DeclaredDependencyValuesBrand<DepsOfBuilder<B>, Ctx>;
543
+ /** Completed applications expose inspection and invocation, not authoring after erasure. */
544
+ type AnyCrust = Pick<Crust<FlagsDef, ArgsDef, Record<string, ContextValue>, string, string, CommandTree, FlagsDef, AnyCollisionSpellings, unknown, RootCommandMeta>, "_types" | "run" | "execute" | "snapshot">;
545
+ type DefinedRootMetaKeys<Meta extends RootCommandMeta | undefined> = { [K in RootMetaKey]: [Meta] extends [Required<Pick<RootCommandMeta, K>>] ? K : never; }[RootMetaKey];
546
+ export declare class Crust<Flags extends FlagsDef = {}, A extends ArgsDef = [], Ctx extends ContextMap = {}, Sibs extends string = never, Sp extends string = LocalSpellingsOf<Flags>, Tree extends object = {}, CtxFlags extends FlagsDef = {}, CollisionSp extends AnyCollisionSpellings = CollisionSpellings, Result = void, const out Meta extends RootCommandMeta | undefined = {}, const Name extends string = string, Caps extends "app" | "recipe" = "app"> {
547
+ private readonly _contextProof;
548
+ /** @internal — recipe state, without structural inference through builder methods. */
549
+ readonly _recipeTypes: {
550
+ readonly shape: CommandShape<A, Flags, Tree, Result, ProvidersOf<CollisionSp>>;
551
+ readonly deps: RecipeDepsOf<CollisionSp>;
552
+ readonly proof?: (state: [CommandInputShape<CommandShape<A, Flags, Tree, Result, ProvidersOf<CollisionSp>>>, RecipeDepsOf<CollisionSp>]) => void;
553
+ };
554
+ /** Supported type-level seam exposing the application's inferred command types. */
555
+ readonly _types: {
556
+ flags: Flags;
557
+ args: A;
558
+ ctx: Ctx;
559
+ tree: Tree;
560
+ shape: CommandShape<A, Flags, Tree, Result>;
561
+ readonly rootMeta: Meta;
562
+ readonly caps: Caps;
563
+ };
564
+ /** @internal */
565
+ _node: CommandNode;
566
+ /** @internal — Recipe-builder lineage anchor, unique per materialization and preserved by clones */
567
+ _ancestorOwnedFlags: FlagsDef;
568
+ /**
569
+ * Create a new root command builder.
570
+ *
571
+ * @param name - The command name.
572
+ * @param metadata - Optional root description, version, usage, and documentation sections.
573
+ */
574
+ constructor(nameInput: (Name & CommandNameBrand<Name>) & ({} extends Meta ? {} : {
575
+ readonly FIX_ROOT_META: "This root requires metadata";
576
+ }));
577
+ constructor(nameInput: Name & CommandNameBrand<Name>, meta: Meta & (undefined | (RootCommandMeta & LocalSectionsBrand<NoInfer<NonNullable<Meta>>> & { [K in Exclude<keyof Meta, RootMetaKey>]: never; })));
578
+ /** @internal — Clone this builder with a new node, preserving generics. */
579
+ _clone<Out = this>(nodeOverrides: Partial<CommandNode>): Out;
580
+ /**
581
+ * Define local flags for this command from named flag definitions
582
+ * (created with `defineFlag(name, def)` or written inline as
583
+ * `{ name: "dry-run", type: "boolean" }`).
584
+ *
585
+ * Repeated `.flags()` calls accumulate local flags. Returns a new builder
586
+ * with the combined local flag types. The original builder is not mutated.
587
+ *
588
+ * @param defs - Named flag definitions
589
+ * @returns A new `Crust` instance with the given flags
590
+ */
591
+ flags<const Defs extends readonly NamedFlagDef[]>(...defs: ValidateLocalFlagDefs<Defs, Sp>): AfterFlags<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, Defs, Meta, Caps>;
592
+ /**
593
+ * Define positional arguments for this command; argument order is the
594
+ * order they are passed (created with `defineArg(name, def)` or written
595
+ * inline).
596
+ *
597
+ * Repeated `.args()` calls append in call order. Returns a new builder with
598
+ * the combined args types. The original builder is not mutated.
599
+ *
600
+ * @param defs - Positional argument definitions, in positional order
601
+ * @returns A new `Crust` instance with the combined args
602
+ */
603
+ args<const NewA extends ArgsDef>(...defs: NewA & AppendArgsChecks<A, NewA>): AfterArgs<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, NewA, Meta, Caps>;
604
+ /**
605
+ * Declare Contexts this command consumes without supplying their values.
606
+ * Factory references are retained while setup stays lazy.
607
+ */
608
+ use<const Fs extends readonly [AnyContextFactory, ...AnyContextFactory[]]>(this: {
609
+ readonly _types: {
610
+ readonly caps: "recipe";
611
+ };
612
+ }, ...factories: Fs & DeclaredDependencyValuesBrand<ContextDependencies<Fs>, ProvidersOf<CollisionSp>>): AfterUse<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, Fs, Meta>;
613
+ /**
614
+ * Attach Contexts — named command dependencies — to this command.
615
+ *
616
+ * Contexts are inherited by descendant commands and constructed lazily when
617
+ * their `ctx` property is accessed. Dependency order within one call does not
618
+ * affect construction. Disposable values are released in
619
+ * reverse construction order after post-run hooks. TypeScript rejects known Context-owned flag collisions, including pending
620
+ * Extension commands. Consuming operations throw `DEFINITION` for actual collisions.
621
+ *
622
+ */
623
+ provide<const Cs extends readonly AnyContextInstance[]>(...instances: KnownContextInstances<Cs> & ProvideChecks<Sp | CollisionSp["pending"], Cs> & ValidateContextNames<Caps extends "recipe" ? ProvidersOf<CollisionSp> : Ctx, Cs> & ValidateContextDeps<Ctx, Cs> & DeclaredDependencyValuesBrand<CollisionSp["demands"] & RecipeDepsOf<CollisionSp>, ContextsOutput<NoInfer<Cs>>>): AfterProvide<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, Cs, Meta, Caps>;
624
+ /**
625
+ * Define the Command Action — the function that implements this
626
+ * command's behavior after its inputs are ready.
627
+ *
628
+ * The action receives a {@link CrustCommandContext} with `args` typed from
629
+ * `.args()` and `flags` typed from the accumulated `Flags`.
630
+ *
631
+ * Calling `.action()` again replaces the command behavior on the new builder.
632
+ * The original builder is not mutated.
633
+ *
634
+ * @param action - The Command Action function
635
+ * @returns A new `Crust` instance with the action registered
636
+ */
637
+ action<R>(action: (ctx: NoInfer<CrustCommandContext<A, Flags, Ctx>>) => R): AfterAction<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, R, Meta, Caps>;
638
+ /**
639
+ * Register one or more CLI Extensions on the application root.
640
+ *
641
+ * Extensions are application-wide: they own the flags and commands they
642
+ * contribute. Repeated calls accumulate Extensions in registration order,
643
+ * except that registering an `ExtensionId` again keeps only the last
644
+ * registration — its contributions and providers replace the earlier ones
645
+ * and its hooks run once, at the later position. ID deduplication is
646
+ * runtime-only, so removed paths can remain statically visible and reject
647
+ * with `COMMAND_NOT_FOUND`. Canonical replacements use the new
648
+ * shape; open replacements and ambiguous aliases have unknown results.
649
+ * Required root metadata keys are checked against the constructor's inferred
650
+ * metadata by TypeScript, not at runtime.
651
+ * Recipe builders cannot call this root-only method.
652
+ */
653
+ extend<const Es extends readonly Extension<any, any, any, any, DefinedRootMetaKeys<Meta>>[]>(this: {
654
+ readonly _types: {
655
+ readonly caps: "app";
656
+ };
657
+ }, ...extensions: Es & { [I in keyof Es]: (ExtensionCommandDefs<Es[I]> extends ValidateDefinitionFlags<ExtensionCommandDefs<Es[I]>, LocalSpellingsOf<CtxFlags>> ? {} : {
658
+ readonly FIX_ALIAS_COLLISION: "Extension command flags collide with inherited Context flags";
659
+ }) & ValidateDeclaredDeps<MergeProviders<Ctx, ExtensionsProvidesOutput<Es>>, Es>[I] & DeclaredDependencyValuesBrand<CollisionSp["demands"], MergeProviders<Ctx, ExtensionsProvidesOutput<Es>>> & DescendantValuesBrand<Tree, ExtensionDemandValues<Es>> & DefinitionDescendantValuesBrand<ExtensionCommands<Es>, CollisionSp["demands"] & ExtensionDemandValues<Es>> & ExtensionCheckAt<ValidateExtensionFlags<Es, Sp | CollisionSp["tree"] | DefinitionTreeSpellings<ExtensionCommands<Es>>>, I> & ExtensionCheckAt<ValidateExtensionCommands<Es, Sibs>, I> & ExtensionCheckAt<ValidateExtensionProvides<Es, Ctx>, I>; }): AfterExtend<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, Es, Meta, Caps>;
660
+ /**
661
+ * Materialize and register inert reusable command definitions, each
662
+ * under its own carried name (use `.as(name)` to rename).
663
+ *
664
+ */
665
+ add<const Ds extends readonly CommandDefinition<any, any, any, any>[]>(...definitions: Ds & ValidateCommandDefinitions<Ds, Sibs> & ValidateDeclaredDeps<Ctx, Ds> & DefinitionDescendantValuesBrand<Ds, CollisionSp["demands"]> & ValidateDefinitionFlags<Ds, CollisionSp["extension"] | LocalSpellingsOf<CtxFlags>>): AfterAdd<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, Ds, Meta, Caps>;
666
+ /**
667
+ * Define an app-local leaf subcommand inline (root-only sugar for
668
+ * `.add(defineCommand(name, recipe))`).
669
+ *
670
+ * The recipe builder is seeded with the Contexts and Context-owned flags
671
+ * accumulated on this builder so far — the call site. Contexts provided
672
+ * after `.command()` are not visible to it, matching the positional runtime
673
+ * semantics of `.provide()`. Extract to `defineCommand` when a command needs
674
+ * its own file, reuse, or a package. Recipe builders cannot call this
675
+ * root-only method.
676
+ */
677
+ command<const N extends string, B extends AnyCommandDefinitionBuilder>(this: {
678
+ readonly _types: {
679
+ readonly caps: "app";
680
+ };
681
+ }, name: N & CommandNameBrand<N> & CommandCollisionBrand<N, Sibs>, recipe: ((command: CommandDefinitionBuilder<{}, [], Ctx, never, LocalSpellingsOf<CtxFlags>, {}, CtxFlags>) => B & ShapeFlagCollisionBrand<ShapeOfBuilder<B>, CollisionSp["extension"]> & ValidateInlineCommandDeps<Ctx, NoInfer<B>>) & NoInfer<DescendantValuesBrand<{
682
+ child: ShapeOfBuilder<B>;
683
+ }, CollisionSp["demands"]>>): AfterAdd<Flags, A, Ctx, Sibs, Sp, Tree, CtxFlags, CollisionSp, Result, readonly [CommandDefinition<N, readonly [], ShapeOfBuilder<B>, DepsOfBuilder<B>>], Meta, Caps>;
684
+ private _addDefinitions;
685
+ /**
686
+ * Prepare a frozen Command Snapshot for tooling such as man-page, skill,
687
+ * and build generators.
688
+ *
689
+ * Materializes Extension contributions and command definitions without
690
+ * calling Command Actions.
691
+ */
692
+ snapshot(this: {
693
+ readonly _types: {
694
+ readonly caps: "app";
695
+ };
696
+ }): Promise<CommandSnapshot>;
697
+ /**
698
+ * Programmatically invoke a typed command, quietly capturing its output.
699
+ * Returns completed, finished, or failed after cleanup without presenting errors.
700
+ * Use {@link execute} as the streaming terminal adapter.
701
+ *
702
+ * @param path - Typed path to the command to invoke (`[]` selects the root)
703
+ * @param input - Structured argument, flag, and raw values
704
+ * @param io - Optional `stdout(text)` / `stderr(text)` callbacks, also
705
+ * exposed to Command Actions and Extensions
706
+ */
707
+ run<const Path extends CommandPath<Tree>>(this: {
708
+ readonly _types: {
709
+ readonly caps: "app";
710
+ };
711
+ }, path: Path & KnownCommandPath<Path, Tree>, ...args: RunArguments<CommandShapeAt<CommandShape<A, Flags, Tree, Result>, Path>>): Promise<RunOutcome<CommandShapeAt<CommandShape<A, Flags, Tree, Result>, Path>["result"]>>;
712
+ run<const Path extends CommandPath<Tree>, const Input>(this: {
713
+ readonly _types: {
714
+ readonly caps: "app";
715
+ };
716
+ }, path: Path & KnownCommandPath<Path, Tree>, input: Input, ...validation: [Input] extends [CompatibleRunInput<CommandShapeAt<CommandShape<A, Flags, Tree, Result>, NoInfer<Path>>, Input>] ? readonly [io?: Partial<InvocationIO>] : readonly [invalidInput: never]): Promise<RunOutcome<CommandShapeAt<CommandShape<A, Flags, Tree, Result>, Path>["result"]>>;
717
+ /**
718
+ * Parse `process.argv`, resolve subcommands, run Extension hooks, and
719
+ * execute the matched Command Action.
720
+ *
721
+ * This is the terminal CLI boundary — call it on the root builder. It
722
+ * renders a failure once (through Extension `onError` hooks, ending in
723
+ * Core's default renderer), sets `process.exitCode` (`1`, or
724
+ * `130` for an `AbortError` cancellation), and resolves to the exit code.
725
+ *
726
+ * @param options - Optional overrides (e.g. custom `argv` and captured
727
+ * `io` for in-process testing of exit codes and
728
+ * rendered failures)
729
+ * @returns The terminal exit code (`0`, `1`, or `130` for cancellation)
730
+ */
731
+ execute(this: {
732
+ readonly _types: {
733
+ readonly caps: "app";
734
+ };
735
+ }, options?: {
736
+ argv?: string[];
737
+ io?: Partial<InvocationIO>;
738
+ }): Promise<number>;
253
739
  }
254
- /** A single-value number flag */
255
- interface NumberFlagDef extends SingleFlagBase {
256
- type: "number";
257
- /** Default number value */
258
- default?: number;
259
- /** Not supported on number flags — use `type: "string"` with `parse`. */
260
- parse?: never;
740
+ //#endregion
741
+ //#region src/errors.d.ts
742
+ /** A value thrown by user code crossing a Core error boundary. */
743
+ type CaughtError = unknown;
744
+ /** Details for a subcommand token that could not be resolved. */
745
+ interface CommandNotFoundErrorDetails {
746
+ /** Unrecognized subcommand token. */
747
+ input: string;
748
+ /** Canonical names of visible available child commands. */
749
+ available: string[];
750
+ /** Canonical path to the command whose child could not be resolved. */
751
+ commandPath: string[];
752
+ /** Readonly, serializable snapshot of the parent command. */
753
+ parentCommand: CommandSnapshot;
261
754
  }
262
- /** A single-value boolean flag */
263
- interface BooleanFlagDef extends SingleFlagBase {
264
- type: "boolean";
265
- /** Default boolean value */
266
- default?: boolean;
267
- /** When `true`, hide the generated `--no-{name}` help label */
268
- noNegate?: true;
269
- /** Not supported on boolean flags — use `type: "string"` with `parse`. */
270
- parse?: never;
755
+ /** Aggregated Standard Schema and required-value validation failures. */
756
+ interface ValidationErrorDetails {
757
+ /** Issues normalized under `args.<name>` or `flags.<name>`. */
758
+ issues: readonly {
759
+ readonly message: string;
760
+ readonly path: string;
761
+ }[];
271
762
  }
272
- /** A single-value URL flag (parsed via `new URL()`) */
273
- interface UrlFlagDef extends SingleFlagBase {
274
- type: "url";
275
- /** Default URL value */
276
- default?: URL;
277
- /** Not supported on url flags — use `type: "string"` with `parse`. */
278
- parse?: never;
763
+ /** Details for argv syntax or built-in value parsing failures. */
764
+ interface ParseErrorDetails {
765
+ readonly flag?: string;
766
+ readonly argument?: string;
767
+ /** Retained for compatibility; Core no longer populates this field. */
768
+ readonly value?: string;
769
+ readonly reason?: string;
279
770
  }
280
- /** A single-value path flag (expanded `~` + resolved against `process.cwd()`) */
281
- interface PathFlagDef extends SingleFlagBase {
282
- type: "path";
283
- /** Default path string value */
284
- default?: string;
285
- /** Not supported on path flags — use `type: "string"` with `parse`. */
286
- parse?: never;
771
+ /** Details for runtime recipe, Extension, Context, and documentation failures. */
772
+ interface DefinitionErrorDetails {
773
+ readonly subject?: "command" | "context" | "extension" | "flag" | "argument";
774
+ readonly name?: string;
775
+ readonly reason?: string;
287
776
  }
288
- /** A single-value JSON flag (parsed via `JSON.parse()` to `unknown`) */
289
- interface JsonFlagDef extends SingleFlagBase {
290
- type: "json";
291
- /** Default parsed JSON value */
292
- default?: unknown;
293
- /** Not supported on json flags — use `type: "string"` with `parse`. */
294
- parse?: never;
777
+ interface CrustErrorDetailsMap {
778
+ DEFINITION: DefinitionErrorDetails | undefined;
779
+ VALIDATION: ValidationErrorDetails | undefined;
780
+ PARSE: ParseErrorDetails | undefined;
781
+ COMMAND_NOT_FOUND: CommandNotFoundErrorDetails;
295
782
  }
296
- /** Base for multi-value flags — `multiple` is required as `true` */
297
- interface MultiFlagBase extends FlagDefBase {
298
- /** Collect repeated values into an array */
299
- multiple: true;
783
+ /**
784
+ * All possible error codes emitted by Crust.
785
+ *
786
+ * - `DEFINITION` — Runtime recipe, Extension, Context, or documentation definition failure
787
+ * - `VALIDATION` — Missing required arguments or flags
788
+ * - `PARSE` — Argv parsing failures (unknown flags, type coercion)
789
+ * - `COMMAND_NOT_FOUND` — Unrecognised subcommand at the current level
790
+ *
791
+ * @example
792
+ * ```ts
793
+ * const outcome = await app.run(path, input);
794
+ * if (outcome.status === "failed") {
795
+ * const err = outcome.error;
796
+ * if (err instanceof CrustError) {
797
+ * switch (err.code) {
798
+ * case "VALIDATION":
799
+ * console.error(err.message);
800
+ * showHelp(cmd);
801
+ * break;
802
+ * case "PARSE":
803
+ * console.error(err.message);
804
+ * break;
805
+ * }
806
+ * }
807
+ * }
808
+ * ```
809
+ */
810
+ type CrustErrorCode = keyof CrustErrorDetailsMap;
811
+ type CrustErrorDetails<C extends CrustErrorCode> = CrustErrorDetailsMap[C];
812
+ interface CrustErrorJson<C extends CrustErrorCode> {
813
+ code: C;
814
+ message: string;
815
+ details: CrustErrorDetails<C>;
300
816
  }
301
- /** A multi-value string flag (collects repeated values into an array) */
302
- interface StringMultiFlagDef extends MultiFlagBase {
303
- type: "string";
304
- /** Default string array value */
305
- default?: string[];
306
- /**
307
- * Static enum of valid values for each occurrence of this flag.
308
- *
309
- * Each element is validated at parse time before `parse` runs. Passing
310
- * a value outside `choices` throws `CrustError("PARSE", …)` before any
311
- * `parse` transform is applied. Also consumed by shell-completion
312
- * plugins (e.g. `@crustjs/plugins/completion`) to emit value candidates.
313
- *
314
- * Only available on string-typed multi-flags; not supported on number/boolean.
315
- *
316
- * @example
317
- * { type: "string", multiple: true, choices: ["unit", "integration"] }
318
- */
319
- choices?: readonly string[];
320
- /**
321
- * Custom synchronous per-element parser for each raw argv string.
322
- * See {@link StringFlagDef.parse} for full semantics. Runs once per
323
- * occurrence; the resolved value is `ReturnType<typeof parse>[]`.
324
- */
325
- parse?: (raw: string) => unknown;
817
+ /**
818
+ * A typed error for runtime recipe, Extension, Context, documentation, argv, and validation failures.
819
+ *
820
+ * Every `CrustError` carries a {@link CrustErrorCode} that identifies the specific
821
+ * failure, enabling programmatic error handling without fragile message parsing.
822
+ *
823
+ * @example
824
+ * ```ts
825
+ * import { CrustError } from "@crustjs/core";
826
+ *
827
+ * const outcome = await app.run(["deploy"], { args: { target: "prod" } });
828
+ * if (outcome.status === "failed") {
829
+ * const err = outcome.error;
830
+ * if (err instanceof CrustError) {
831
+ * console.error(`[${err.code}] ${err.message}`);
832
+ * }
833
+ * }
834
+ * ```
835
+ */
836
+ export declare class CrustError<C extends CrustErrorCode = CrustErrorCode> extends Error {
837
+ /** Machine-readable error code for programmatic handling */
838
+ readonly code: C;
839
+ /** Structured payload for programmatic handling */
840
+ readonly details: CrustErrorDetails<C>;
841
+ /** Optional wrapped original error/value */
842
+ override cause?: unknown;
843
+ constructor(code: C, message: string, ...details: undefined extends CrustErrorDetails<C> ? [] | [CrustErrorDetails<C>] : [CrustErrorDetails<C>]);
844
+ is<T extends CrustErrorCode>(code: T): this is CrustError<T>;
845
+ withCause(cause: unknown): this;
846
+ toJSON(): CrustErrorJson<C>;
326
847
  }
327
- /** A multi-value number flag (collects repeated values into an array) */
328
- interface NumberMultiFlagDef extends MultiFlagBase {
329
- type: "number";
330
- /** Default number array value */
331
- default?: number[];
332
- /** Not supported — use `type: "string"`, `multiple: true`, with `parse`. */
333
- parse?: never;
848
+ //#endregion
849
+ //#region src/api/extension.d.ts
850
+ declare const finishedBrand: unique symbol;
851
+ /** Opaque token returned by {@link ExtensionContext.finish} to end an invocation successfully. */
852
+ interface Finished {
853
+ readonly [finishedBrand]: true;
334
854
  }
335
- /** A multi-value boolean flag (collects repeated values into an array) */
336
- interface BooleanMultiFlagDef extends MultiFlagBase {
337
- type: "boolean";
338
- /** Default boolean array value */
339
- default?: boolean[];
340
- /** When `true`, hide the generated `--no-{name}` help label */
341
- noNegate?: true;
342
- /** Not supported — use `type: "string"`, `multiple: true`, with `parse`. */
343
- parse?: never;
855
+ type InvocationOutcome = {
856
+ readonly status: "completed";
857
+ } | {
858
+ readonly status: "finished";
859
+ readonly by: ExtensionId;
860
+ } | {
861
+ readonly status: "failed";
862
+ readonly error: unknown;
863
+ readonly by?: ExtensionId;
864
+ };
865
+ /** Authored root metadata fields an Extension may require. */
866
+ type RootMetaKey = keyof RootCommandMeta;
867
+ type RootCommandSnapshot<K extends RootMetaKey> = CommandSnapshot & {
868
+ readonly meta: Readonly<Required<Pick<CommandMeta, K>>>;
869
+ };
870
+ /** Artifact paths relative to `outDir`. */
871
+ type BuildArtifacts = readonly string[];
872
+ /** Artifacts reported by Extension build hooks, in hook execution order. */
873
+ interface BuildReport {
874
+ readonly extensions: readonly {
875
+ readonly id: ExtensionId;
876
+ readonly files: readonly string[] | "unknown";
877
+ }[];
344
878
  }
345
- /** A multi-value URL flag (collects repeated URL values into an array) */
346
- interface UrlMultiFlagDef extends MultiFlagBase {
347
- type: "url";
348
- /** Default URL array value */
349
- default?: URL[];
350
- /** Not supported — use `type: "string"`, `multiple: true`, with `parse`. */
351
- parse?: never;
879
+ /** Build-time context passed to an Extension's artifact generator. */
880
+ interface ExtensionBuildContext<MetaKeys extends RootMetaKey = never> {
881
+ /**
882
+ * Frozen snapshot prepared before this hook starts. It does not include this hook's own
883
+ * outputs; later-registered hooks receive refreshed snapshots.
884
+ */
885
+ readonly snapshot: RootCommandSnapshot<MetaKeys>;
886
+ /** Resolved absolute output directory. */
887
+ readonly outDir: string;
352
888
  }
353
- /** A multi-value path flag (collects repeated path strings into an array) */
354
- interface PathMultiFlagDef extends MultiFlagBase {
355
- type: "path";
356
- /** Default path array value */
357
- default?: string[];
358
- /** Not supported — use `type: "string"`, `multiple: true`, with `parse`. */
359
- parse?: never;
889
+ /**
890
+ * Readonly invocation view passed to Extension hooks.
891
+ *
892
+ * Commands cross this boundary as readonly, serializable
893
+ * {@link CommandSnapshot}s — never as internal command nodes.
894
+ *
895
+ * Examples below assume the `tool deploy api --trace -- --dry-run` invocation.
896
+ */
897
+ interface ExtensionContext<Defs extends readonly NamedExtensionFlagDef[] = [], Deps extends ContextMap = {}, MetaKeys extends RootMetaKey = never> extends Readonly<InvocationIO> {
898
+ /**
899
+ * Complete argv passed to the application, including routed command names.
900
+ * For typed `run()` this is the command path only; structured values are never rendered as argv.
901
+ *
902
+ * @example `["deploy", "api", "--trace", "--", "--dry-run"]`
903
+ */
904
+ readonly argv: readonly string[];
905
+ /**
906
+ * Snapshot of the application root, including Extension-contributed flags/commands.
907
+ *
908
+ * @example
909
+ * ```ts
910
+ * ctx.rootCommand.meta.name; // "tool"
911
+ * Object.keys(ctx.rootCommand.subCommands); // ["deploy"]
912
+ * ```
913
+ */
914
+ readonly rootCommand: RootCommandSnapshot<MetaKeys>;
915
+ /**
916
+ * Snapshot of the resolved command (the root when routing failed).
917
+ *
918
+ * @example
919
+ * ```ts
920
+ * ctx.command.meta.name; // "deploy"
921
+ * ctx.command.args; // [{ name: "target", type: "string", required: true }]
922
+ * ```
923
+ */
924
+ readonly command: CommandSnapshot;
925
+ /**
926
+ * Canonical names from the application root through the resolved command.
927
+ *
928
+ * @example `["tool", "deploy"]`
929
+ */
930
+ readonly commandPath: readonly string[];
931
+ /**
932
+ * Bound positional values for the resolved command, before validation: parsed from
933
+ * argv tokens for `execute()`, taken as-is from the structured input for typed `run()`
934
+ * (URL/JSON values keep their identity).
935
+ *
936
+ * @example `{ target: "api" }`
937
+ */
938
+ readonly args: Readonly<Record<string, ParsedArgValue>>;
939
+ /**
940
+ * Bound own flags plus unknown flags from the resolved command, before validation;
941
+ * parsed from argv tokens for `execute()`, taken as-is from the structured input for typed `run()`.
942
+ *
943
+ * @example `{ trace: true }`
944
+ */
945
+ readonly flags: Readonly<InferExtensionFlags<Defs> & Record<string, ParsedFlagValue>>;
946
+ /**
947
+ * Positional values that appeared after the `--` separator, or the `raw` array passed to typed `run()`.
948
+ *
949
+ * @example `["--dry-run"]`
950
+ */
951
+ readonly rawArgs: readonly string[];
952
+ /** Declared Contexts, constructed lazily on first property access. */
953
+ readonly ctx: ContextBag<Deps>;
954
+ /**
955
+ * End the invocation successfully before validation, Context construction, and the action.
956
+ *
957
+ * @example
958
+ * ```ts
959
+ * preRun(ctx) {
960
+ * if (ctx.flags.help === true) return ctx.finish();
961
+ * }
962
+ * ```
963
+ */
964
+ readonly finish: () => Finished;
360
965
  }
361
- /** A multi-value JSON flag (collects repeated parsed JSON values) */
362
- interface JsonMultiFlagDef extends MultiFlagBase {
363
- type: "json";
364
- /** Default parsed JSON array value */
365
- default?: unknown[];
366
- /** Not supported — use `type: "string"`, `multiple: true`, with `parse`. */
367
- parse?: never;
966
+ interface ExtensionHooks<Defs extends readonly NamedExtensionFlagDef[] = [], Deps extends ContextMap = {}, MetaKeys extends RootMetaKey = never> {
967
+ /**
968
+ * Runs after routing and input binding (argv parsing for `execute()`, structured
969
+ * binding for typed `run()`), before validation, in `.extend()` order.
970
+ * Return `ctx.finish()` to end the invocation successfully; later pre-run hooks,
971
+ * validation, schemas, Contexts, and the Command Action do not run.
972
+ */
973
+ readonly preRun?: (ctx: ExtensionContext<Defs, Deps, MetaKeys>) => Awaitable<void | Finished>;
974
+ /**
975
+ * Runs after the invocation settles, in reverse `.extend()` order. This is the
976
+ * `finally` slot for cleanup and post-run side effects.
977
+ */
978
+ readonly postRun?: (ctx: ExtensionContext<Defs, Deps, MetaKeys>, outcome: InvocationOutcome) => Awaitable<void>;
979
+ /**
980
+ * Renders a failure in `execute()` only. Return true when rendered to stop the
981
+ * chain; falsy values delegate to the next Extension and then Core's renderer.
982
+ * A hook that throws ends the chain: remaining hooks are skipped and Core's
983
+ * default renderer reports the original failure.
984
+ *
985
+ * Receives the base context: routing or syntax-parse failures render with a
986
+ * fallback context whose `flags` are empty, so owned-flag inference would lie here.
987
+ */
988
+ readonly onError?: (error: CaughtError, ctx: ExtensionContext<[], Deps, MetaKeys>) => Awaitable<boolean | void>;
368
989
  }
369
990
  /**
370
- * Defines a single named flag for a CLI command.
371
- *
372
- * Discriminated by `type` and `multiple` for type-safe `default` values.
373
- * Boolean toggle fields (`required`, `multiple`) only accept `true`.
374
- *
375
- * @example
376
- * ```ts
377
- * const flags = {
378
- * verbose: { type: "boolean", description: "Enable verbose logging", short: "v" },
379
- * port: { type: "number", description: "Port number", default: 3000 },
380
- * files: { type: "string", multiple: true, default: ["index.ts"] },
381
- * } satisfies FlagsDef;
382
- * ```
383
- */
384
- type FlagDef = StringFlagDef | NumberFlagDef | BooleanFlagDef | UrlFlagDef | PathFlagDef | JsonFlagDef | StringMultiFlagDef | NumberMultiFlagDef | BooleanMultiFlagDef | UrlMultiFlagDef | PathMultiFlagDef | JsonMultiFlagDef;
385
- /** Record mapping flag names to their definitions */
386
- type FlagsDef = Record<string, FlagDef>;
387
- /**
388
- * Extract the `short` alias literal from a flag definition.
389
- * Resolves to `never` when no `short` field exists or when the type
390
- * is the broad `string` (not a narrowed literal).
391
- */
392
- type ExtractShort<F> = F extends {
393
- short: infer S;
394
- } ? S extends string ? string extends S ? never : S : never : never;
395
- /**
396
- * Extract alias string literals from the `aliases` array of a flag definition.
397
- * Resolves to `never` when no `aliases` field exists or when the element type
398
- * is the broad `string` (not narrowed literals).
399
- */
400
- type ExtractLongAliases<F> = F extends {
401
- aliases: infer A;
402
- } ? A extends readonly string[] ? string extends A[number] ? never : A[number] : never : never;
403
- /**
404
- * Extract all alias identifiers (short + long) from a flag definition.
405
- *
406
- * Generalized to work with any shape (`FlagDef`, `FlagSpec`, etc.) —
407
- * values without `short`/`aliases` fields resolve to `never`.
408
- *
409
- * Includes `string extends ...` guards so non-narrowed types (e.g. the
410
- * broad `string` type from a default generic) resolve to `never` instead
411
- * of causing false-positive collisions.
412
- */
413
- type ExtractAllAliases<F> = ExtractShort<F> | ExtractLongAliases<F>;
414
- /**
415
- * Collects aliases from every flag *except* flag K.
416
- * Used to detect alias→alias duplicates across different flags.
417
- */
418
- type AliasesExcluding<
419
- F extends Record<string, unknown>,
420
- K extends keyof F & string
421
- > = { [J in Exclude<keyof F & string, K>] : ExtractAllAliases<F[J]> }[Exclude<keyof F & string, K>];
422
- /**
423
- * Per-flag collision detection: resolves to the alias literal(s) of flag K
424
- * that collide with another flag's name or another flag's alias,
425
- * or `never` when K's aliases are all unique.
426
- */
427
- type CollidingAliases<
428
- F extends Record<string, unknown>,
429
- K extends keyof F & string
430
- > = (ExtractAllAliases<F[K]> & Exclude<keyof F & string, K>) | (ExtractAllAliases<F[K]> & AliasesExcluding<F, K>);
431
- /**
432
- * Per-flag validation mapped type. Resolves to `F` when no collisions exist.
433
- * For flags with colliding aliases, adds a branded error property to the
434
- * specific flag definition, causing a type error on that flag's value.
435
- *
436
- * Generalized to work with any `Record<string, unknown>` shape — core uses
437
- * it with `FlagsDef`, the validate package uses it with `FlagShape`, etc.
438
- *
439
- * ```
440
- * Property 'FIX_ALIAS_COLLISION' is missing in type '{ type: "string"; short: "m" }'
441
- * but required in type
442
- * '{ readonly FIX_ALIAS_COLLISION: "Alias \"m\" collides with another flag name or alias" }'.
443
- * ```
444
- */
445
- type ValidateFlagAliases<F extends Record<string, unknown>> = { [K in keyof F & string] : CollidingAliases<F, K> extends never ? F[K] : F[K] & {
446
- readonly FIX_ALIAS_COLLISION: `Alias "${CollidingAliases<F, K>}" collides with another flag name or alias`;
447
- } };
448
- /**
449
- * Collects aliases from inherited flags, excluding those whose keys the
450
- * child overrides (intentional override — child redefines a flag by name).
451
- */
452
- type InheritedAliasesExcluding<
453
- I extends Record<string, unknown>,
454
- OverrideKeys extends string
455
- > = { [K in Exclude<keyof I & string, OverrideKeys>] : ExtractAllAliases<I[K]> }[Exclude<keyof I & string, OverrideKeys>];
456
- /**
457
- * Per-flag cross-collision detection between a child flag K (from local
458
- * flags F) and the inherited flag set I. Resolves to the colliding
459
- * identifier, or `never` when no collision exists.
460
- *
461
- * Detects three collision classes:
462
- * 1. Child alias → inherited flag name
463
- * 2. Child alias → inherited flag alias
464
- * 3. Child flag name → inherited flag alias
465
- *
466
- * Intentional name overrides (child defines a flag with the same key as
467
- * an inherited flag) are excluded — those are handled by `MergeFlags`.
468
- */
469
- type CrossCollision<
470
- I extends Record<string, unknown>,
471
- F extends Record<string, unknown>,
472
- K extends keyof F & string
473
- > = (ExtractAllAliases<F[K]> & Exclude<keyof I & string, keyof F & string>) | (ExtractAllAliases<F[K]> & InheritedAliasesExcluding<I, keyof F & string>) | (K & InheritedAliasesExcluding<I, keyof F & string>);
474
- /**
475
- * Per-flag validation mapped type for cross-collisions between inherited
476
- * and local flags. Resolves to `F` when no collisions exist.
477
- *
478
- * When `Inherited` is the wide `FlagsDef` type (root commands with no
479
- * parent), the validation is skipped to avoid false positives since
480
- * `keyof FlagsDef` is `string`.
481
- *
482
- * ```
483
- * Property 'FIX_INHERITED_COLLISION' is missing in type '{ type: "string"; aliases: ["verbose"] }'
484
- * but required in type
485
- * '{ readonly FIX_INHERITED_COLLISION: "\"verbose\" collides with inherited flag" }'.
486
- * ```
487
- */
488
- type ValidateCrossCollisions<
489
- I extends Record<string, unknown>,
490
- F extends Record<string, unknown>
491
- > = string extends keyof I ? F : { [K in keyof F & string] : CrossCollision<I, F, K> extends never ? F[K] : F[K] & {
492
- readonly FIX_INHERITED_COLLISION: `"${CrossCollision<I, F, K> & string}" collides with inherited flag`;
493
- } };
494
- /**
495
- * Detects whether a single alias literal starts with `"no-"`.
496
- * Resolves to the offending alias, or `never` when it is clean.
497
- */
498
- type NoPrefixedAlias<A> = A extends `no-${string}` ? A : never;
499
- /**
500
- * Collects all `"no-"`-prefixed alias literals from a flag definition.
501
- * Checks both `short` and `aliases` fields.
502
- * Non-narrowed `string` types resolve to `never` to avoid false positives.
503
- */
504
- type NoPrefixedAliases<F> = NoPrefixedAlias<ExtractShort<F>> | NoPrefixedAlias<ExtractLongAliases<F>>;
505
- /**
506
- * Per-flag validation mapped type. Resolves to `F` when no `"no-"` prefixes
507
- * exist on flag names, short aliases, or long aliases. For flags with offending values,
508
- * adds a branded error property causing a compile-time type error.
509
- *
510
- * The `"no-"` prefix is reserved for boolean flag negation (`--no-flag`).
511
- * Define only the positive form (e.g. `cache`) and use `--no-cache` at runtime.
512
- *
513
- * ```
514
- * Property 'FIX_NO_PREFIX' is missing in type '{ type: "boolean" }'
515
- * but required in type
516
- * '{ readonly FIX_NO_PREFIX: "Flag name \"no-cache\" must not start with \"no-\"; define \"cache\" instead and use \"--no-cache\" at runtime" }'.
517
- * ```
518
- */
519
- type ValidateNoPrefixedFlags<F extends Record<string, unknown>> = { [K in keyof F & string] : K extends `no-${infer Base}` ? F[K] & {
520
- readonly FIX_NO_PREFIX: `Flag name "${K}" must not start with "no-"; define "${Base}" instead and use "--no-${Base}" at runtime`;
521
- } : NoPrefixedAliases<F[K]> extends never ? F[K] : F[K] & {
522
- readonly FIX_NO_PREFIX: `Alias "${NoPrefixedAliases<F[K]>}" must not start with "no-"; the "no-" prefix is reserved for boolean negation`;
523
- } };
524
- /**
525
- * Per-arg validation tuple type. Resolves to `A` when the constraint is
526
- * satisfied (only the last arg is variadic). For non-last args that have
527
- * `variadic: true`, adds a branded error property to the specific arg.
528
- *
529
- * Generalized to work with any ordered tuple of object-typed definitions —
530
- * core uses it with `ArgsDef`, the validate package uses it with
531
- * `ArgSpec[]`, etc. Uses `readonly object[]` to avoid TypeScript's weak
532
- * type detection (all-optional constraint rejection).
533
- *
534
- * ```
535
- * Property 'FIX_VARIADIC_POSITION' is missing in type '{ name: "files"; ... variadic: true }'
536
- * but required in type
537
- * '{ readonly FIX_VARIADIC_POSITION: "Only the last positional argument can be variadic" }'.
538
- * ```
539
- */
540
- type ValidateVariadicArgs<A extends readonly object[]> = A extends readonly [infer Head, ...infer Tail extends readonly object[]] ? Tail extends readonly [unknown, ...unknown[]] ? Head extends {
541
- variadic: true;
542
- } ? readonly [Head & {
543
- readonly FIX_VARIADIC_POSITION: "Only the last positional argument can be variadic";
544
- }, ...ValidateVariadicArgs<Tail>] : readonly [Head, ...ValidateVariadicArgs<Tail>] : readonly [Head] : A;
545
- /**
546
- * Picks only the flags from `F` that have `inherit: true`.
547
- *
548
- * Flags without `inherit` (or with `inherit` omitted) are excluded.
549
- *
550
- * @example
551
- * ```ts
552
- * type Flags = {
553
- * verbose: { type: "boolean"; inherit: true };
554
- * port: { type: "number" };
555
- * };
556
- * type Result = InheritableFlags<Flags>;
557
- * // Result = { verbose: { type: "boolean"; inherit: true } }
558
- * ```
559
- */
560
- type InheritableFlags<F extends FlagsDef> = { [K in keyof F as F[K] extends {
561
- inherit: true;
562
- } ? K : never] : F[K] };
563
- /**
564
- * Merges parent flags with local flags, where local keys override parent keys.
565
- *
566
- * @example
567
- * ```ts
568
- * type Parent = { verbose: { type: "boolean" }; port: { type: "number" } };
569
- * type Local = { port: { type: "string" } };
570
- * type Result = MergeFlags<Parent, Local>;
571
- * // Result = { verbose: { type: "boolean" }; port: { type: "string" } }
572
- * ```
573
- */
574
- type MergeFlags<
575
- Parent extends FlagsDef,
576
- Local extends FlagsDef
577
- > = Simplify<Omit<Parent, keyof Local> & Local>;
578
- /**
579
- * Computes the effective flags for a command by filtering the inherited flags
580
- * (only those with `inherit: true`) and merging them with local flags.
581
- *
582
- * Local flags override inherited flags with the same key.
583
- *
584
- * @example
585
- * ```ts
586
- * type Inherited = {
587
- * verbose: { type: "boolean"; inherit: true };
588
- * port: { type: "number" };
589
- * };
590
- * type Local = { output: { type: "string" } };
591
- * type Result = EffectiveFlags<Inherited, Local>;
592
- * // Result = { verbose: { type: "boolean"; inherit: true }; output: { type: "string" } }
593
- * ```
594
- */
595
- type EffectiveFlags<
596
- Inherited extends FlagsDef,
597
- Local extends FlagsDef
598
- > = string extends keyof Inherited ? Local : MergeFlags<InheritableFlags<Inherited>, Local>;
599
- /**
600
- * Infer the resolved type for a single ArgDef:
601
- *
602
- * - **variadic** → `primitive[]` (always an array, never `undefined`,
603
- * regardless of `required` or `default`)
604
- * - **required** or **has default** → `primitive` (non-optional)
605
- * - otherwise → `primitive | undefined`
606
- *
607
- * The variadic branch is checked first and takes precedence. Combining
608
- * `variadic: true` with `required: true` keeps the inferred type as `T[]`;
609
- * `required` only gates empty-array validation, not the type.
610
- */
611
- type InferArgValue<A extends ArgDef> = A extends {
612
- type: infer _T extends ValueType;
613
- } ? A extends {
614
- variadic: true;
615
- } ? ResolveBaseType<A>[] : A extends {
616
- required: true;
617
- } ? ResolveBaseType<A> : A extends {
618
- default: unknown;
619
- } ? ResolveBaseType<A> : ResolveBaseType<A> | undefined : A extends {
620
- variadic: true;
621
- } ? unknown[] : A extends {
622
- required: true;
623
- } | {
624
- default: unknown;
625
- } ? unknown : unknown;
626
- /**
627
- * Recursively converts an ArgsDef tuple into a named object type.
628
- *
629
- * Each element's `name` literal becomes a key, and its value is resolved
630
- * via {@link InferArgValue}. Uses intersection + `Simplify` to flatten.
631
- */
632
- type InferArgsTuple<A extends readonly ArgDef[]> = A extends readonly [infer Head extends ArgDef, ...infer Tail extends readonly ArgDef[]] ? { [K in Head["name"]] : InferArgValue<Head> } & InferArgsTuple<Tail> : {};
633
- /** Flattens an intersection of objects into a single object type for readability */
634
- type Simplify<T> = { [K in keyof T] : T[K] };
635
- /**
636
- * Maps an ArgsDef tuple to resolved arg types keyed by each arg's `name`.
637
- *
638
- * @example
639
- * ```ts
640
- * type Result = InferArgs<readonly [
641
- * { name: "port"; type: "number"; default: 3000 },
642
- * { name: "name"; type: "string"; required: true },
643
- * { name: "files"; type: "string"; variadic: true },
644
- * ]>;
645
- * // Result = { port: number; name: string; files: string[] }
646
- * ```
647
- */
648
- type InferArgs<A> = A extends ArgsDef ? Simplify<InferArgsTuple<A>> : Record<string, never>;
649
- /**
650
- * Infer the resolved type for a single FlagDef:
651
- *
652
- * - **multiple** → wraps the resolved type in an array
653
- * - **required** or **has default** → `primitive` (non-optional)
654
- * - otherwise → `primitive | undefined`
655
- */
656
- type InferFlagValue<F extends FlagDef> = F extends {
657
- type: infer _T extends ValueType;
991
+ * A flag owned by an Extension. `recursive` (default `true`) contributes the
992
+ * flag to every command in the application; set `false` for a root-only flag.
993
+ */
994
+ type ExtensionFlagDef = FlagDef & {
995
+ readonly recursive?: boolean;
996
+ };
997
+ /** A named flag definition accepted by {@link defineExtension}. */
998
+ type NamedExtensionFlagDef = NamedFlagDef & {
999
+ readonly recursive?: boolean;
1000
+ };
1001
+ type InferPreSchemaExtensionFlag<F extends ExtensionFlagDef> = F extends {
1002
+ schema: unknown;
658
1003
  } ? F extends {
659
- multiple: true;
1004
+ multiple: true;
660
1005
  } ? F extends {
661
- required: true;
662
- } ? ResolveBaseType<F>[] : F extends {
663
- default: readonly unknown[];
664
- } ? ResolveBaseType<F>[] : ResolveBaseType<F>[] | undefined : F extends {
665
- required: true;
666
- } ? ResolveBaseType<F> : F extends {
667
- default: unknown;
668
- } ? ResolveBaseType<F> : ResolveBaseType<F> | undefined : never;
669
- /**
670
- * Maps a full FlagsDef record to resolved flag types.
671
- *
672
- * @example
673
- * ```ts
674
- * type Result = InferFlags<{
675
- * verbose: { type: "boolean" };
676
- * port: { type: "number", default: 3000 };
677
- * }>;
678
- * // Result = { verbose: boolean | undefined; port: number }
679
- * ```
680
- */
681
- type InferFlags<F> = F extends FlagsDef ? { [K in keyof F] : InferFlagValue<F[K]> } : Record<string, never>;
682
- /** Metadata describing a CLI command */
683
- interface CommandMeta {
684
- /** The command name (used in help text and routing) */
685
- name: string;
686
- /** Human-readable description for help text */
687
- description?: string;
688
- /** Custom usage string (overrides auto-generated usage) */
689
- usage?: string;
690
- /**
691
- * Alternative names that resolve to the same command.
692
- *
693
- * Each entry is a sibling-level alternative for `name`. For example,
694
- * `meta: { name: "issue", aliases: ["issues", "i"] }` makes `cli issue`,
695
- * `cli issues`, and `cli i` all route to the same command node.
696
- *
697
- * **Conflict policy.** Alias strings must not collide with this command's
698
- * own canonical `name`, with any sibling's `name`, or with any sibling's
699
- * own alias. Collisions throw a `CrustError("DEFINITION", …)` at
700
- * registration time (or during `validateCommandTree` for plugin-installed
701
- * subcommands). Each alias must also be a non-empty string with no
702
- * whitespace and must not start with `-`.
703
- *
704
- * **Display contract.** Help output renders the canonical name with
705
- * aliases inline as `name (a, b, c)`. The canonical `name` is what
706
- * appears in `commandPath`, error messages, and suggestions from
707
- * `didYouMeanPlugin` — it does not depend on which alias the user typed.
708
- *
709
- * @example
710
- * meta: { name: "issue", aliases: ["issues", "i"] }
711
- */
712
- aliases?: readonly string[];
713
- /**
714
- * When `true`, omit this command from every tooling surface that
715
- * enumerates the command tree for users:
716
- *
717
- * - `helpPlugin` rendered output (subcommand list + USAGE token)
718
- * - `@crustjs/man` generated man pages (`SUBCOMMANDS` section)
719
- * - `completionPlugin` candidate lists (recursively — hidden
720
- * subcommands and their descendants never appear in generated
721
- * bash/zsh/fish scripts)
722
- * - `didYouMeanPlugin` typo suggestions and "Available commands"
723
- * list (so internal names never surface in error UX)
724
- * - `skillPlugin` manifests
725
- *
726
- * The command is **only hidden from listings**: routing in
727
- * `@crustjs/core` does not consult `meta.hidden`, so it stays fully
728
- * invocable by direct name (or alias). The intended use case is
729
- * internal/runtime commands like a `__complete` shell-completion
730
- * entrypoint. Marking a user-facing command `hidden` is supported but
731
- * unusual.
732
- *
733
- * **Scope: commands only.** There is no analogous `hidden` field on
734
- * `FlagDef` or `ArgDef`; flags and positional arguments always surface
735
- * in help, completion, and man output. If you need a flag that does
736
- * not advertise itself, the workaround is to register it through a
737
- * plugin's `setup()` hook without describing it (omit `description`),
738
- * which suppresses its description body but still lists the spelling
739
- * — there is intentionally no full hide mechanism at the flag layer.
740
- *
741
- * Tooling contract: any renderer or generator that walks
742
- * `subCommands` to produce a user-facing listing should skip nodes
743
- * where `meta.hidden === true`.
744
- *
745
- * @example
746
- * meta: { name: "__complete", hidden: true, description: "Internal" }
747
- */
748
- hidden?: boolean;
749
- }
750
- /**
751
- * The result of parsing argv against a command's arg/flag definitions.
752
- *
753
- * Generic parameters flow from the command definition to provide
754
- * strongly-typed `args` and `flags` objects.
755
- */
756
- interface ParseResult<
757
- A extends ArgsDef = ArgsDef,
758
- F extends FlagsDef = FlagsDef
759
- > {
760
- /** Resolved positional arguments, keyed by arg name */
761
- args: InferArgs<A>;
762
- /** Resolved flags, keyed by flag name */
763
- flags: InferFlags<F>;
764
- /** Raw arguments that appeared after the `--` separator */
765
- rawArgs: string[];
766
- }
767
- interface PluginState {
768
- get<T = unknown>(key: string): T | undefined;
769
- has(key: string): boolean;
770
- set(key: string, value: unknown): void;
771
- delete(key: string): boolean;
1006
+ type: "boolean";
1007
+ } ? boolean[] | undefined : string[] | undefined : F extends {
1008
+ type: "boolean";
1009
+ } ? boolean | undefined : string | undefined : F extends {
1010
+ required: true;
1011
+ } ? F extends {
1012
+ default: unknown;
1013
+ } ? InferFlags<{
1014
+ value: F;
1015
+ }>["value"] : InferFlags<{
1016
+ value: F;
1017
+ }>["value"] | undefined : InferFlags<{
1018
+ value: F;
1019
+ }>["value"];
1020
+ type InferExtensionFlag<F> = F extends ExtensionFlagDef ? F extends {
1021
+ recursive: false;
1022
+ } ? InferPreSchemaExtensionFlag<F> | undefined : InferPreSchemaExtensionFlag<F> : never;
1023
+ /** Infer the syntax-parsed values visible to an Extension's hooks. */
1024
+ type InferExtensionFlags<Defs extends readonly NamedExtensionFlagDef[]> = { [K in keyof NamedFlagsRecord<Defs>]: InferExtensionFlag<NamedFlagsRecord<Defs>[K]>; };
1025
+ /** A documentation section an Extension contributes to one command path. */
1026
+ type ExtensionSectionContribution = RuntimeCommandSectionInput & {
1027
+ readonly command: readonly string[];
1028
+ };
1029
+ type CommandDefinitionsDependencies<Commands extends readonly CommandDefinition<any, any, any, any>[]> = Commands extends readonly [infer H extends CommandDefinition<any, any, any, any>, ...infer T extends readonly CommandDefinition<any, any, any, any>[]] ? ([H] extends [never] ? {} : DeclaredDepsOf<H>) & CommandDefinitionsDependencies<T> : Commands extends readonly [] ? {} : Record<string, ContextValue>;
1030
+ interface ExtensionConfig<Defs extends readonly NamedExtensionFlagDef[] = readonly NamedExtensionFlagDef[], Uses extends readonly AnyContextFactory[] = readonly AnyContextFactory[], Provides extends readonly AnyContextInstance[] = readonly AnyContextInstance[], Commands extends readonly CommandDefinition<any, any, any, any>[] = readonly CommandDefinition<any, any, any, any>[], MetaKeys extends RootMetaKey = never> {
1031
+ readonly flags?: Defs;
1032
+ readonly commands?: Commands;
1033
+ readonly uses?: Uses;
1034
+ readonly provides?: Provides;
1035
+ readonly sections?: (snapshot: RootCommandSnapshot<MetaKeys>) => readonly ExtensionSectionContribution[];
1036
+ readonly build?: (ctx: ExtensionBuildContext<MetaKeys>) => BuildArtifacts | void | Promise<BuildArtifacts | void>;
1037
+ readonly hooks?: ExtensionHooks<Defs, ContextDependencies<Uses>, MetaKeys>;
772
1038
  }
773
- /** A command target accepted by SetupActions */
774
- type CommandTarget = CommandNode;
775
- interface SetupActions {
776
- /**
777
- * Inject a flag definition into a command's flags object.
778
- *
779
- * Use this in plugin `setup()` hooks to register plugin-specific flags
780
- * (e.g. `--version`, `--help`) so they are recognized by the parser
781
- * and rendered in help text.
782
- *
783
- * The flag is added to `effectiveFlags` only — `localFlags` is left
784
- * unchanged to distinguish user-defined flags from plugin-injected ones.
785
- *
786
- * @param command - The command node to add the flag to
787
- * @param name - The flag name (e.g. "version")
788
- * @param def - The flag definition
789
- */
790
- addFlag(command: CommandTarget, name: string, def: FlagDef): void;
791
- /**
792
- * Inject a subcommand into a command's `subCommands` record.
793
- *
794
- * Use this in plugin `setup()` hooks to register plugin-provided commands
795
- * (e.g. a "skill" management command). If the parent already has a
796
- * subcommand with the same name (user-defined), the call is silently
797
- * skipped — user definitions always take priority over plugin injections.
798
- *
799
- * @param parent - The parent command node to add the subcommand to
800
- * @param name - The subcommand name (used for routing)
801
- * @param command - The subcommand node to register
802
- */
803
- addSubCommand(parent: CommandTarget, name: string, command: CommandTarget): void;
1039
+ type ValidateExtensionConfig<Defs extends readonly NamedExtensionFlagDef[], Provides extends readonly AnyContextInstance[], Commands extends readonly CommandDefinition<any, any, any, any>[], Uses extends readonly AnyContextFactory[]> = {
1040
+ readonly uses?: Uses;
1041
+ readonly commands?: ValidateCommandDefinitions<Commands>;
1042
+ readonly flags?: ValidateLocalFlagDefs<Defs, ProvidedContextSpellings<Provides>>;
1043
+ readonly provides?: KnownContextInstances<Provides> & ProvideChecks<never, Provides>;
1044
+ };
1045
+ declare const extensionHookProof: unique symbol;
1046
+ interface Extension<Deps extends ContextMap = ContextMap, Provides extends readonly AnyContextInstance[] = readonly AnyContextInstance[], FlagDefs extends readonly NamedExtensionFlagDef[] = readonly NamedExtensionFlagDef[], Commands extends readonly CommandDefinition<any, any, any, any>[] = readonly CommandDefinition<any, any, any, any>[], out MetaKeys extends RootMetaKey = never, HookDeps extends ContextMap = Deps> extends Defining<Extension<Deps, Provides, FlagDefs, Commands, MetaKeys, HookDeps>> {
1047
+ /** @internal Hook demands are distinct from command/provider attachment dependencies. */
1048
+ readonly _hookDeps?: HookDeps;
1049
+ readonly [extensionHookProof]?: (deps: HookDeps) => void;
1050
+ readonly id: ExtensionId;
1051
+ readonly flags?: Readonly<Record<string, ExtensionFlagDef>>;
1052
+ /** @internal — phantom carrying declared flag literals for extend-time collision checks */
1053
+ readonly _flagDefs?: FlagDefs;
1054
+ readonly commands?: Commands;
1055
+ readonly uses: readonly AnyContextFactory[];
1056
+ readonly provides?: Provides;
1057
+ readonly sections?: (snapshot: RootCommandSnapshot<MetaKeys>) => readonly ExtensionSectionContribution[];
1058
+ readonly build?: (ctx: ExtensionBuildContext<MetaKeys>) => BuildArtifacts | void | Promise<BuildArtifacts | void>;
1059
+ readonly hooks?: ExtensionHooks<any, Deps, MetaKeys>;
1060
+ readonly _deps?: Deps;
804
1061
  }
805
- /** Shared context fields available in both setup and middleware phases. */
806
- interface BaseContext {
807
- readonly argv: readonly string[];
808
- readonly rootCommand: CommandNode;
809
- readonly state: PluginState;
1062
+ /** @internal Broad Extension constraint; contravariance requires the full metadata key set. */
1063
+ type AnyExtension = Extension<any, any, any, any, RootMetaKey>;
1064
+ type ExtensionProvidesOutput<E> = DefiningOf<E> extends Extension<any, infer Provides, any, any, RootMetaKey> ? ContextsOutput<Provides> : {};
1065
+ type ExtensionsProvidesOutput<Es extends readonly AnyExtension[]> = Es extends readonly [infer H, ...infer T extends readonly AnyExtension[]] ? MergeProviders<ExtensionProvidesOutput<H>, ExtensionsProvidesOutput<T>> : Es extends readonly [] ? {} : Es[number] extends Extension<any, infer P, any, any, RootMetaKey> ? P[number] extends never ? {} : Record<string, ContextValue> : {};
1066
+ /**
1067
+ * A callable Extension constructor whose identity is also a section consumer.
1068
+ * Contribution parameters default to closed sets. Use `ContextMap`,
1069
+ * `readonly AnyContextInstance[]`, `readonly NamedExtensionFlagDef[]`, or
1070
+ * `readonly CommandDefinition<any, any, any, any>[]` to keep a namespace open.
1071
+ */
1072
+ 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>) & {
1073
+ readonly id: ExtensionId;
1074
+ };
1075
+ /** Curried Extension definer: explicit metadata keys leave all other types inferred. */
1076
+ interface DefineExtensionWith<MetaKeys extends RootMetaKey> {
1077
+ <Args extends readonly unknown[], const Defs extends readonly NamedExtensionFlagDef[] = [], const Uses extends readonly AnyContextFactory[] = [], const Provides extends readonly AnyContextInstance[] = [], const Commands extends readonly CommandDefinition<any, any, any, any>[] = []>(id: ExtensionId, factory: (...args: Args) => ExtensionConfig<Defs, Uses, Provides, Commands, MetaKeys> & ValidateExtensionConfig<Defs, Provides, Commands, Uses>): ExtensionFactory<Args, ContextDependencies<Uses> & ContextsDependencies<Provides> & CommandDefinitionsDependencies<Commands>, Provides, Defs, Commands, MetaKeys, ContextDependencies<Uses>>;
1078
+ <const Defs extends readonly NamedExtensionFlagDef[] = [], const Uses extends readonly AnyContextFactory[] = [], const Provides extends readonly AnyContextInstance[] = [], const Commands extends readonly CommandDefinition<any, any, any, any>[] = []>(id: ExtensionId, config?: ExtensionConfig<Defs, Uses, Provides, Commands, MetaKeys> & ValidateExtensionConfig<Defs, Provides, Commands, Uses>): Extension<ContextDependencies<Uses> & ContextsDependencies<Provides> & CommandDefinitionsDependencies<Commands>, Provides, Defs, Commands, MetaKeys, ContextDependencies<Uses>>;
810
1079
  }
811
- /** Context passed to plugin `setup()` hooks. */
812
- interface SetupContext extends BaseContext {}
813
- /** Context passed to plugin `middleware()` hooks. */
814
- interface MiddlewareContext extends BaseContext {
815
- route: Readonly<CommandRoute> | null;
816
- input: ParseResult | null;
1080
+ /**
1081
+ * Define an Extension, or a factory that builds one from config on each call.
1082
+ *
1083
+ * Extensions apply to the whole application and own the flags and commands
1084
+ * they contribute. Factories expose the same identity for section audiences.
1085
+ * `defineExtension<"version">()(id, configOrFactory)` declares required root
1086
+ * metadata keys without preventing inference of flags, Contexts, or commands.
1087
+ */
1088
+ export declare function defineExtension<MetaKeys extends RootMetaKey = never>(): DefineExtensionWith<MetaKeys>;
1089
+ export declare function defineExtension<Args extends readonly unknown[], const Defs extends readonly NamedExtensionFlagDef[] = [], const Uses extends readonly AnyContextFactory[] = [], const Provides extends readonly AnyContextInstance[] = [], const Commands extends readonly CommandDefinition<any, any, any, any>[] = []>(id: ExtensionId, factory: (...args: Args) => ExtensionConfig<Defs, Uses, Provides, Commands> & ValidateExtensionConfig<Defs, Provides, Commands, Uses>): ExtensionFactory<Args, ContextDependencies<Uses> & ContextsDependencies<Provides> & CommandDefinitionsDependencies<Commands>, Provides, Defs, Commands, never, ContextDependencies<Uses>>;
1090
+ export declare function defineExtension<const Defs extends readonly NamedExtensionFlagDef[] = [], const Uses extends readonly AnyContextFactory[] = [], const Provides extends readonly AnyContextInstance[] = [], const Commands extends readonly CommandDefinition<any, any, any, any>[] = []>(id: ExtensionId, config?: ExtensionConfig<Defs, Uses, Provides, Commands> & ValidateExtensionConfig<Defs, Provides, Commands, Uses>): Extension<ContextDependencies<Uses> & ContextsDependencies<Provides> & CommandDefinitionsDependencies<Commands>, Provides, Defs, Commands, never, ContextDependencies<Uses>>;
1091
+ //#endregion
1092
+ //#region src/parsing/spellings.d.ts
1093
+ interface FlagSpelling {
1094
+ canonicalName: string;
1095
+ def: FlagDef;
1096
+ kind: "canonical" | "short" | "alias";
1097
+ negatable: boolean;
817
1098
  }
818
- type Next = () => Promise<void>;
819
- type PluginMiddleware = (context: MiddlewareContext, next: Next) => void | Promise<void>;
820
- interface CrustPlugin {
821
- name?: string;
822
- setup?: (context: SetupContext, actions: SetupActions) => void | Promise<void>;
823
- middleware?: PluginMiddleware;
1099
+ //#endregion
1100
+ //#region src/command/node.d.ts
1101
+ /** Runtime-erased Command Action; typed builders and run() own the specific result contract. */
1102
+ type CommandAction = (ctx: CrustCommandContext) => unknown;
1103
+ interface CommandContext {
1104
+ instance: AnyContextInstance;
1105
+ extensionId?: ExtensionId;
824
1106
  }
825
1107
  /**
826
- * Internal representation of a single node in the command tree.
827
- *
828
- * Built by the `Crust` builder class; not part of the public API.
829
- * Each node carries its own local flags, the pre-computed effective
830
- * (inherited + local merged) flags, positional args, subcommands,
831
- * plugins, and lifecycle handlers.
832
- */
1108
+ * Internal representation of a single node in the command tree.
1109
+ *
1110
+ * Built by the `Crust` builder class; not part of the public API.
1111
+ * Each node carries its own local flags, the pre-computed effective
1112
+ * (Context-owned + local merged) flags, positional args, subcommands,
1113
+ * extensions, and the Command Action.
1114
+ */
833
1115
  interface CommandNode {
834
- /** Command metadata (name, description, usage) */
835
- meta: CommandMeta;
836
- /** Flags defined directly on this command via `.flags()` */
837
- localFlags: FlagsDef;
838
- /** Inherited flags merged with local flags (used by the parser) */
839
- effectiveFlags: FlagsDef;
840
- /** Positional argument definitions */
841
- args: ArgsDef | undefined;
842
- /** Named subcommands keyed by name */
843
- subCommands: Record<string, CommandNode>;
844
- /** Plugins registered via `.use()` */
845
- plugins: CrustPlugin[];
846
- /** Called before `run()` — useful for initialization */
847
- preRun?: (ctx: unknown) => void | Promise<void>;
848
- /** The main command handler */
849
- run?: (ctx: unknown) => void | Promise<void>;
850
- /** Called after `run()` (even if it throws) — useful for teardown */
851
- postRun?: (ctx: unknown) => void | Promise<void>;
1116
+ /** Command metadata (name, description, usage) */
1117
+ meta: CommandMeta;
1118
+ /** Flags defined directly on this command via `.flags()` */
1119
+ localFlags: FlagsDef;
1120
+ /** Accumulated flags owned by Contexts provided on this command path */
1121
+ ownedFlags: FlagsDef;
1122
+ /** Context-owned and local flags merged for parsing */
1123
+ effectiveFlags: FlagsDef;
1124
+ /** Cached canonical/short/alias table for the effective flags. */
1125
+ flagSpellings: Map<string, FlagSpelling>;
1126
+ /** Positional argument definitions */
1127
+ args: ArgsDef;
1128
+ /** Named subcommands keyed by name */
1129
+ subCommands: Record<string, CommandNode>;
1130
+ /** Contexts available to this command in provide order (construction order is pull-driven). */
1131
+ contexts: CommandContext[];
1132
+ /** Declared command demands; validated when recipes are materialized. */
1133
+ demands: readonly AnyContextFactory[];
1134
+ /** Extensions registered via `.extend()` (root builder only) */
1135
+ extensions: Extension[];
1136
+ /** The Command Action */
1137
+ run?: CommandAction;
852
1138
  }
853
- /**
854
- * The runtime context object passed to `preRun()`, `run()`, and `postRun()`
855
- * lifecycle hooks on the `Crust` builder.
856
- *
857
- * Generic parameters:
858
- * - `A` — positional argument definitions tuple
859
- * - `F` — the effective (inherited + local merged) flag definitions
860
- */
861
- interface CrustCommandContext<
862
- A extends ArgsDef = ArgsDef,
863
- F extends FlagsDef = FlagsDef
864
- > {
865
- /** Resolved positional arguments, keyed by arg name */
866
- args: InferArgs<A>;
867
- /** Resolved flags, keyed by flag name */
868
- flags: InferFlags<F>;
869
- /** Raw arguments that appeared after the `--` separator */
870
- rawArgs: string[];
871
- /** The resolved command node that is being executed */
872
- command: CommandNode;
1139
+ //#endregion
1140
+ //#region src/api/context.d.ts
1141
+ /** Upper bound for phantom name-to-value context maps. */
1142
+ type ContextMap = object;
1143
+ declare const defining: unique symbol;
1144
+ declare const contextProof: unique symbol;
1145
+ /** @internal Immutable defining data retained through public structural copies. */
1146
+ interface Defining<T> {
1147
+ readonly [defining]: T;
873
1148
  }
874
- /**
875
- * Build-time validation protocol.
876
- *
877
- * `crust build` spawns the user's entrypoint as a subprocess with
878
- * `CRUST_INTERNAL_VALIDATE_ONLY=1` (and the companion
879
- * {@link VALIDATION_FORCE_EXIT_ENV}=`1`). When `.execute()` detects
880
- * `VALIDATION_MODE_ENV` it runs the validation pipeline and surfaces errors
881
- * via stderr and `process.exitCode`.
882
- *
883
- * Process termination is opt-in via {@link VALIDATION_FORCE_EXIT_ENV} so
884
- * that in-process callers (tests, embedders) that set only this env get
885
- * the validation result without having their host process killed.
886
- */
887
- declare const VALIDATION_MODE_ENV = "CRUST_INTERNAL_VALIDATE_ONLY";
888
- /**
889
- * Companion to {@link VALIDATION_MODE_ENV}. When set to `"1"` _alongside_
890
- * `VALIDATION_MODE_ENV`, `.execute()` calls `process.exit()` after the
891
- * validation pipeline completes — ensuring any code that follows
892
- * `await app.execute()` in the user's entrypoint does not run during
893
- * `crust build`'s pre-compile validation subprocess.
894
- *
895
- * Without this flag, `.execute()` only sets `process.exitCode` and returns,
896
- * matching the rest of `.execute()`'s error handling. This is the path
897
- * in-process callers (tests that toggle `VALIDATION_MODE_ENV`, programmatic
898
- * embedders) take so the host event loop is not terminated.
899
- */
900
- declare const VALIDATION_FORCE_EXIT_ENV = "CRUST_INTERNAL_VALIDATE_FORCE_EXIT";
901
- /**
902
- * Chainable builder for defining CLI commands with full type inference.
903
- *
904
- * Generic parameters:
905
- * - `Inherited` — flags inherited from a parent command (populated by `.command()`)
906
- * - `Local` — flags defined on this command via `.flags()`
907
- * - `A` — positional argument definitions
908
- * - `Eff` — effective flags (merged inherited + local flags, computed internally)
909
- *
910
- * @example
911
- * ```ts
912
- * const app = new Crust("my-cli")
913
- * .flags({
914
- * verbose: { type: "boolean", short: "v", inherit: true },
915
- * })
916
- * .args([{ name: "file", type: "string", required: true }])
917
- * .run(({ args, flags }) => {
918
- * console.log(args.file, flags.verbose);
919
- * });
920
- * ```
921
- */
922
- declare class Crust<
923
- Inherited extends FlagsDef = FlagsDef,
924
- Local extends FlagsDef = FlagsDef,
925
- A extends ArgsDef = ArgsDef,
926
- Eff extends FlagsDef = EffectiveFlags<Inherited, Local>
927
- > {
928
- /** @internal — Phantom property exposing generic parameters for type-level testing */
929
- readonly _types: {
930
- inherited: Inherited;
931
- local: Local;
932
- args: A;
933
- effective: Eff;
934
- };
935
- /** @internal */
936
- readonly _node: CommandNode;
937
- /** @internal — The inherited flags record (runtime counterpart of Inherited generic) */
938
- readonly _inheritedFlags: FlagsDef;
939
- /**
940
- * Create a new root or standalone command builder.
941
- *
942
- * @param name - The command name.
943
- * @throws {CrustError} `DEFINITION` if name is empty or whitespace-only
944
- */
945
- constructor(name: string);
946
- /**
947
- * @internal — Create a child builder with pre-populated inherited flags.
948
- * Used by `.command()` to propagate parent flags to the child.
949
- */
950
- static _createChild<I extends FlagsDef>(name: string, inheritedFlags: FlagsDef): Crust<I, {}, [], EffectiveFlags<I, {}>>;
951
- /**
952
- * @internal — Clone this builder with a new node, preserving generics.
953
- */
954
- private _clone;
955
- /**
956
- * Set metadata (description, usage) for this command.
957
- *
958
- * The command name is already set by the builder source (constructor,
959
- * `.sub()`, or the child builder passed into `.command(name, cb)`).
960
- * Provide `description`, `usage`, and/or `aliases` here.
961
- *
962
- * Returns a new builder with updated metadata. The original builder
963
- * is not mutated.
964
- *
965
- * @param meta - Metadata fields to set (description, usage, aliases)
966
- * @returns A new `Crust` instance with updated metadata
967
- * @example
968
- * ```ts
969
- * .command("issue", (cmd) =>
970
- * cmd.meta({ aliases: ["issues", "i"] }).run(() => {})
971
- * )
972
- * ```
973
- */
974
- meta(meta: Omit<CommandMeta, "name">): Crust<Inherited, Local, A, Eff>;
975
- /**
976
- * Define local flags for this command.
977
- *
978
- * Returns a new builder with updated local flag types. The original
979
- * builder is not mutated.
980
- *
981
- * NOTE: Compile-time inherited/local cross-collision checks are intentionally
982
- * omitted here to reduce TypeScript type-check cost in large projects.
983
- * Runtime collision checks still run during parsing and command-tree validation.
984
- *
985
- * @param defs - Flag definitions record
986
- * @returns A new `Crust` instance with the given flags
987
- * @throws {CrustError} `DEFINITION` if flag names/aliases violate constraints
988
- */
989
- flags<const F extends FlagsDef>(defs: F & ValidateNoPrefixedFlags<ValidateFlagAliases<F>>): Crust<Inherited, F, A, EffectiveFlags<Inherited, F>>;
990
- /**
991
- * Define positional arguments for this command.
992
- *
993
- * Returns a new builder with updated args types. The original
994
- * builder is not mutated.
995
- *
996
- * @param defs - Ordered tuple of positional argument definitions
997
- * @returns A new `Crust` instance with the given args
998
- */
999
- args<const NewA extends ArgsDef>(defs: NewA & ValidateVariadicArgs<NewA>): Crust<Inherited, Local, NewA, Eff>;
1000
- /**
1001
- * Set the main command handler.
1002
- *
1003
- * The handler receives a {@link CrustCommandContext} with `args` typed from
1004
- * `.args()` and `flags` typed as `EffectiveFlags<Inherited, Local>` (inherited
1005
- * flags merged with local flags).
1006
- *
1007
- * Returns a new builder with the handler stored. The original builder is
1008
- * not mutated.
1009
- *
1010
- * @param handler - The main command handler function
1011
- * @returns A new `Crust` instance with the handler registered
1012
- */
1013
- run(handler: (ctx: NoInfer<CrustCommandContext<A, Eff>>) => void | Promise<void>): Crust<Inherited, Local, A, Eff>;
1014
- /**
1015
- * Set the pre-run lifecycle hook.
1016
- *
1017
- * Called before `run()` — useful for initialization and setup.
1018
- * Receives the same {@link CrustCommandContext} as `run()`.
1019
- *
1020
- * @param handler - The pre-run handler function
1021
- * @returns A new `Crust` instance with the preRun handler registered
1022
- */
1023
- preRun(handler: (ctx: NoInfer<CrustCommandContext<A, Eff>>) => void | Promise<void>): Crust<Inherited, Local, A, Eff>;
1024
- /**
1025
- * Set the post-run lifecycle hook.
1026
- *
1027
- * Called after `run()` (even if it throws) — useful for teardown and cleanup.
1028
- * Receives the same {@link CrustCommandContext} as `run()`.
1029
- *
1030
- * @param handler - The post-run handler function
1031
- * @returns A new `Crust` instance with the postRun handler registered
1032
- */
1033
- postRun(handler: (ctx: NoInfer<CrustCommandContext<A, Eff>>) => void | Promise<void>): Crust<Inherited, Local, A, Eff>;
1034
- /**
1035
- * Register a plugin on this command.
1036
- *
1037
- * Plugins are collected during `.execute()` and their `setup()` hooks
1038
- * receive `SetupContext` and `SetupActions`. Middleware hooks run in
1039
- * registration order.
1040
- *
1041
- * Returns a new builder with the plugin appended. The original builder
1042
- * is not mutated.
1043
- *
1044
- * @param plugin - The plugin to register
1045
- * @returns A new `Crust` instance with the plugin registered
1046
- */
1047
- use(plugin: CrustPlugin): Crust<Inherited, Local, A, Eff>;
1048
- /**
1049
- * Create a subcommand builder pre-typed with this command's inheritable flags.
1050
- *
1051
- * This is the factory method for the file-splitting pattern. The returned
1052
- * builder carries this command's effective flags (filtered for `inherit: true`)
1053
- * as its `Inherited` generic, enabling full type inference in split files
1054
- * without needing `Crust<any, any, any>`.
1055
- *
1056
- * Register the resulting builder with `.command(builder)` on the parent.
1057
- *
1058
- * @param name - Subcommand name (must be non-empty)
1059
- * @returns A new `Crust` builder pre-typed with inherited flags
1060
- * @throws {CrustError} `DEFINITION` if name is empty or whitespace-only
1061
- *
1062
- * @example
1063
- * ```ts
1064
- * // shared.ts
1065
- * const app = new Crust("my-cli")
1066
- * .flags({ verbose: { type: "boolean", inherit: true } });
1067
- *
1068
- * // commands/deploy.ts
1069
- * const deployCmd = app.sub("deploy")
1070
- * .flags({ env: { type: "string", required: true } })
1071
- * .run(({ flags }) => {
1072
- * flags.verbose; // boolean | undefined — typed!
1073
- * flags.env; // string — typed!
1074
- * });
1075
- *
1076
- * // cli.ts
1077
- * app.command(deployCmd).execute();
1078
- * ```
1079
- */
1080
- sub<N extends string>(name: N): Crust<Eff, {}, [], EffectiveFlags<Eff, {}>>;
1081
- /**
1082
- * Register a named subcommand via inline callback.
1083
- *
1084
- * The callback receives a fresh `Crust` builder pre-typed with this
1085
- * command's effective inheritable flags, enabling TypeScript contextual
1086
- * typing to flow inherited flag types into subcommand definitions.
1087
- *
1088
- * @param name - Subcommand name (must be non-empty, unique among siblings)
1089
- * @param cb - Callback that receives a child builder and returns the configured builder
1090
- * @returns A new `Crust` instance with the subcommand registered
1091
- * @throws {CrustError} `DEFINITION` if name is empty or already registered
1092
- */
1093
- command<N extends string>(name: N, cb: (cmd: Crust<Eff, {}, [], EffectiveFlags<Eff, {}>>) => Crust<any, any, any>): Crust<Inherited, Local, A, Eff>;
1094
- /**
1095
- * Register a pre-built subcommand builder.
1096
- *
1097
- * The builder's name (from its constructor or `.sub()`) is used as the
1098
- * subcommand name. Builders created with `.sub()` inherit the parent's
1099
- * `inherit: true` flags; standalone `new Crust(name)` builders remain
1100
- * isolated. This is the complement to `.sub()` for the file-splitting
1101
- * pattern.
1102
- *
1103
- * @param builder - A pre-configured `Crust` builder instance
1104
- * @returns A new `Crust` instance with the subcommand registered
1105
- * @throws {CrustError} `DEFINITION` if builder name is empty or already registered
1106
- */
1107
- command(builder: Crust<any, any, any>): Crust<Inherited, Local, A, Eff>;
1108
- /**
1109
- * Build a frozen, validated copy of the command tree after running plugin
1110
- * `setup()` hooks. Does not mutate this builder or call command handlers.
1111
- *
1112
- * Use for documentation generators (e.g. man pages) that need the same
1113
- * tree shape as runtime, including flags injected by plugins.
1114
- *
1115
- * @param options - Optional synthetic `argv` passed to `setup()` (defaults to `[]`)
1116
- * @returns The cloned root node and any plugin-setup warnings
1117
- * @throws {CrustError} When the tree fails validation (same as `execute()`)
1118
- */
1119
- prepareCommandTree(options?: {
1120
- argv?: readonly string[];
1121
- }): Promise<{
1122
- root: CommandNode;
1123
- warnings: readonly string[];
1124
- }>;
1125
- /**
1126
- * Parse `process.argv`, resolve subcommands, run plugins and middleware,
1127
- * and execute the matched command handler.
1128
- *
1129
- * This is the entry point for CLI execution — call it on the root builder.
1130
- *
1131
- * @param options - Optional overrides (e.g. custom `argv` for testing)
1132
- * @returns A promise that resolves when execution completes
1133
- */
1134
- execute(options?: {
1135
- argv?: string[];
1136
- }): Promise<void>;
1149
+ /** @internal */
1150
+ type DefiningOf<T> = T extends Defining<unknown> ? T[typeof defining] : T;
1151
+ /** Lazy, invocation-scoped Context values. Reading a property starts construction. */
1152
+ type ContextBag<Deps extends ContextMap = {}> = { readonly [K in keyof Deps]: Promise<Deps[K]>; };
1153
+ interface ContextConfig {
1154
+ readonly flags?: readonly NamedFlagDef[];
1155
+ readonly uses?: readonly AnyContextFactory[];
1137
1156
  }
1138
- interface CommandNotFoundErrorDetails {
1139
- input: string;
1140
- available: string[];
1141
- commandPath: string[];
1142
- parentCommand: CommandNode;
1157
+ type ValidateContextConfig<R extends ContextConfig> = {
1158
+ readonly flags?: R["flags"] extends readonly NamedFlagDef[] ? ValidateLocalFlagDefs<R["flags"], never> : "flags" extends keyof R ? {} : never;
1159
+ readonly uses?: R["uses"] extends readonly AnyContextFactory[] ? R["uses"] : "uses" extends keyof R ? {} : never;
1160
+ };
1161
+ interface ContextSetupInput<OF extends FlagsDef = FlagsDef> extends InvocationIO {
1162
+ readonly flags: InferFlags<OF>;
1163
+ readonly ctx: ContextBag<ContextMap>;
1164
+ readonly defer: (cleanup: () => void | PromiseLike<void>) => void;
1143
1165
  }
1144
- interface ValidationErrorDetails {
1145
- issues: readonly {
1146
- readonly message: string;
1147
- readonly path: string;
1148
- }[];
1166
+ interface ContextInstance<Name extends string = string, Value = unknown, OF extends FlagsDef = FlagsDef, Deps extends ContextMap = Record<string, ContextValue>> extends Defining<ContextInstance<Name, Value, OF, Deps>> {
1167
+ readonly [contextProof]?: string extends keyof OF | keyof Deps ? unknown : (state: [OF, Deps]) => void;
1168
+ readonly name: Name;
1169
+ readonly ownedFlags: FlagsDef;
1170
+ /** @internal — declared direct dependency factories */
1171
+ readonly uses: readonly AnyContextFactory[];
1172
+ setup(input: ContextSetupInput<OF>): Awaitable<Value>;
1173
+ readonly _ownedFlags?: OF;
1174
+ /** @internal — phantom carrying the transitive dependency closure */
1175
+ readonly _deps?: Deps;
1149
1176
  }
1150
- interface CrustErrorDetailsMap {
1151
- DEFINITION: undefined;
1152
- VALIDATION: ValidationErrorDetails | undefined;
1153
- PARSE: undefined;
1154
- EXECUTION: undefined;
1155
- COMMAND_NOT_FOUND: CommandNotFoundErrorDetails;
1156
- CONFIG: undefined;
1177
+ /** @internal Existential registry constraint; never evidence for trusted attachment. */
1178
+ type AnyContextInstance = ContextInstance<string, unknown, any, any>;
1179
+ /** Whatever a Context setup produces, erased at the runtime registry. */
1180
+ type ContextValue = Awaited<ReturnType<AnyContextInstance["setup"]>>;
1181
+ interface ContextSetup<Options, OF extends FlagsDef = {}, Deps extends ContextMap = {}> extends InvocationIO {
1182
+ readonly options: Options;
1183
+ readonly flags: InferFlags<OF>;
1184
+ readonly ctx: ContextBag<Deps>;
1185
+ /**
1186
+ * Registers cleanup on the invocation's disposal stack: callbacks run after
1187
+ * post-run hooks in reverse registration order. Throws once setup has settled.
1188
+ */
1189
+ readonly defer: (cleanup: () => void | PromiseLike<void>) => void;
1157
1190
  }
1158
- /**
1159
- * All possible error codes emitted by Crust.
1160
- *
1161
- * - `DEFINITION` — Invalid command configuration (empty name, alias collision, bad variadic position)
1162
- * - `VALIDATION` — Missing required arguments or flags
1163
- * - `PARSE` — Argv parsing failures (unknown flags, type coercion)
1164
- * - `EXECUTION` — Runtime command/middleware failures
1165
- * - `COMMAND_NOT_FOUND` — Unrecognised subcommand at the current level
1166
- * - `CONFIG` — Unsupported command/flag definition surfaced at setup time (e.g. async `parse`)
1167
- *
1168
- * @example
1169
- * ```ts
1170
- * try {
1171
- * parseArgs(cmd, argv);
1172
- * } catch (err) {
1173
- * if (err instanceof CrustError) {
1174
- * switch (err.code) {
1175
- * case "VALIDATION":
1176
- * console.error(err.message);
1177
- * showHelp(cmd);
1178
- * break;
1179
- * case "PARSE":
1180
- * console.error(err.message);
1181
- * break;
1182
- * }
1183
- * }
1184
- * }
1185
- * ```
1186
- */
1187
- type CrustErrorCode = keyof CrustErrorDetailsMap;
1188
- type CrustErrorDetails<C extends CrustErrorCode> = CrustErrorDetailsMap[C];
1189
- /**
1190
- * A typed error thrown by Crust when command definition or argument parsing fails.
1191
- *
1192
- * Every `CrustError` carries a {@link CrustErrorCode} that identifies the specific
1193
- * failure, enabling programmatic error handling without fragile message parsing.
1194
- *
1195
- * @example
1196
- * ```ts
1197
- * import { CrustError, parseArgs } from "@crustjs/core";
1198
- *
1199
- * try {
1200
- * const result = parseArgs(cmd, process.argv.slice(2));
1201
- * } catch (err) {
1202
- * if (err instanceof CrustError) {
1203
- * console.error(`[${err.code}] ${err.message}`);
1204
- * }
1205
- * }
1206
- * ```
1207
- */
1208
- declare class CrustError<C extends CrustErrorCode = CrustErrorCode> extends Error {
1209
- /** Machine-readable error code for programmatic handling */
1210
- readonly code: C;
1211
- /** Structured payload for programmatic handling */
1212
- readonly details: CrustErrorDetails<C>;
1213
- /** Optional wrapped original error/value */
1214
- cause?: unknown;
1215
- constructor(code: C, message: string, ...details: undefined extends CrustErrorDetails<C> ? [] | [CrustErrorDetails<C>] : [CrustErrorDetails<C>]);
1216
- is<T extends CrustErrorCode>(code: T): this is CrustError<T>;
1217
- withCause(cause: unknown): this;
1191
+ interface ContextFactory<Name extends string, Options, Value, OF extends FlagsDef = {}, Deps extends ContextMap = {}> extends Defining<ContextFactory<Name, Options, Value, OF, Deps>> {
1192
+ (options: Options): ContextInstance<Name, Value, OF, Deps>;
1193
+ readonly contextName: Name;
1194
+ /** @internal — declared direct dependency factories */
1195
+ readonly uses: readonly AnyContextFactory[];
1196
+ of(value: Value): ContextInstance<Name, Value, OF, {}>;
1197
+ readonly _deps?: Deps;
1218
1198
  }
1219
- /**
1220
- * Parse argv against a command's arg/flag definitions.
1221
- *
1222
- * Wraps Node's `util.parseArgs` with Crust's enhanced semantics:
1223
- * positional arg mapping, type coercion, alias expansion, default values,
1224
- * variadic args, and strict mode.
1225
- *
1226
- * This is a pure parse+coerce function — it never throws for missing required
1227
- * values. Use {@link validateParsed} to enforce required constraints after
1228
- * middleware has had a chance to intercept (e.g. `--help`).
1229
- *
1230
- * @param command - The command whose arg/flag definitions drive the parsing
1231
- * @param argv - The argv array to parse (typically `process.argv.slice(2)`)
1232
- * @returns Parsed args, flags, and rawArgs (everything after `--`)
1233
- * @throws {CrustError} On unknown flags, type coercion failure, or alias collisions
1234
- */
1235
- declare function parseArgs<
1236
- A extends ArgsDef = ArgsDef,
1237
- F extends FlagsDef = FlagsDef
1238
- >(command: CommandNode, argv: string[]): ParseResult<A, F>;
1239
- /**
1240
- * Validate a parse result against its command's required-value constraints.
1241
- *
1242
- * Separated from {@link parseArgs} so that middleware (e.g. `--help`) can
1243
- * inspect the parse result before validation errors are surfaced.
1244
- *
1245
- * @param command - The command whose definitions drive the validation
1246
- * @param parsed - The parse result from {@link parseArgs}
1247
- * @throws {CrustError} On missing required args or flags
1248
- */
1249
- declare function validateParsed(command: CommandNode, parsed: ParseResult): void;
1250
- export { validateParsed, resolveCommand, parseArgs, ValueType, ValidateVariadicArgs, ValidateNoPrefixedFlags, ValidateFlagAliases, ValidateCrossCollisions, VALIDATION_MODE_ENV, VALIDATION_FORCE_EXIT_ENV, SetupContext, SetupActions, ResolveBaseType, Resolve, PluginMiddleware, ParseResult, MiddlewareContext, MergeFlags, InheritableFlags, InferFlags, InferArgs, FlagsDef, FlagDef, EffectiveFlags, CrustPlugin, CrustErrorCode, CrustError, CrustCommandContext, Crust, CommandRoute, CommandNode, CommandMeta, ArgsDef, ArgDef };
1199
+ type AnyContextFactory = ContextFactory<string, any, any, any, any>;
1200
+ type NamedOutput<Name extends string, Value> = IsClosedName<Name> extends false ? Record<string, Awaited<Value>> : IsUnion<Name> extends true ? Record<string, Awaited<Value>> : { [K in Name]: Awaited<Value>; };
1201
+ type ContextOutput<C> = C extends AnyContextInstance ? DefiningOf<C> extends ContextInstance<infer Name, infer Value, any, any> ? NamedOutput<Name, Value> : never : never;
1202
+ type ContextsOutput<Cs extends readonly AnyContextInstance[]> = IsStaticTuple<Cs> extends true ? Cs extends readonly [infer H, ...infer T extends readonly AnyContextInstance[]] ? MergeProviders<ContextOutput<H>, ContextsOutput<T>> : {} : Cs[number] extends never ? {} : Record<string, ContextOutput<Cs[number]> extends (infer O) ? O extends unknown ? O[keyof O] : never : never>;
1203
+ type ContextsOwnedFlags<Cs extends readonly AnyContextInstance[]> = IsStaticTuple<Cs> extends true ? Cs extends readonly [infer H, ...infer T extends readonly AnyContextInstance[]] ? MergeFlags<ContextOwnedFlags<H>, ContextsOwnedFlags<T>> : {} : keyof UnionToIntersection<ContextOwnedFlags<Cs[number]>> extends never ? {} : FlagsDef;
1204
+ type FactoryOutput<F> = F extends AnyContextFactory ? DefiningOf<F> extends ContextFactory<infer Name, any, infer Value, any, any> ? NamedOutput<Name, Value> : never : never;
1205
+ type FactoriesOutput<Fs extends readonly AnyContextFactory[]> = Fs extends readonly [infer H, ...infer T extends readonly AnyContextFactory[]] ? FactoryOutput<H> & FactoriesOutput<T> : {};
1206
+ type FactoryDeps<F> = F extends AnyContextFactory ? DefiningOf<F> extends ContextFactory<any, any, any, any, infer Deps> ? Deps : {} : {};
1207
+ type FactoriesDeps<Fs extends readonly AnyContextFactory[]> = Fs extends readonly [infer H, ...infer T extends readonly AnyContextFactory[]] ? FactoryDeps<H> & FactoriesDeps<T> : {};
1208
+ type ContextDependencies<Uses extends readonly AnyContextFactory[]> = IsStaticTuple<Uses> extends true ? FactoriesOutput<Uses> & FactoriesDeps<Uses> : Record<string, ContextValue>;
1209
+ type ContextDepsOf<C> = C extends AnyContextInstance ? DefiningOf<C> extends {
1210
+ readonly _deps?: infer Deps extends ContextMap;
1211
+ } ? Deps : {} : {};
1212
+ type ContextsDependencies<Cs extends readonly AnyContextInstance[]> = Cs extends readonly [infer H, ...infer T extends readonly AnyContextInstance[]] ? ContextDepsOf<H> & ContextsDependencies<T> : {};
1213
+ type OwnedFlagsOf<R extends ContextConfig> = R extends {
1214
+ flags: infer F extends readonly NamedFlagDef[];
1215
+ } ? AttachedFlags<F> : "flags" extends keyof R ? FlagsDef : {};
1216
+ type UsesOf<R extends ContextConfig> = R extends {
1217
+ uses: infer Uses extends readonly AnyContextFactory[];
1218
+ } ? Uses : "uses" extends keyof R ? readonly AnyContextFactory[] : readonly [];
1219
+ /** Define a named, lazy command dependency. Declared `uses` are exposed on `ctx`. */
1220
+ export declare function defineContext<Name extends string, Value, Options = void>(name: Name, setup: (input: ContextSetup<Options>) => Awaitable<Value>): ContextFactory<Name, Options, Value>;
1221
+ export declare function defineContext<Name extends string, const R extends ContextConfig, Value, Options = void>(name: Name, config: R & ValidateContextConfig<R> & ContextConfig, setup: (input: ContextSetup<Options, OwnedFlagsOf<R>, ContextDependencies<UsesOf<R>>>) => Awaitable<Value>): ContextFactory<Name, Options, Value, OwnedFlagsOf<R>, ContextDependencies<UsesOf<R>>>;
1222
+ type FactoryValueOf<F extends AnyContextFactory> = F extends ContextFactory<any, any, infer Value, any, any> ? Awaited<Value> : never;
1223
+ //#endregion
1224
+ //#region src/api/flags.d.ts
1225
+ /** Distribute `Omit<_, "name">` over the {@link ArgDef} union. */
1226
+ type OmitName<T> = T extends {
1227
+ name: string;
1228
+ } ? Omit<T, "name"> : never;
1229
+ /** A positional argument definition without its name — the `defineArg` input shape. */
1230
+ type UnnamedArgDef = OmitName<ArgDef>;
1231
+ type Frozen<T> = { readonly [K in keyof T]: K extends "aliases" | "choices" | (T extends {
1232
+ multiple: true;
1233
+ } ? "default" : never) ? Readonly<T[K]> : T[K]; };
1234
+ type Named<N extends string, D> = D extends unknown ? Frozen<{
1235
+ name: N;
1236
+ } & D> : never;
1237
+ /** Define and own one flag locally; attachment checks destination collisions. */
1238
+ export declare function defineFlag<const N extends string, const D extends FlagDef>(name: N & LocalFlagNameBrand<N>, def: D & LocalFlagBrand<{
1239
+ name: N;
1240
+ } & D>): Named<N, D>;
1241
+ /** Define and own one positional argument; layout belongs to its consuming command. */
1242
+ export declare function defineArg<const N extends string, const D extends UnnamedArgDef>(name: N & EmptyArgNameBrand<N>, def: D & LocalValueBrand<D>): Named<N, D>;
1243
+ //#endregion
1244
+ export { type AnyContextFactory, type AnyCrust, type ArgDef, type ArgSnapshot, type ArgsDef, type BuildArtifacts, type BuildReport, type CommandConfig, type CommandDefinition, type CommandDefinitionBuilder, type CommandMeta, type CommandNotFoundErrorDetails, type CommandPath, type CommandSection, type CommandSectionInput, type CommandShape, type CommandShapeAt, type CommandSnapshot, type CommandTree, type ContextBag, type ContextConfig, type ContextFactory, type ContextInstance, type ContextMap, type ContextSetup, type CrustCommandContext, type CrustErrorCode, type CrustErrorDetails, type CrustErrorDetailsMap, type CrustErrorJson, type DefineExtensionWith, type DefinitionErrorDetails, type Extension, type ExtensionBuildContext, type ExtensionConfig, type ExtensionContext, type ExtensionFactory, type ExtensionFlagDef, type ExtensionHooks, type ExtensionId, type ExtensionSectionContribution, type FactoryValueOf, type Finished, type FlagDef, type FlagSnapshot, type FlagsDef, type InferExtensionFlags, type InputArgs, type InputFlags, type InvocationIO, type InvocationOutcome, type MergeContext, type MergeFlags, type NamedExtensionFlagDef, type NamedFlagDef, type ParseErrorDetails, type ParseResult, type ParsedArgValue, type ParsedFlagValue, type RootCommandMeta, type RootMetaKey, type RunArguments, type RunInput, type RunInputArguments, type RunOutcome, type SectionAudience, type SectionConsumer, type UnnamedArgDef, type ValidatedInput, type ValidationErrorDetails, type ValueType, defineExtensionId };