@lovrabet/cli-framework 0.1.1-beta.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.
@@ -0,0 +1,176 @@
1
+ /**
2
+ * @fileoverview Schema serialization for machine-readable CLI metadata.
3
+ *
4
+ * Produces a structured JSON payload describing every command, service,
5
+ * flag, and argument registered in the CLI. This payload is used by:
6
+ * - The `--schema` / `--json-schema` introspection flag.
7
+ * - Language-server / IDE integrations that need to enumerate available
8
+ * commands and their signatures.
9
+ * - Documentation generators and code-skeleton scaffolding tools.
10
+ *
11
+ * Serialized types drop TypeScript-specific constructs (e.g. `RegExp` objects
12
+ * become `{ source, flags, description }`) for broad compatibility.
13
+ */
14
+ import type { CommandDefinition, FlagDef } from "./types.js";
15
+ /**
16
+ * A serialized regex pattern from a {@link FlagDef.pattern}.
17
+ * RegExp objects cannot be reliably serialized to JSON, so the source
18
+ * string, flags, and human-readable description are preserved separately.
19
+ */
20
+ export interface SerializedFlagPattern {
21
+ /** The regex source string (e.g. `"^[a-z]{3,8}$"`). */
22
+ source: string;
23
+ /** The regex flags string (e.g. `"i"` or `""`). */
24
+ flags: string;
25
+ /** Human-readable description of the expected format. */
26
+ description: string;
27
+ }
28
+ /**
29
+ * A serialized {@link FlagDef} with RegExp replaced by {@link SerializedFlagPattern}.
30
+ */
31
+ export interface SerializedFlag {
32
+ name: string;
33
+ type: "string" | "boolean" | "number";
34
+ description: string;
35
+ required?: boolean;
36
+ default?: string | boolean | number;
37
+ enum?: string[];
38
+ pattern?: SerializedFlagPattern;
39
+ alias?: string;
40
+ hidden?: boolean;
41
+ }
42
+ /**
43
+ * A serialized {@link CommandDefinition} describing one subcommand.
44
+ */
45
+ export interface SchemaCommandEntry {
46
+ command: string;
47
+ description: string;
48
+ /** Optional tag shown in help listings. */
49
+ tag?: string;
50
+ risk: string;
51
+ requiresAuth: boolean;
52
+ requiresAppCode: boolean;
53
+ hasFormat: boolean;
54
+ supportsDryRun: boolean;
55
+ args?: Array<{
56
+ name: string;
57
+ description: string;
58
+ required?: boolean;
59
+ }>;
60
+ flags: SerializedFlag[];
61
+ /**
62
+ * Static help extra string, or `{ dynamic: true }` when the command
63
+ * uses a thunk so the content cannot be serialized statically.
64
+ */
65
+ helpExtra?: string | {
66
+ dynamic: true;
67
+ };
68
+ }
69
+ /**
70
+ * A serialized service containing its command list.
71
+ */
72
+ export interface SchemaServiceEntry {
73
+ service: string;
74
+ label: string;
75
+ isSingleCommand?: boolean;
76
+ defaultCommand?: string;
77
+ /** `true` when the service has a wildcard (`<script>`) command. */
78
+ hasWildcard: boolean;
79
+ commands: SchemaCommandEntry[];
80
+ }
81
+ /**
82
+ * The root payload of the schema export.
83
+ * Includes CLI identity metadata, global flags, and all services.
84
+ */
85
+ export interface SchemaPayload {
86
+ /** Schema format version; increment when the shape changes. */
87
+ schemaVersion: number;
88
+ cli: {
89
+ bin: string;
90
+ displayName: string;
91
+ version: string;
92
+ gitCommit: string;
93
+ };
94
+ /** Global flags that apply to all commands. */
95
+ globalFlags: Array<{
96
+ name: string;
97
+ type: "string" | "boolean";
98
+ description: string;
99
+ hint?: string;
100
+ hidden?: boolean;
101
+ }>;
102
+ services: SchemaServiceEntry[];
103
+ }
104
+ /**
105
+ * A stripped-down command listing used for schema generation.
106
+ * Contains only the metadata needed to render command summaries.
107
+ */
108
+ export interface SchemaCommandListing {
109
+ command: string;
110
+ description: string;
111
+ tag?: string;
112
+ }
113
+ /**
114
+ * A stripped-down service entry used for schema generation.
115
+ */
116
+ export interface SchemaServiceListing {
117
+ service: string;
118
+ label: string;
119
+ isSingleCommand?: boolean;
120
+ defaultCommand?: string;
121
+ wildcardDef?: CommandDefinition;
122
+ commands: SchemaCommandListing[];
123
+ }
124
+ /** Options for {@link buildSchemaPayload}. */
125
+ export interface BuildSchemaOptions {
126
+ /** CLI binary name. */
127
+ cliBinName: string;
128
+ /** CLI display name. */
129
+ cliDisplayName: string;
130
+ /** Current CLI version string. */
131
+ cliVersion: string;
132
+ /** Current git commit SHA. */
133
+ gitCommit: string;
134
+ /** Global flag definitions. */
135
+ globalFlags: Array<{
136
+ name: string;
137
+ type: "string" | "boolean";
138
+ description: string;
139
+ hint?: string;
140
+ hidden?: boolean;
141
+ }>;
142
+ /**
143
+ * Simplified service registry (without full `CommandDefinition` objects)
144
+ * used to enumerate services and commands.
145
+ */
146
+ serviceRegistry: SchemaServiceListing[];
147
+ /**
148
+ * Resolves the effective flag list for a command.
149
+ * Must include built-in flags (from {@link buildAllFlags}).
150
+ */
151
+ buildAllFlags(def: CommandDefinition): FlagDef[];
152
+ /**
153
+ * Looks up a full {@link CommandDefinition} by service and command name.
154
+ */
155
+ findDefinition(service: string, command: string): CommandDefinition | undefined;
156
+ }
157
+ /**
158
+ * Builds the complete schema payload for the CLI.
159
+ *
160
+ * Iterates over every service and command in `options.serviceRegistry`,
161
+ * resolves the full command definition, and serializes flags (with regex
162
+ * patterns converted to plain objects).
163
+ *
164
+ * @param options - Schema build options.
165
+ * @returns A complete {@link SchemaPayload} ready for JSON serialization.
166
+ *
167
+ * @example
168
+ * ```ts
169
+ * const schema = buildSchemaPayload({
170
+ * cliBinName: "rabetbase",
171
+ * // ... other options from the registry
172
+ * });
173
+ * process.stdout.write(JSON.stringify(schema, null, 2));
174
+ * ```
175
+ */
176
+ export declare function buildSchemaPayload(options: BuildSchemaOptions): SchemaPayload;
@@ -0,0 +1,148 @@
1
+ /**
2
+ * @fileoverview Schema serialization for machine-readable CLI metadata.
3
+ *
4
+ * Produces a structured JSON payload describing every command, service,
5
+ * flag, and argument registered in the CLI. This payload is used by:
6
+ * - The `--schema` / `--json-schema` introspection flag.
7
+ * - Language-server / IDE integrations that need to enumerate available
8
+ * commands and their signatures.
9
+ * - Documentation generators and code-skeleton scaffolding tools.
10
+ *
11
+ * Serialized types drop TypeScript-specific constructs (e.g. `RegExp` objects
12
+ * become `{ source, flags, description }`) for broad compatibility.
13
+ */
14
+ /**
15
+ * Builds the complete schema payload for the CLI.
16
+ *
17
+ * Iterates over every service and command in `options.serviceRegistry`,
18
+ * resolves the full command definition, and serializes flags (with regex
19
+ * patterns converted to plain objects).
20
+ *
21
+ * @param options - Schema build options.
22
+ * @returns A complete {@link SchemaPayload} ready for JSON serialization.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const schema = buildSchemaPayload({
27
+ * cliBinName: "rabetbase",
28
+ * // ... other options from the registry
29
+ * });
30
+ * process.stdout.write(JSON.stringify(schema, null, 2));
31
+ * ```
32
+ */
33
+ export function buildSchemaPayload(options) {
34
+ const services = [];
35
+ for (const entry of options.serviceRegistry) {
36
+ const commands = [];
37
+ for (const c of entry.commands) {
38
+ const def = c.command === "<script>" && entry.wildcardDef
39
+ ? entry.wildcardDef
40
+ : options.findDefinition(entry.service, c.command);
41
+ if (!def)
42
+ continue;
43
+ commands.push(serializeCommand(def, c, options.buildAllFlags));
44
+ }
45
+ services.push({
46
+ service: entry.service,
47
+ label: entry.label,
48
+ ...(entry.isSingleCommand ? { isSingleCommand: true } : {}),
49
+ ...(entry.defaultCommand ? { defaultCommand: entry.defaultCommand } : {}),
50
+ hasWildcard: Boolean(entry.wildcardDef),
51
+ commands,
52
+ });
53
+ }
54
+ return {
55
+ schemaVersion: 1,
56
+ cli: {
57
+ bin: options.cliBinName,
58
+ displayName: options.cliDisplayName,
59
+ version: options.cliVersion,
60
+ gitCommit: options.gitCommit,
61
+ },
62
+ globalFlags: options.globalFlags.map((f) => ({
63
+ name: f.name,
64
+ type: f.type,
65
+ description: f.description,
66
+ ...(f.hint ? { hint: f.hint } : {}),
67
+ ...(f.hidden ? { hidden: true } : {}),
68
+ })),
69
+ services,
70
+ };
71
+ }
72
+ /**
73
+ * Serializes a single command definition into a {@link SchemaCommandEntry}.
74
+ *
75
+ * @param def - Command definition to serialize.
76
+ * @param listing - Stripped listing from the registry.
77
+ * @param buildAllFlagsFn - Function to build the effective flag list.
78
+ * @returns Serialized command entry.
79
+ */
80
+ function serializeCommand(def, listing, buildAllFlagsFn) {
81
+ const requiresAuth = def.requiresAuth !== false;
82
+ const requiresAppCode = def.requiresAppCode !== false;
83
+ const hasFormat = def.hasFormat !== false;
84
+ const helpExtra = serializeHelpExtra(def);
85
+ return {
86
+ command: listing.command,
87
+ description: listing.description,
88
+ ...(listing.tag ? { tag: listing.tag.trim() } : {}),
89
+ risk: def.risk,
90
+ requiresAuth,
91
+ requiresAppCode,
92
+ hasFormat,
93
+ supportsDryRun: Boolean(def.dryRun),
94
+ ...(def.args && def.args.length > 0 ? { args: def.args } : {}),
95
+ flags: buildAllFlagsFn(def).map(serializeFlag),
96
+ ...(helpExtra !== undefined ? { helpExtra } : {}),
97
+ };
98
+ }
99
+ /**
100
+ * Serializes the `helpExtra` field of a command.
101
+ *
102
+ * - `undefined` → `undefined`
103
+ * - Static string → the string
104
+ * - Thunk → `{ dynamic: true }` (cannot be evaluated without running the code)
105
+ *
106
+ * @param def - Command definition.
107
+ * @returns Serialized help extra value.
108
+ */
109
+ function serializeHelpExtra(def) {
110
+ if (def.helpExtra === undefined)
111
+ return undefined;
112
+ if (typeof def.helpExtra === "function")
113
+ return { dynamic: true };
114
+ return def.helpExtra;
115
+ }
116
+ /**
117
+ * Serializes a single {@link FlagDef} into a {@link SerializedFlag}.
118
+ * Converts the `pattern.regex` RegExp into a plain `{ source, flags, description }`
119
+ * object so the result is safe to serialize to JSON.
120
+ *
121
+ * @param f - Flag definition to serialize.
122
+ * @returns Serialized flag.
123
+ */
124
+ function serializeFlag(f) {
125
+ const out = {
126
+ name: f.name,
127
+ type: f.type,
128
+ description: f.description,
129
+ };
130
+ if (f.required !== undefined)
131
+ out.required = f.required;
132
+ if (f.default !== undefined)
133
+ out.default = f.default;
134
+ if (f.enum && f.enum.length > 0)
135
+ out.enum = f.enum;
136
+ if (f.alias)
137
+ out.alias = f.alias;
138
+ if (f.hidden)
139
+ out.hidden = f.hidden;
140
+ if (f.pattern) {
141
+ out.pattern = {
142
+ source: f.pattern.regex.source,
143
+ flags: f.pattern.regex.flags,
144
+ description: f.pattern.description,
145
+ };
146
+ }
147
+ return out;
148
+ }
@@ -0,0 +1,338 @@
1
+ /**
2
+ * @fileoverview Core domain types for the CLI framework.
3
+ *
4
+ * Defines the primitive risk levels, output formats, and the central
5
+ * {@link CommandDefinition} interface that every declarative command must implement.
6
+ */
7
+ /**
8
+ * Risk level of a command, used for policy enforcement and user confirmation.
9
+ *
10
+ * - `read` – read-only operations; no confirmation required.
11
+ * - `write` – modifies data; may require confirmation in some contexts.
12
+ * - `high-risk-write` – destructive or irreversible operations; always requires
13
+ * either `--yes` or an interactive `--yes/-y` confirmation prompt.
14
+ */
15
+ export type Risk = "read" | "write" | "high-risk-write";
16
+ /**
17
+ * Returns a numeric ordering value for a given risk level.
18
+ * Higher numbers indicate greater risk. Used by {@link assertRiskWithinLevel}
19
+ * to compare the command's risk against the configured risk ceiling.
20
+ *
21
+ * @param risk - A `Risk` value, an arbitrary string, or `undefined`.
22
+ * @returns `0` for `read`, `1` for `write`, `2` for `high-risk-write`, or `0` as fallback.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * riskLevelOrder("high-risk-write") > riskLevelOrder("read") // true
27
+ * ```
28
+ */
29
+ export declare function riskLevelOrder(risk: Risk | string | undefined): number;
30
+ /**
31
+ * Supported CLI output formats.
32
+ *
33
+ * - `json` – Full JSON object with indentation (for scripting / debugging).
34
+ * - `pretty` – Human-readable plain-text layout (for interactive use).
35
+ * - `compress` – Single-line JSON without indentation (default; for piping).
36
+ */
37
+ export type OutputFormat = "json" | "pretty" | "compress";
38
+ /**
39
+ * Type guard that checks whether a value is a valid {@link OutputFormat}.
40
+ *
41
+ * @param v - Any value to test.
42
+ * @returns `true` if `v` is `"json"`, `"pretty"`, or `"compress"`.
43
+ */
44
+ export declare function isValidFormat(v: unknown): v is OutputFormat;
45
+ /**
46
+ * Converts legacy or alternative format aliases to the canonical {@link OutputFormat}.
47
+ * The legacy alias `"table"` maps to `"pretty"`.
48
+ *
49
+ * @param v - Any value to normalize.
50
+ * @returns The canonical `OutputFormat`, or `undefined` if `v` cannot be mapped.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * normalizeLegacyOutputFormat("table") // "pretty"
55
+ * normalizeLegacyOutputFormat("compress") // "compress"
56
+ * normalizeLegacyOutputFormat(123) // undefined
57
+ * ```
58
+ */
59
+ export declare function normalizeLegacyOutputFormat(v: unknown): OutputFormat | undefined;
60
+ /**
61
+ * Defines a positional argument accepted by a command.
62
+ */
63
+ export interface ArgDef {
64
+ /**
65
+ * Argument name shown in usage strings as `<name>`.
66
+ */
67
+ name: string;
68
+ /**
69
+ * Human-readable description shown in help output.
70
+ */
71
+ description: string;
72
+ /**
73
+ * When `true`, the argument is mandatory.
74
+ * @default true
75
+ */
76
+ required?: boolean;
77
+ }
78
+ /**
79
+ * Defines a named flag (CLI option) accepted by a command.
80
+ */
81
+ export interface FlagDef {
82
+ /**
83
+ * Flag name, used as `--name`. Hyphens are converted internally (e.g. `--app-code`).
84
+ */
85
+ name: string;
86
+ /** Value type of the flag. */
87
+ type: "string" | "boolean" | "number";
88
+ /** Human-readable description shown in help output. */
89
+ description: string;
90
+ /**
91
+ * When `true`, the flag must be provided.
92
+ * @default false
93
+ */
94
+ required?: boolean;
95
+ /**
96
+ * Default value applied when the flag is absent from the command line.
97
+ */
98
+ default?: string | boolean | number;
99
+ /**
100
+ * Permitted values for a string flag. Rejects any value not in this list.
101
+ */
102
+ enum?: string[];
103
+ /**
104
+ * A regex constraint with a human-readable description.
105
+ * Applied after parsing; throws a validation error if the value does not match.
106
+ */
107
+ pattern?: {
108
+ regex: RegExp;
109
+ description: string;
110
+ };
111
+ /**
112
+ * Single-character shorthand (e.g. `-f`). Short names may not be mixed-case.
113
+ */
114
+ alias?: string;
115
+ /**
116
+ * When `true`, the flag is excluded from generated help text.
117
+ */
118
+ hidden?: boolean;
119
+ }
120
+ /**
121
+ * Return type produced by every command's `execute` function.
122
+ * The result is passed to {@link formatOutput} for rendering.
123
+ */
124
+ export interface CommandResult<T = any> {
125
+ /** Indicates whether the command succeeded. */
126
+ ok: boolean;
127
+ /**
128
+ * Structured command payload. `undefined` is valid when `ok === false`.
129
+ */
130
+ data?: T;
131
+ /**
132
+ * Human-readable message, typically shown on failure or as a fallback
133
+ * when `data` is empty in `pretty` mode.
134
+ */
135
+ message?: string;
136
+ }
137
+ /**
138
+ * Preview payload returned by a command's `dryRun` hook.
139
+ * Describes what the command *would* do without actually performing it.
140
+ */
141
+ export interface DryRunResult {
142
+ /** HTTP method that would be used (e.g. "GET", "POST"). */
143
+ method: string;
144
+ /** Full URL that would be requested. */
145
+ url: string;
146
+ /**
147
+ * Request body that would be sent, if any.
148
+ */
149
+ body?: Record<string, any> | unknown[];
150
+ /**
151
+ * Optional free-text description of the operation (shown in `--help`).
152
+ */
153
+ description?: string;
154
+ }
155
+ /**
156
+ * Wraps a {@link CommandResult} with command metadata injected by the framework.
157
+ * This is the shape written to stdout in `json` / `compress` output modes.
158
+ */
159
+ export interface OutputEnvelope<T = any> {
160
+ /** Mirrors `CommandResult.ok`. */
161
+ ok: boolean;
162
+ /** Full command label used to invoke the command (e.g. `"api list"`). */
163
+ command: string;
164
+ /** Risk level of the executed command. */
165
+ risk: Risk;
166
+ /**
167
+ * Present when the command returned data.
168
+ */
169
+ data?: T;
170
+ /**
171
+ * Present only in dry-run mode (`--dry-run`).
172
+ */
173
+ dryRun?: boolean;
174
+ /**
175
+ * Present only when `ok === false`.
176
+ */
177
+ error?: {
178
+ /** Fixed error code emitted alongside the message. */
179
+ code: string;
180
+ /** Human-readable error message. */
181
+ message: string;
182
+ /** Optional actionable hint. */
183
+ hint?: string;
184
+ };
185
+ }
186
+ /**
187
+ * Base interface mixed into every runtime context object passed to commands.
188
+ * Provides uniform access to parsed flags, positional arguments, and the
189
+ * `output()` helper that renders a {@link CommandResult} through the formatter.
190
+ */
191
+ export interface RuntimeContextBase {
192
+ /**
193
+ * The raw parsed flags map — key is the flag name (kebab or camelCase)
194
+ * and value is the coerced typed value.
195
+ */
196
+ rawFlags: Record<string, any>;
197
+ /** Effective output format resolved from flag → config → definition default. */
198
+ format: OutputFormat;
199
+ /**
200
+ * `true` when the session is non-interactive (stdin is not a TTY or
201
+ * `--yes` / `-y` was supplied).
202
+ */
203
+ nonInteractive: boolean;
204
+ /**
205
+ * Positional arguments after the command name, in order.
206
+ */
207
+ args: string[];
208
+ /**
209
+ * Retrieves a positional argument by its name (from {@link ArgDef.name}) or
210
+ * by its zero-based index.
211
+ *
212
+ * @param nameOrIndex - Argument name or numeric position.
213
+ * @param defaultVal - Value returned when the argument is absent.
214
+ * @returns The argument value, `defaultVal`, or `""` if neither is available.
215
+ */
216
+ arg(nameOrIndex: string | number, defaultVal?: string): string;
217
+ /**
218
+ * Retrieves a string flag value, returning `""` when absent.
219
+ *
220
+ * @param name - Flag name.
221
+ */
222
+ str(name: string): string;
223
+ /**
224
+ * Retrieves a boolean flag value. Falls back to the boolean defaults map.
225
+ *
226
+ * @param name - Flag name.
227
+ */
228
+ bool(name: string): boolean;
229
+ /**
230
+ * Retrieves a numeric flag value. Falls back to the number defaults map or
231
+ * `defaultVal`.
232
+ *
233
+ * @param name - Flag name.
234
+ * @param defaultVal - Fallback value when the flag is absent.
235
+ */
236
+ num(name: string, defaultVal?: number): number;
237
+ /**
238
+ * Reads a flag by name, returning the raw coerced value.
239
+ *
240
+ * @template T - Expected value type.
241
+ * @param name - Flag name.
242
+ */
243
+ flag<T extends string | boolean | number>(name: string): T;
244
+ /**
245
+ * Formats and prints a {@link CommandResult} using the configured format.
246
+ * Preferred over calling `console.log` directly so output respects the
247
+ * `--format` / `--jq` pipeline settings.
248
+ *
249
+ * @param result - Command result to render.
250
+ */
251
+ output<T>(result: CommandResult<T>): void;
252
+ }
253
+ /**
254
+ * Intersection of the base context with caller-supplied extra properties.
255
+ * The `extras` type parameter carries app-code, session cookie, and other
256
+ * runtime dependencies into the command's closure.
257
+ *
258
+ * @template Extras - Object shape merged on top of {@link RuntimeContextBase}.
259
+ */
260
+ export type RuntimeContext<Extras extends object = {}> = RuntimeContextBase & Extras;
261
+ /**
262
+ * Declarative command descriptor consumed by the framework runner.
263
+ *
264
+ * All declared commands are registered in `registry.ts` and executed through
265
+ * {@link runCommandWithAdapter} via a {@link RunnerAdapter} implementation.
266
+ *
267
+ * @template Ctx - Concrete context type, defaults to the untyped base.
268
+ */
269
+ export interface CommandDefinition<Ctx extends RuntimeContext = RuntimeContext> {
270
+ /** Service namespace (e.g. `"api"`, `"dataset"`). */
271
+ service: string;
272
+ /** Subcommand name within the service (e.g. `"list"`, `"create"`). */
273
+ command: string;
274
+ /** One-line description shown in help listings. */
275
+ description: string;
276
+ /** Risk level governing confirmation and policy checks. */
277
+ risk: Risk;
278
+ /**
279
+ * When `false`, the command skips auth checks entirely.
280
+ * @default true
281
+ */
282
+ requiresAuth?: boolean;
283
+ /**
284
+ * When `false`, the command does not require an app-code context.
285
+ * @default true
286
+ */
287
+ requiresAppCode?: boolean;
288
+ /**
289
+ * When `true`, the command requires an active session cookie in addition to
290
+ * any API key credentials.
291
+ * @default false
292
+ */
293
+ requiresCookieAuth?: boolean;
294
+ /**
295
+ * When `false`, the `--format` flag is suppressed from this command.
296
+ * @default true
297
+ */
298
+ hasFormat?: boolean;
299
+ /**
300
+ * Default `--format` value specific to this command, used only when neither
301
+ * the CLI flag nor the config default is set.
302
+ */
303
+ defaultOutputFormat?: OutputFormat;
304
+ /** Ordered list of positional argument definitions. */
305
+ flags: FlagDef[];
306
+ /** Ordered list of positional argument definitions. */
307
+ args?: ArgDef[];
308
+ /**
309
+ * Extra text appended below the standard help block for this command.
310
+ * Can be a static string or a thunk for lazy / dynamic content.
311
+ */
312
+ helpExtra?: string | (() => string);
313
+ /**
314
+ * Business-level validation hook called after flag parsing but before
315
+ * execution. Throw a {@link CliError} (via the error factory) to halt
316
+ * with a formatted error.
317
+ *
318
+ * @param ctx - Fully constructed runtime context.
319
+ * @throws Any {@link CliError} to abort; return normally to proceed.
320
+ */
321
+ validate?: (ctx: Ctx) => Promise<void>;
322
+ /**
323
+ * Dry-run hook. When present, `--dry-run` is automatically added to the
324
+ * flag list and the hook is called instead of `execute`.
325
+ *
326
+ * @param ctx - Fully constructed runtime context.
327
+ * @returns A preview describing what would happen.
328
+ */
329
+ dryRun?: (ctx: Ctx) => Promise<DryRunResult>;
330
+ /**
331
+ * Command implementation. Return a {@link CommandResult}.
332
+ * Throw a {@link CliError} (via the error factory) on any failure.
333
+ *
334
+ * @param ctx - Fully constructed runtime context.
335
+ * @returns The command result, rendered by {@link formatOutput}.
336
+ */
337
+ execute: (ctx: Ctx) => Promise<CommandResult>;
338
+ }