cli-kiss 0.2.3 → 0.2.4

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.
@@ -14,9 +14,7 @@ export default defineConfig({
14
14
  ],
15
15
  ],
16
16
  themeConfig: {
17
- search: {
18
- provider: "local",
19
- },
17
+ search: { provider: "local", options: { detailedView: true } },
20
18
  nav: [
21
19
  { text: "Guide", link: "/guide/01_getting_started" },
22
20
  { text: "npm", link: "https://www.npmjs.com/package/cli-kiss" },
@@ -0,0 +1,4 @@
1
+ import DefaultTheme from "vitepress/theme";
2
+ import "./style.css";
3
+
4
+ export default DefaultTheme;
@@ -0,0 +1,4 @@
1
+ :root {
2
+ --vp-home-hero-image-background-image: linear-gradient( -50deg, #ff003cff 0%, #00000000 50%, #459900ff 100% );
3
+ --vp-home-hero-image-filter: blur(150px);
4
+ }
@@ -16,37 +16,13 @@ const greet = command(
16
16
  options: {},
17
17
  positionals: [positionalRequired({ type: typeString, label: "NAME" })],
18
18
  },
19
- async (_ctx, { positionals: [name] }) => {
19
+ async function (_ctx, { positionals: [name] }) {
20
20
  console.log(`Hello, ${name}!`);
21
21
  },
22
22
  ),
23
23
  );
24
24
  ```
25
25
 
26
- ### `CommandInformation`
27
-
28
- Every command accepts a metadata object:
29
-
30
- | Field | Type | Description |
31
- | ------------- | ----------- | ------------------------------------------------- |
32
- | `description` | `string` | Short description shown in help output |
33
- | `hint` | `string?` | Note shown in parentheses next to the description |
34
- | `details` | `string[]?` | Extra lines printed below the description |
35
-
36
- ```ts
37
- command(
38
- {
39
- description: "Deploy the application",
40
- hint: "experimental",
41
- details: [
42
- "Pushes to the configured remote.",
43
- "Runs migrations after push.",
44
- ],
45
- },
46
- deployOperation,
47
- );
48
- ```
49
-
50
26
  ## `commandWithSubcommands` — dispatch to a subcommand
51
27
 
52
28
  User must pick one of several sub-actions.
@@ -63,19 +39,19 @@ const rootCmd = commandWithSubcommands(
63
39
  { description: "My deployment CLI" },
64
40
  // This operation runs before the subcommand is selected.
65
41
  // Its return value becomes the subcommand's context.
66
- operation({ options: {}, positionals: [] }, async (_ctx) => ({
67
- db: "postgres://localhost/mydb",
68
- })),
42
+ operation({ options: {}, positionals: [] }, async function (_ctx) {
43
+ return { db: "postgres://localhost/mydb" };
44
+ }),
69
45
  {
70
46
  deploy: command(
71
47
  { description: "Deploy the latest build" },
72
- operation({ options: {}, positionals: [] }, async (ctx) => {
48
+ operation({ options: {}, positionals: [] }, async function (ctx) {
73
49
  console.log(`Deploying with DB: ${ctx.db}`);
74
50
  }),
75
51
  ),
76
52
  rollback: command(
77
53
  { description: "Rollback to the previous release" },
78
- operation({ options: {}, positionals: [] }, async (ctx) => {
54
+ operation({ options: {}, positionals: [] }, async function (ctx) {
79
55
  console.log(`Rolling back, DB: ${ctx.db}`);
80
56
  }),
81
57
  ),
@@ -128,7 +104,7 @@ const authenticatedDeploy = commandChained(
128
104
  long: "token",
129
105
  type: typeString,
130
106
  description: "API token",
131
- default: () => {
107
+ default: function () {
132
108
  const t = process.env.API_TOKEN;
133
109
  if (!t) throw new Error("API_TOKEN env var is required");
134
110
  return t;
@@ -150,3 +126,63 @@ const authenticatedDeploy = commandChained(
150
126
  ```
151
127
 
152
128
  All stages share a single flat usage — users see one combined command.
129
+
130
+ ## `CommandInformation`
131
+
132
+ Every command accepts a metadata object:
133
+
134
+ | Field | Type | Description |
135
+ | ------------- | ------------ | ------------------------------------------------- |
136
+ | `description` | `string` | Short description shown in help output |
137
+ | `hint` | `string?` | Note shown in parentheses next to the description |
138
+ | `details` | `string[]?` | Extra lines printed below the description |
139
+ | `examples` | `Example[]?` | Usage examples shown in the `Examples:` section |
140
+
141
+ Each `Example` entry has:
142
+
143
+ | Field | Type | Description |
144
+ | ------------- | -------------- | ------------------------------------------------------- |
145
+ | `explanation` | `string` | Comment line shown above the example command |
146
+ | `commandArgs` | `CommandArg[]` | Ordered list of arguments to render on the command line |
147
+
148
+ Each `CommandArg` is one of:
149
+
150
+ | Shape | Renders as |
151
+ | ----------------------------------------------- | ---------------- |
152
+ | `string` | literal text |
153
+ | `{ positional: string }` | positional label |
154
+ | `{ subcommand: string }` | subcommand name |
155
+ | `{ option: { long: string; value?: string } }` | `--long[=value]` |
156
+ | `{ option: { short: string; value?: string } }` | `-s[=value]` |
157
+
158
+ ```ts
159
+ command(
160
+ {
161
+ description: "Deploy the application",
162
+ hint: "experimental",
163
+ details: [
164
+ "Pushes to the configured remote.",
165
+ "Runs migrations after push.",
166
+ ],
167
+ examples: [
168
+ {
169
+ explanation: "Deploy with a specific tag",
170
+ commandArgs: [
171
+ { positional: "v1.2.3" },
172
+ { option: { long: "dry-run" } },
173
+ ],
174
+ },
175
+ ],
176
+ },
177
+ deployOperation,
178
+ );
179
+ ```
180
+
181
+ The `Examples:` section in `--help` output renders each entry as a comment
182
+ followed by the reconstructed command line:
183
+
184
+ ```text
185
+ Examples:
186
+ # Deploy with a specific tag
187
+ deploy v1.2.3 --dry-run
188
+ ```
@@ -1,10 +1,11 @@
1
1
  # Options
2
2
 
3
- Named `--` arguments (or `-` for short forms). Declared in the `options` map of [`operation`](/guide/02_commands).
3
+ Named `--` arguments (or `-` for short forms). Declared in the `options` map of
4
+ [`operation`](/guide/02_commands).
4
5
 
5
6
  ## `optionFlag` — boolean toggle
6
7
 
7
- Present or absent. Also accepts `--flag=yes` / `--flag=no`.
8
+ Present or absent. Also accepts `--flag=true` / `--flag=no` / `--no-flag`.
8
9
 
9
10
  ```ts
10
11
  import { optionFlag } from "cli-kiss";
@@ -20,14 +21,14 @@ const verbose = optionFlag({
20
21
  // (absent) → false
21
22
  ```
22
23
 
23
- | Parameter | Type | Description |
24
- | ------------- | --------------------- | -------------------------------------------- |
25
- | `long` | `Lowercase<string>` | Long flag name (without `--`) |
26
- | `short` | `string?` | Short flag name (without `-`) |
27
- | `description` | `string?` | Help text |
28
- | `hint` | `string?` | Short note in parentheses |
29
- | `default` | `() => boolean` | Default when absent (default: `() => false`) |
30
- | `aliases` | `{ longs?, shorts? }` | Additional names for the flag |
24
+ | Parameter | Type | Description |
25
+ | ------------- | --------------------- | ------------------------------------ |
26
+ | `long` | `Lowercase<string>` | Long flag name (without `--`) |
27
+ | `short` | `string?` | Short flag name (without `-`) |
28
+ | `description` | `string?` | Help text |
29
+ | `hint` | `string?` | Short note in parentheses |
30
+ | `default` | `boolean? ` | Value when absent (default: `false`) |
31
+ | `aliases` | `{ longs?, shorts? }` | Additional names for the flag |
31
32
 
32
33
  ::: tip A flag specified more than once triggers a parse error. Use
33
34
  [`optionRepeatable`](#optionrepeatable-collect-multiple-values) if you need
@@ -7,7 +7,7 @@ A `Type<Value>` converts a raw CLI string into a typed value: a `content` label
7
7
  | Export | TypeScript type | Accepts |
8
8
  | ------------- | --------------- | ---------------------------------------------------------- |
9
9
  | `typeString` | `string` | Any string |
10
- | `typeBoolean` | `boolean` | `true`, `yes`, `false`, `no` (case-insensitive) |
10
+ | `typeBoolean` | `boolean` | `true/yes/on/1/y/t` → true, `false/no/off/0/n/f` → false (case-insensitive) |
11
11
  | `typeNumber` | `number` | Integers, floats, scientific notation |
12
12
  | `typeInteger` | `bigint` | Integer strings only |
13
13
  | `typeDate` | `Date` | Any format accepted by `Date.parse` (ISO 8601 recommended) |
@@ -24,7 +24,7 @@ await runAndExit(cliName, cliArgs, context, command, options?);
24
24
  | `usageOnHelp` | `boolean?` | `true` | Enables `--help` flag |
25
25
  | `usageOnError` | `boolean?` | `true` | Prints usage to stderr when parsing fails |
26
26
  | `useTtyColors` | `boolean \| "mock"?` | auto | Controls ANSI color output |
27
- | `onError` | `(error: unknown) => void` | — | Custom handler for execution errors |
27
+ | `onError` | `(error: unknown) => void` | — | Custom handler for parse and execution errors |
28
28
  | `onExit` | `(code: number) => never` | `process.exit` | Override for testing |
29
29
 
30
30
  ### Exit codes
package/docs/index.md CHANGED
@@ -4,10 +4,12 @@ layout: home
4
4
  hero:
5
5
  name: CLI-kiss
6
6
  text: CLI for TypeScript.
7
+
7
8
  tagline:
8
- No bloat, no dependencies.<br/>Standard behavior users expect.<br/><span>K</span>eep
9
- <span>I</span>t <span>S</span>imple and <span>S</span>tupid, it just does the
10
- job.
9
+ No bloat, no dependencies.<br/>Standard behavior users expect.<br/>Keep It Simple and Stupid, it just does the job.
10
+
11
+ image:
12
+ src: /hero.png
11
13
 
12
14
  actions:
13
15
  - theme: brand
@@ -19,12 +21,15 @@ hero:
19
21
 
20
22
  features:
21
23
  - title: Zero dependencies
24
+ icon: 📦
22
25
  details:
23
26
  Ships with no runtime dependencies.<br/>Pure TypeScript, 5kb bundled.
24
27
  - title: Fully typed
28
+ icon: 🧠
25
29
  details:
26
30
  TypeScript first.<br/>Options and positionals inputs strongly typed.
27
31
  - title: Composable
32
+ icon: 🧩
28
33
  details:
29
34
  Easily create nested subcommands.<br/>Build complex CLIs by nesting logic.
30
35
  ---
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cli-kiss",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "devDependencies": {
@@ -1,6 +1,4 @@
1
1
  import { Operation } from "./Operation";
2
- import { OptionUsage } from "./Option";
3
- import { PositionalUsage } from "./Positional";
4
2
  import { ReaderArgs } from "./Reader";
5
3
  import {
6
4
  TypoError,
@@ -9,22 +7,21 @@ import {
9
7
  typoStyleUserInput,
10
8
  TypoText,
11
9
  } from "./Typo";
10
+ import { UsageCommand, UsageSegment } from "./Usage";
12
11
 
13
12
  /**
14
- * A CLI command parses arguments and executes within a given context.
15
- * Created with {@link command}, {@link commandWithSubcommands}, or {@link commandChained}.
16
- * Usually passed through {@link runAndExit} to run.
13
+ * A CLI command. Created with {@link command}, {@link commandWithSubcommands}, or {@link commandChained}.
17
14
  *
18
- * @typeParam Context - Injected at execution time; forwarded to handlers. Use to inject dependencies.
19
- * @typeParam Result - Value produced on execution; typically `void` for leaf commands.
15
+ * @typeParam Context - Injected at execution; forwarded to handlers.
16
+ * @typeParam Result - Produced on execution; typically `void`.
20
17
  */
21
18
  export type Command<Context, Result> = {
22
19
  /**
23
- * Returns the command's static metadata.
20
+ * Returns static metadata.
24
21
  */
25
22
  getInformation(): CommandInformation;
26
23
  /**
27
- * Consumes args in a `readerArgs` and returns a {@link CommandDecoder}.
24
+ * Registers options/positionals on `readerArgs`; returns a {@link CommandDecoder}.
28
25
  */
29
26
  consumeAndMakeDecoder(
30
27
  readerArgs: ReaderArgs,
@@ -39,10 +36,9 @@ export type Command<Context, Result> = {
39
36
  */
40
37
  export type CommandDecoder<Context, Result> = {
41
38
  /**
42
- * Builds the {@link CommandUsage} for the current command path.
43
- * Used for `--help` and `usageOnError`.
39
+ * Returns {@link UsageCommand} for the current command path.
44
40
  */
45
- generateUsage(): CommandUsage;
41
+ generateUsage(): UsageCommand;
46
42
  /**
47
43
  * Creates a ready-to-execute {@link CommandInterpreter}.
48
44
  *
@@ -54,7 +50,7 @@ export type CommandDecoder<Context, Result> = {
54
50
  /**
55
51
  * A fully parsed, decoded and ready-to-execute command.
56
52
  *
57
- * @typeParam Context - Caller-supplied context.
53
+ * @typeParam Context - Context passed to the handler.
58
54
  * @typeParam Result - Value produced on success.
59
55
  */
60
56
  export type CommandInterpreter<Context, Result> = {
@@ -65,15 +61,15 @@ export type CommandInterpreter<Context, Result> = {
65
61
  };
66
62
 
67
63
  /**
68
- * Static metadata for a command, shown in `--help` output via {@link usageToStyledLines}.
64
+ * Command metadata shown in `--help` output.
69
65
  */
70
66
  export type CommandInformation = {
71
67
  /**
72
- * Short description shown in the usage header.
68
+ * Shown in the usage header.
73
69
  */
74
70
  description: string;
75
71
  /**
76
- * Short note shown in parentheses (e.g. `"deprecated"`, `"experimental"`).
72
+ * Shown in parentheses, e.g. `"deprecated"`, `"experimental"`.
77
73
  */
78
74
  hint?: string;
79
75
  /**
@@ -81,7 +77,7 @@ export type CommandInformation = {
81
77
  */
82
78
  details?: Array<string>;
83
79
  /**
84
- * Examples shown in the `Examples:` section of the usage output.
80
+ * Shown in the `Examples:` section.
85
81
  */
86
82
  examples?: Array<{
87
83
  /**
@@ -89,7 +85,7 @@ export type CommandInformation = {
89
85
  */
90
86
  explanation: string;
91
87
  /**
92
- * Command line args to show as an example of usage.
88
+ * Example command args.
93
89
  */
94
90
  commandArgs: Array<
95
91
  | string
@@ -104,66 +100,14 @@ export type CommandInformation = {
104
100
  }>;
105
101
  };
106
102
 
107
- /**
108
- * Full usage/help model.
109
- * Produced by {@link CommandDecoder.generateUsage},
110
- * Consumed by {@link usageToStyledLines}.
111
- */
112
- export type CommandUsage = {
113
- /**
114
- * Segments forming the usage line
115
- * (e.g. `my-cli <POSITIONAL> subcommand <ANOTHER_POSITIONAL>`).
116
- */
117
- segments: Array<CommandUsageSegment>;
118
- /**
119
- * Command's static metadata.
120
- */
121
- information: CommandInformation;
122
- /**
123
- * Positionals in declaration order.
124
- */
125
- positionals: Array<PositionalUsage>;
126
- /**
127
- * Available subcommands. Non-empty when subcommand was not specified.
128
- */
129
- subcommands: Array<CommandUsageSubcommand>;
130
- /**
131
- * Options in registration order.
132
- */
133
- options: Array<OptionUsage>;
134
- };
135
-
136
- /**
137
- * One element in the usage segment trail.
138
- */
139
- export type CommandUsageSegment = { positional: string } | { command: string };
140
-
141
- /**
142
- * Subcommand entry shown in the `Subcommands:` section of the usage output.
143
- */
144
- export type CommandUsageSubcommand = {
145
- /**
146
- * Literal token the user types (e.g. `"deploy"`).
147
- */
148
- name: string;
149
- /**
150
- * Short description from the subcommand's {@link CommandInformation}.
151
- */
152
- description: string | undefined;
153
- /**
154
- * Hint from the subcommand's {@link CommandInformation}.
155
- */
156
- hint: string | undefined;
157
- };
158
-
159
103
  /**
160
104
  * Creates a leaf command that directly executes an {@link Operation}.
161
105
  *
162
106
  * @typeParam Context - Context forwarded to the handler.
163
107
  * @typeParam Result - Value returned by the handler.
164
108
  *
165
- * @param information - Command metadata (description, hint, details).
166
- * @param operation - Defines: options, positionals, and the handler.
109
+ * @param information - Command metadata.
110
+ * @param operation - Options, positionals, and handler.
167
111
  * @returns A {@link Command}.
168
112
  *
169
113
  * @example
@@ -198,7 +142,7 @@ export function command<Context, Result>(
198
142
  );
199
143
  }
200
144
  return {
201
- generateUsage: () => generateUsageShallow(information, operation),
145
+ generateUsage: () => generateUsageLeaf(information, operation),
202
146
  decodeAndMakeInterpreter() {
203
147
  const operationInterpreter =
204
148
  operationDecoder.decodeAndMakeInterpreter();
@@ -211,7 +155,7 @@ export function command<Context, Result>(
211
155
  };
212
156
  } catch (error) {
213
157
  return {
214
- generateUsage: () => generateUsageShallow(information, operation),
158
+ generateUsage: () => generateUsageLeaf(information, operation),
215
159
  decodeAndMakeInterpreter() {
216
160
  throw error;
217
161
  },
@@ -222,17 +166,16 @@ export function command<Context, Result>(
222
166
  }
223
167
 
224
168
  /**
225
- * Creates a command that runs an {@link Operation} to produce a `Payload`,
226
- * then dispatches to a named subcommand based on the next positional token.
169
+ * Creates a command that runs `operation` first, then dispatches to a named subcommand.
227
170
  *
228
171
  * @typeParam Context - Context accepted by `operation`.
229
172
  * @typeParam Payload - Output of `operation`; becomes the subcommand's context.
230
173
  * @typeParam Result - Value produced by the selected subcommand.
231
174
  *
232
- * @param information - Command metadata (description, hint, details).
233
- * @param operation - Always runs first; its output becomes the subcommand's context.
175
+ * @param information - Command metadata.
176
+ * @param operation - Runs first; output becomes the subcommand's context.
234
177
  * @param subcommands - Map of subcommand names to their {@link Command}s.
235
- * @returns A {@link Command} that dispatches to one of the provided subcommands.
178
+ * @returns A dispatching {@link Command}.
236
179
  *
237
180
  * @example
238
181
  * ```ts
@@ -283,8 +226,8 @@ export function commandWithSubcommands<Context, Payload, Result>(
283
226
  return {
284
227
  generateUsage() {
285
228
  const subcommandUsage = subcommandDecoder.generateUsage();
286
- const currentUsage = generateUsageShallow(information, operation);
287
- currentUsage.segments.push(segmentCommand(subcommandName));
229
+ const currentUsage = generateUsageLeaf(information, operation);
230
+ currentUsage.segments.push(segmentSubcommand(subcommandName));
288
231
  currentUsage.segments.push(...subcommandUsage.segments);
289
232
  currentUsage.information = subcommandUsage.information;
290
233
  currentUsage.positionals.push(...subcommandUsage.positionals);
@@ -309,7 +252,7 @@ export function commandWithSubcommands<Context, Payload, Result>(
309
252
  } catch (error) {
310
253
  return {
311
254
  generateUsage() {
312
- const currentUsage = generateUsageShallow(information, operation);
255
+ const currentUsage = generateUsageLeaf(information, operation);
313
256
  currentUsage.segments.push(segmentPositional("<SUBCOMMAND>"));
314
257
  for (const [name, subcommand] of Object.entries(subcommands)) {
315
258
  const { description, hint } = subcommand.getInformation();
@@ -334,10 +277,10 @@ export function commandWithSubcommands<Context, Payload, Result>(
334
277
  * @typeParam Payload - Output of `operation`; becomes `subcommand`'s context.
335
278
  * @typeParam Result - Value produced by `subcommand`.
336
279
  *
337
- * @param information - Command metadata (description, hint, details).
338
- * @param operation - First stage; its output is passed as `subcommand`'s context.
339
- * @param subcommand - Second stage, executed after `operation`.
340
- * @returns A {@link Command} transparently composing the two stages.
280
+ * @param information - Command metadata.
281
+ * @param operation - Runs first; output becomes `subcommand`'s context.
282
+ * @param subcommand - Runs after `operation`.
283
+ * @returns A {@link Command} composing both stages.
341
284
  *
342
285
  * @example
343
286
  * ```ts
@@ -367,7 +310,7 @@ export function commandChained<Context, Payload, Result>(
367
310
  return {
368
311
  generateUsage() {
369
312
  const subcommandUsage = subcommandDecoder.generateUsage();
370
- const currentUsage = generateUsageShallow(information, operation);
313
+ const currentUsage = generateUsageLeaf(information, operation);
371
314
  currentUsage.segments.push(...subcommandUsage.segments);
372
315
  currentUsage.information = subcommandUsage.information;
373
316
  currentUsage.positionals.push(...subcommandUsage.positionals);
@@ -392,7 +335,7 @@ export function commandChained<Context, Payload, Result>(
392
335
  } catch (error) {
393
336
  return {
394
337
  generateUsage() {
395
- const currentUsage = generateUsageShallow(information, operation);
338
+ const currentUsage = generateUsageLeaf(information, operation);
396
339
  currentUsage.segments.push(segmentPositional("[REST]..."));
397
340
  return currentUsage;
398
341
  },
@@ -405,18 +348,18 @@ export function commandChained<Context, Payload, Result>(
405
348
  };
406
349
  }
407
350
 
408
- function segmentPositional(value: string): CommandUsageSegment {
351
+ function segmentPositional(value: string): UsageSegment {
409
352
  return { positional: value };
410
353
  }
411
354
 
412
- function segmentCommand(value: string): CommandUsageSegment {
413
- return { command: value };
355
+ function segmentSubcommand(value: string): UsageSegment {
356
+ return { subcommand: value };
414
357
  }
415
358
 
416
- function generateUsageShallow(
359
+ function generateUsageLeaf(
417
360
  information: CommandInformation,
418
361
  operation: Operation<any, any>,
419
- ): CommandUsage {
362
+ ): UsageCommand {
420
363
  const { positionals, options } = operation.generateUsage();
421
364
  return {
422
365
  segments: positionals.map((positional) =>
@@ -1,6 +1,7 @@
1
- import { Option, OptionDecoder, OptionUsage } from "./Option";
2
- import { Positional, PositionalDecoder, PositionalUsage } from "./Positional";
1
+ import { Option, OptionDecoder } from "./Option";
2
+ import { Positional, PositionalDecoder } from "./Positional";
3
3
  import { ReaderArgs } from "./Reader";
4
+ import { UsageOperation, UsageOption, UsagePositional } from "./Usage";
4
5
 
5
6
  /**
6
7
  * Options, positionals, and an async handler that together form the logic of a CLI command.
@@ -8,14 +9,14 @@ import { ReaderArgs } from "./Reader";
8
9
  * Created with {@link operation} and passed to {@link command},
9
10
  * {@link commandWithSubcommands}, or {@link commandChained}.
10
11
  *
11
- * @typeParam Context - Injected at execution time; forwarded to handlers. Use to inject dependencies.
12
- * @typeParam Result - Value produced on execution; typically `void` for leaf commands.
12
+ * @typeParam Context - Injected at execution; forwarded to handlers.
13
+ * @typeParam Result - Value produced on execution; typically `void`.
13
14
  */
14
15
  export type Operation<Context, Result> = {
15
16
  /**
16
17
  * Returns usage metadata without consuming any arguments.
17
18
  */
18
- generateUsage(): OperationUsage;
19
+ generateUsage(): UsageOperation;
19
20
  /**
20
21
  * Consumes args from `readerArgs` and returns an {@link OperationDecoder}.
21
22
  */
@@ -52,27 +53,10 @@ export type OperationInterpreter<Context, Result> = {
52
53
  executeWithContext(context: Context): Promise<Result>;
53
54
  };
54
55
 
55
- /**
56
- * Usage metadata produced by {@link Operation.generateUsage}.
57
- * Consumed when building {@link CommandUsage}.
58
- */
59
- export type OperationUsage = {
60
- /**
61
- * Usage descriptors for all registered options.
62
- */
63
- options: Array<OptionUsage>;
64
- /**
65
- * Usage descriptors for all declared positionals, in order.
66
- */
67
- positionals: Array<PositionalUsage>;
68
- };
69
-
70
56
  /**
71
57
  * Creates an {@link Operation} from options, positionals, and an async handler.
72
58
  *
73
- * The `handler` receives the parent `context` and an `inputs` object with
74
- * `options` (keyed by the same names declared in `inputs.options`) and
75
- * `positionals` (a tuple in declaration order).
59
+ * The `handler` receives `context` and `inputs` with decoded `options` and `positionals`.
76
60
  *
77
61
  * @typeParam Context - Context type accepted by the handler.
78
62
  * @typeParam Result - Return type of the handler.
@@ -83,7 +67,7 @@ export type OperationUsage = {
83
67
  * @param inputs.options - Map of keys to {@link Option} descriptors.
84
68
  * @param inputs.positionals - Ordered array of {@link Positional} descriptors.
85
69
  * @param handler - Async function implementing the command logic.
86
- * @returns An {@link Operation} ready to be composed into a command.
70
+ * @returns An {@link Operation}.
87
71
  *
88
72
  * @example
89
73
  * ```ts
@@ -96,7 +80,7 @@ export type OperationUsage = {
96
80
  * positionalRequired({ type: typeString, label: "NAME", description: "Name to greet" }),
97
81
  * ],
98
82
  * },
99
- * async (_ctx, { options: { loud }, positionals: [name] }) => {
83
+ * async function (_ctx, { options: { loud }, positionals: [name] }) {
100
84
  * const message = `Hello, ${name}!`;
101
85
  * console.log(loud ? message.toUpperCase() : message);
102
86
  * },
@@ -123,14 +107,12 @@ export function operation<
123
107
  ): Operation<Context, Result> {
124
108
  return {
125
109
  generateUsage() {
126
- const optionsUsage = new Array<OptionUsage>();
110
+ const optionsUsage = new Array<UsageOption>();
127
111
  for (const optionKey in inputs.options) {
128
112
  const optionInput = inputs.options[optionKey]!;
129
- if (optionInput) {
130
- optionsUsage.push(optionInput.generateUsage());
131
- }
113
+ optionsUsage.push(optionInput.generateUsage());
132
114
  }
133
- const positionalsUsage = new Array<PositionalUsage>();
115
+ const positionalsUsage = new Array<UsagePositional>();
134
116
  for (const positionalInput of inputs.positionals) {
135
117
  positionalsUsage.push(positionalInput.generateUsage());
136
118
  }