@gunshi/docs 0.27.0-beta.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.
@@ -0,0 +1,724 @@
1
+ # Declarative Configuration
2
+
3
+ Gunshi allows you to configure your commands declaratively, making your CLI code more organized and maintainable.
4
+
5
+ This approach separates the command definition from its execution logic.
6
+
7
+ ## Basic Declarative Structure
8
+
9
+ If you've followed the Getting Started guide, you've seen how simple it is to create a basic CLI with Gunshi.
10
+
11
+ In that guide, we created a simple greeting command. As your CLI grows with more options, validation rules, and features, declarative configuration becomes essential for managing this complexity effectively.
12
+
13
+ A declaratively configured command in Gunshi follows this structure:
14
+
15
+ ```js
16
+ const command = {
17
+ // Command metadata
18
+ name: 'command-name',
19
+ description: 'Command description',
20
+
21
+ // Command arguments
22
+ args: {
23
+ // Argument definitions
24
+ },
25
+
26
+ // Command examples
27
+ examples: 'Example usage',
28
+
29
+ // Command execution function
30
+ run: ctx => {
31
+ // Command implementation
32
+ }
33
+ }
34
+ ```
35
+
36
+ Let's see how this structure works in practice with a complete example.
37
+
38
+ ## Complete Example
39
+
40
+ Let's start with a simple example that demonstrates the declarative approach.
41
+
42
+ We'll build a greeting command with a few basic options:
43
+
44
+ ```js [cli.js]
45
+ import { cli } from 'gunshi'
46
+
47
+ // Define a command with declarative configuration
48
+ const command = {
49
+ // Command metadata
50
+ name: 'greet',
51
+ description: 'A greeting command with declarative configuration',
52
+
53
+ // Command arguments with descriptions
54
+ args: {
55
+ name: {
56
+ type: 'string',
57
+ short: 'n',
58
+ description: 'Name to greet',
59
+ default: 'World'
60
+ },
61
+ greeting: {
62
+ type: 'string',
63
+ short: 'g',
64
+ default: 'Hello',
65
+ description: 'Greeting to use'
66
+ },
67
+ uppercase: {
68
+ type: 'boolean',
69
+ short: 'u',
70
+ description: 'Display greeting in uppercase'
71
+ }
72
+ },
73
+
74
+ // Command examples
75
+ examples: `
76
+ # Basic usage with default values
77
+ $ node cli.js
78
+
79
+ # Specify a name
80
+ $ node cli.js --name Alice
81
+
82
+ # Use custom greeting and name
83
+ $ node cli.js -n Bob -g "Good morning"
84
+
85
+ # Display in uppercase
86
+ $ node cli.js --name Charlie --uppercase
87
+ `,
88
+
89
+ // Command execution function
90
+ run: ctx => {
91
+ const { name, greeting, uppercase } = ctx.values
92
+
93
+ let message = `${greeting}, ${name}!`
94
+
95
+ if (uppercase) {
96
+ message = message.toUpperCase()
97
+ }
98
+
99
+ console.log(message)
100
+ }
101
+ }
102
+
103
+ // Run the command with the declarative configuration
104
+ await cli(process.argv.slice(2), command, {
105
+ name: 'greet-cli',
106
+ version: '1.0.0',
107
+ description: 'A simple greeting CLI'
108
+ })
109
+ ```
110
+
111
+ <!-- eslint-disable markdown/no-missing-label-refs -->
112
+
113
+ > [!TIP]
114
+ > The example fully code is [here](https://github.com/kazupon/gunshi/tree/main/playground/essentials/declarative).
115
+
116
+ <!-- eslint-enable markdown/no-missing-label-refs -->
117
+
118
+ This example demonstrates the key components of declarative configuration:
119
+
120
+ - **Command metadata** (`name`, `description`) identifies your command
121
+ - **Arguments definition** (`args`) specifies what options the command accepts
122
+ - **Examples** show users how to use the command
123
+ - **Run function** contains the command's execution logic
124
+
125
+ When you run this command with `--help`, Gunshi automatically generates comprehensive help text from your declarative configuration.
126
+
127
+ ## Command Configuration Options
128
+
129
+ ### Command Metadata
130
+
131
+ - `name`: The name of the command
132
+ - `description`: A description of what the command does
133
+
134
+ ### Additional Command Properties
135
+
136
+ Since v0.27.0, commands support additional configuration properties:
137
+
138
+ - `internal`: Boolean flag to mark commands as internal (default: `false`). Internal commands are hidden from help usage but remain fully functional. This is useful for debug commands, administrative functions, or implementation details that shouldn't be exposed to end users.
139
+ - `entry`: Boolean flag that marks the main command when subcommands exist (default: `undefined`). This property is typically set automatically by the framework. When used with the `fallbackToEntry` CLI option, it enables fallback behavior for unmatched subcommands.
140
+ - `rendering`: Object to customize how the command displays help, usage, and error messages. This allows fine-grained control over command-level rendering output.
141
+
142
+ <!-- eslint-disable markdown/no-missing-label-refs -->
143
+
144
+ > [!TIP]
145
+ > For detailed information about customizing rendering output including headers, usage text, and validation errors, see the [Rendering Customization guide](../advanced/custom-rendering.md).
146
+
147
+ <!-- eslint-enable markdown/no-missing-label-refs -->
148
+
149
+ ### Command Options
150
+
151
+ Each option can have the following properties:
152
+
153
+ <!-- eslint-disable markdown/no-missing-label-refs -->
154
+
155
+ - `type`: The data type ('string', 'number', 'boolean', 'positional', 'custom'[, 'enum' if supported])
156
+
157
+ <!-- eslint-enable markdown/no-missing-label-refs -->
158
+
159
+ - `short`: A single-character alias for the option (e.g., `-n` as a shorthand for `--name`), making commands quicker to type for frequent use.
160
+ <!-- eslint-disable markdown/no-missing-label-refs -->
161
+ > [!TIP] Multiple boolean short option flags can be grouped together.
162
+ > (e.g., `-Vb` is equivalent to `-V -b`). Options requiring values (like `string`, `number`, `enum`) cannot be part of a group.
163
+ <!-- eslint-enable markdown/no-missing-label-refs -->
164
+ - `description`: A description of what the option does
165
+ - `default`: Default value if the option is not provided
166
+ - `required`: Set to `true` if the option is required (Note: Positional arguments defined with `type: 'positional'` are implicitly required by the parser).
167
+ - `multiple`: Set to `true` if multiple option values are allowed
168
+ - `toKebab`: Set to `true` to convert camelCase argument names to kebab-case in help text and command-line usage
169
+ - `parse`: A function to parse and validate the argument value. Required when `type` is 'custom'
170
+ - `conflicts`: Specify mutually exclusive options that cannot be used together
171
+
172
+ #### Positional Arguments
173
+
174
+ To define arguments that are identified by their position rather than a name/flag (like `--name`), set their `type` to `'positional'`.
175
+
176
+ The _key_ you use for the argument in the `args` object serves as its name for accessing the value later.
177
+
178
+ Here's how you define positional arguments in your command configuration:
179
+
180
+ ```js
181
+ const command = {
182
+ args: {
183
+ // ... other options
184
+
185
+ // 'source' is the key and the name used to access the value
186
+ source: {
187
+ type: 'positional',
188
+ description: 'The source file path'
189
+ },
190
+
191
+ // 'destination' is the key and the name used to access the value
192
+ destination: {
193
+ type: 'positional',
194
+ description: 'The destination file path'
195
+ }
196
+ // ... potentially more positional arguments
197
+ }
198
+ }
199
+ ```
200
+
201
+ - **Implicitly Required**: Unlike named options which can be optional, positional arguments must always be provided by the user. When you define an argument with `type: 'positional'` in the schema, Gunshi expects it to be present on the command line. If it's missing, a validation error will occur. They cannot be truly optional like named flags.
202
+ - **Order Matters**: Positional arguments are matched based on the order they appear on the command line and the order they are defined in the `args` object.
203
+ - **Accessing Values**: The resolved value is accessible via `ctx.values`, using the _key_ you defined in the `args` object (e.g., `ctx.values.source`, `ctx.values.destination`).
204
+ - **`ctx.positionals`**: This array still exists and contains the raw string values of positional arguments in the order they were parsed (e.g., `ctx.positionals[0]`, `ctx.positionals[1]`). While available, using `ctx.values.<key>` is generally preferred for clarity and consistency.
205
+ - **Descriptions**: The `description` property is used for generating help/usage messages.
206
+ - **Type Conversion**: `args-tokens` resolves positional arguments as strings. You typically need to perform type conversions or further validation on the values accessed via `ctx.values.<key>` within your `run` function based on your application's needs.
207
+
208
+ #### Custom Type Arguments
209
+
210
+ While the built-in types (`string`, `number`, `boolean`, `positional`) cover most use cases, you may need more complex parsing logic for certain arguments.
211
+
212
+ Gunshi supports custom argument types with user-defined parsing functions, allowing you to parse structured input like JSON, validate ranges, or integrate with validation libraries like `zod`.
213
+
214
+ To define a custom argument type:
215
+
216
+ ```js
217
+ import { z } from 'zod'
218
+
219
+ // custom schema with `zod`
220
+ const config = z.object({
221
+ debug: z.boolean(),
222
+ mode: z.string()
223
+ })
224
+
225
+ const command = {
226
+ name: 'example',
227
+ description: 'Example command with custom argument types',
228
+ args: {
229
+ // CSV parser example
230
+ tags: {
231
+ type: 'custom',
232
+ short: 't',
233
+ description: 'Comma-separated list of tags',
234
+ parse: value => value.split(',').map(tag => tag.trim())
235
+ },
236
+
237
+ // JSON parser example with `zod`
238
+ config: {
239
+ type: 'custom',
240
+ short: 'c',
241
+ description: 'JSON configuration',
242
+ parse: value => {
243
+ return config.parse(JSON.parse(value))
244
+ }
245
+ },
246
+
247
+ // Custom validation example
248
+ port: {
249
+ type: 'custom',
250
+ short: 'p',
251
+ description: 'Port number (1024-65535)',
252
+ parse: value => {
253
+ const port = Number(value)
254
+ if (Number.isNaN(port) || port < 1024 || port > 65_535) {
255
+ throw new TypeError(`Invalid port: ${value}. Must be a number between 1024 and 65535`)
256
+ }
257
+ return port
258
+ }
259
+ }
260
+ },
261
+ run: ctx => {
262
+ // Access the parsed values
263
+ console.log('Tags:', ctx.values.tags) // Array of strings
264
+ console.log('Config:', ctx.values.config) // Parsed JSON object
265
+ console.log('Port:', ctx.values.port) // Validated port number
266
+ }
267
+ }
268
+ ```
269
+
270
+ Custom type arguments support:
271
+
272
+ - **Type safety**: The return type of the `parse` function is properly inferred in TypeScript
273
+ - **Validation**: Throw an error from the `parse` function to indicate invalid input
274
+ - **Default values**: Set a `default` property to provide a value when the argument is not specified
275
+ - **Multiple values**: Set `multiple: true` to allow multiple instances of the argument
276
+ - **Short aliases**: Set a `short` property to provide a single-character alias
277
+
278
+ #### Kebab-Case Argument Names
279
+
280
+ <!-- eslint-disable markdown/no-missing-label-refs -->
281
+
282
+ > [!TIP]
283
+ > This feature is particularly useful for users migrating from the [`cac` library](https://github.com/cacjs/cac), which automatically converts camelCase argument names to kebab-case. If you're transitioning from `cac` to Gunshi, enabling the `toKebab` option will help maintain the same command-line interface for your users.
284
+
285
+ <!-- eslint-enable markdown/no-missing-label-refs -->
286
+
287
+ By default, argument names are displayed in the help text and used on the command line exactly as they are defined in the `args` object.
288
+
289
+ However, it's common practice in CLI applications to use kebab-case for multi-word argument names (e.g., `--user-name` instead of `--userName`).
290
+
291
+ Gunshi supports automatic conversion of camelCase argument names to kebab-case with the `toKebab` property, which can be set at two levels:
292
+
293
+ 1. **Command level**: Apply to all arguments in the command
294
+
295
+ To apply kebab-case conversion to all arguments in a command, set the `toKebab` property at the command level:
296
+
297
+ ```js
298
+ const command = {
299
+ name: 'example',
300
+ description: 'Example command',
301
+ toKebab: true, // Apply to all arguments
302
+ args: {
303
+ userName: { type: 'string' }, // Will be displayed as --user-name
304
+ maxRetries: { type: 'number' } // Will be displayed as --max-retries
305
+ },
306
+ run: ctx => {
307
+ /* ... */
308
+ }
309
+ }
310
+ ```
311
+
312
+ 2. **Argument level**: Apply to specific arguments only
313
+
314
+ Alternatively, you can apply kebab-case conversion to specific arguments only by setting it at the argument level:
315
+
316
+ ```js
317
+ const command = {
318
+ name: 'example',
319
+ description: 'Example command',
320
+ args: {
321
+ userName: {
322
+ type: 'string',
323
+ toKebab: true // Will be displayed as --user-name
324
+ },
325
+ maxRetries: { type: 'number' } // Will remain as --maxRetries
326
+ },
327
+ run: ctx => {
328
+ /* ... */
329
+ }
330
+ }
331
+ ```
332
+
333
+ When `toKebab` is enabled:
334
+
335
+ - Argument names are converted from camelCase to kebab-case in help text and usage information
336
+ - Parameter placeholders are also displayed in kebab-case (e.g., `--user-name <user-name>`)
337
+ - Negatable boolean options use kebab-case (e.g., `--no-auto-save` for `autoSave: { type: 'boolean', negatable: true, toKebab: true }`)
338
+
339
+ <!-- eslint-disable markdown/no-missing-label-refs -->
340
+
341
+ > [!NOTE]
342
+ > The argument values are still accessed using the original camelCase keys in your code (e.g., `ctx.values.userName`), regardless of how they appear on the command line.
343
+
344
+ <!-- eslint-enable markdown/no-missing-label-refs -->
345
+
346
+ #### Negatable Boolean Options
347
+
348
+ Sometimes you need to explicitly disable a boolean option that might be enabled by default or through configuration files.
349
+
350
+ Negatable options solve this by providing both positive and negative forms of the same option.
351
+
352
+ To enable this (e.g., allowing both `--verbose` and `--no-verbose`), add the `negatable: true` property to the option's definition.
353
+
354
+ - If you define an option like `verbose: { type: 'boolean', negatable: true }`, Gunshi will recognize both `--verbose` and `--no-verbose`.
355
+ - If `-V` or `--verbose` is passed, the value will be `true`.
356
+ - If `--no-verbose` is passed, the value will be `false`.
357
+ - If neither is passed, the value will be `undefined` (unless a `default` is specified).
358
+
359
+ Without `negatable: true`, only the positive form (e.g., `--verbose`) is recognized, and passing it sets the value to `true`.
360
+
361
+ The description for the negatable option (e.g., `--no-verbose`) is automatically generated (e.g., "Negatable of --verbose"). You can customize this message using [internationalization resource files](../advanced/internationalization.md) by providing a translation for the specific `arg:no-<optionName>` key (e.g., `arg:no-verbose`).
362
+
363
+ #### Conflicting Options
364
+
365
+ You can define mutually exclusive options using the `conflicts` property.
366
+
367
+ This ensures that conflicting options cannot be used together:
368
+
369
+ ```js
370
+ const command = {
371
+ name: 'server',
372
+ description: 'Server configuration',
373
+ args: {
374
+ // These options are mutually exclusive
375
+ verbose: {
376
+ type: 'boolean',
377
+ short: 'v',
378
+ description: 'Enable verbose output',
379
+ conflicts: 'quiet' // Cannot be used with --quiet
380
+ },
381
+ quiet: {
382
+ type: 'boolean',
383
+ short: 'q',
384
+ description: 'Suppress all output',
385
+ conflicts: 'verbose' // Cannot be used with --verbose
386
+ },
387
+
388
+ // Multiple conflicts
389
+ development: {
390
+ type: 'boolean',
391
+ description: 'Development mode',
392
+ conflicts: ['production', 'staging'] // Cannot be used with either
393
+ },
394
+ production: {
395
+ type: 'boolean',
396
+ description: 'Production mode',
397
+ conflicts: ['development', 'staging']
398
+ },
399
+ staging: {
400
+ type: 'boolean',
401
+ description: 'Staging mode',
402
+ conflicts: ['development', 'production']
403
+ }
404
+ },
405
+ run: ctx => {
406
+ // Only one of the conflicting options can be true
407
+ if (ctx.values.verbose) {
408
+ console.log('Verbose mode enabled')
409
+ } else if (ctx.values.quiet) {
410
+ // Minimal output
411
+ }
412
+ }
413
+ }
414
+ ```
415
+
416
+ When conflicting options are used together, Gunshi will throw an error:
417
+
418
+ - Bidirectional conflict detection works with both long and short option forms
419
+ - Clear error messages indicate which options are conflicting
420
+ - Helps prevent invalid command configurations
421
+
422
+ ### Examples
423
+
424
+ The `examples` property provides example commands showing how to use the CLI.
425
+
426
+ This helps users understand how to use your command correctly and is displayed in the help output.
427
+
428
+ #### Basic Examples
429
+
430
+ You can provide examples as a simple string:
431
+
432
+ ```js
433
+ const command = {
434
+ name: 'copy',
435
+ description: 'Copy files',
436
+ args: {
437
+ source: {
438
+ type: 'positional',
439
+ description: 'Source file'
440
+ },
441
+ destination: {
442
+ type: 'positional',
443
+ description: 'Destination file'
444
+ },
445
+ recursive: {
446
+ type: 'boolean',
447
+ short: 'r',
448
+ description: 'Copy recursively'
449
+ }
450
+ },
451
+ examples: 'copy file1.txt file2.txt',
452
+ run: ctx => {
453
+ // Implementation
454
+ }
455
+ }
456
+ ```
457
+
458
+ #### Multiple Examples
459
+
460
+ For multiple examples, use a multi-line string with clear formatting:
461
+
462
+ ```js
463
+ const command = {
464
+ name: 'deploy',
465
+ description: 'Deploy application',
466
+ args: {
467
+ environment: {
468
+ type: 'string',
469
+ short: 'e',
470
+ required: true,
471
+ description: 'Target environment'
472
+ },
473
+ tag: {
474
+ type: 'string',
475
+ short: 't',
476
+ description: 'Version tag'
477
+ },
478
+ dryRun: {
479
+ type: 'boolean',
480
+ description: 'Perform a dry run'
481
+ }
482
+ },
483
+ examples: `
484
+ # Deploy to production with a specific tag
485
+ deploy --environment production --tag v1.2.3
486
+
487
+ # Deploy to staging with dry run
488
+ deploy -e staging --dry-run
489
+
490
+ # Deploy to development (using short option)
491
+ deploy -e development
492
+
493
+ # Deploy with multiple options
494
+ deploy --environment production --tag latest --dry-run
495
+ `.trim(),
496
+ run: ctx => {
497
+ // Implementation
498
+ }
499
+ }
500
+ ```
501
+
502
+ #### Dynamic Examples
503
+
504
+ You can also provide examples as a function that returns a string.
505
+
506
+ This is useful when examples need to be generated dynamically or localized:
507
+
508
+ ```js
509
+ const command = {
510
+ name: 'serve',
511
+ description: 'Start development server',
512
+ args: {
513
+ port: {
514
+ type: 'number',
515
+ short: 'p',
516
+ default: 3000,
517
+ description: 'Port number'
518
+ },
519
+ host: {
520
+ type: 'string',
521
+ short: 'h',
522
+ default: 'localhost',
523
+ description: 'Host address'
524
+ }
525
+ },
526
+ examples: ctx => {
527
+ // Generate examples dynamically based on context
528
+ const appName = ctx.name || 'serve'
529
+ return `
530
+ # Start server with default settings
531
+ ${appName}
532
+
533
+ # Start server on custom port
534
+ ${appName} --port 8080
535
+
536
+ # Start server on all interfaces
537
+ ${appName} --host 0.0.0.0 --port 3000
538
+
539
+ # Using short options
540
+ ${appName} -h 192.168.1.100 -p 8080
541
+ `.trim()
542
+ },
543
+ run: ctx => {
544
+ console.log(`Server starting on ${ctx.values.host}:${ctx.values.port}`)
545
+ }
546
+ }
547
+ ```
548
+
549
+ #### Formatted Examples with Descriptions
550
+
551
+ For better readability, you can include descriptions with your examples:
552
+
553
+ ```js
554
+ const command = {
555
+ name: 'git-flow',
556
+ description: 'Git flow commands',
557
+ args: {
558
+ feature: {
559
+ type: 'string',
560
+ description: 'Feature name'
561
+ },
562
+ hotfix: {
563
+ type: 'string',
564
+ description: 'Hotfix name'
565
+ },
566
+ release: {
567
+ type: 'string',
568
+ description: 'Release version'
569
+ }
570
+ },
571
+ examples: `
572
+ Examples:
573
+ # Start a new feature
574
+ $ git-flow --feature user-authentication
575
+
576
+ # Create a hotfix for production
577
+ $ git-flow --hotfix critical-bug-fix
578
+
579
+ # Prepare a new release
580
+ $ git-flow --release 2.0.0
581
+
582
+ Notes:
583
+ - Feature branches are created from develop
584
+ - Hotfixes are created from master
585
+ - Releases merge into both master and develop
586
+ `.trim(),
587
+ run: ctx => {
588
+ // Implementation
589
+ }
590
+ }
591
+ ```
592
+
593
+ #### Async Examples
594
+
595
+ For complex CLIs that need to load examples from external files or generate them based on runtime conditions, you can use async functions.
596
+
597
+ When using the function form, you can return a Promise for async example generation:
598
+
599
+ ```js
600
+ const command = {
601
+ name: 'config',
602
+ description: 'Manage configuration',
603
+ args: {
604
+ set: {
605
+ type: 'string',
606
+ description: 'Set configuration value'
607
+ },
608
+ get: {
609
+ type: 'string',
610
+ description: 'Get configuration value'
611
+ }
612
+ },
613
+ examples: async ctx => {
614
+ // Could load examples from external source
615
+ const examples = await loadExamplesFromFile('config-examples.txt')
616
+ return (
617
+ examples ||
618
+ `
619
+ # Set a configuration value
620
+ config --set "api.key=abc123"
621
+
622
+ # Get a configuration value
623
+ config --get "api.key"
624
+ `.trim()
625
+ )
626
+ },
627
+ run: ctx => {
628
+ // Implementation
629
+ }
630
+ }
631
+ ```
632
+
633
+ The examples are displayed when users run the command with `--help` flag, making it easier for them to understand the correct usage.
634
+
635
+ ### Command Execution
636
+
637
+ The `run` function receives a command context object (`ctx`) with:
638
+
639
+ - `args`: The command arguments configuration (`ArgSchema` object).
640
+ - `values`: An object containing the parsed and validated values for both named options (e.g., `ctx.values.name`) and positional arguments (accessed via their _key_ from the `args` definition, e.g., `ctx.values.file`). Values are properly typed based on their argument definitions - strings, numbers, booleans, or custom parsed types.
641
+ - `explicit`: An object that tracks which arguments were explicitly provided by the user. Each property corresponds to an argument name and is `true` if the user explicitly provided it, `false` otherwise. This is useful for distinguishing between default values and user-provided values.
642
+ - `positionals`: An array of strings containing the raw values of the arguments identified as positional, in the order they were parsed. Useful if you need the original order, but `ctx.values.<key>` is generally recommended.
643
+ - `rest`: An array of strings containing arguments that appear after the `--` separator.
644
+ - `_`: The raw argument array passed to the `cli` function (original command line arguments).
645
+ - `tokens`: The raw tokens parsed by `args-tokens`.
646
+ - `omitted`: A boolean indicating if the command was run without specifying a subcommand name.
647
+ - `name`: The name of the _currently executing_ command.
648
+ - `description`: The description of the _currently executing_ command.
649
+ - `env`: The command environment settings (version, logger, renderers, etc.).
650
+ - `callMode`: Command call mode ('entry', 'subCommand', or 'unexpected') indicating how the command was invoked.
651
+ - `toKebab`: Boolean indicating whether camelCase argument names should be converted to kebab-case.
652
+ - `log`: Function for outputting messages that respects the `usageSilent` setting.
653
+ - `extensions`: Command context extensions for plugin functionality (available in v0.27.0+).
654
+ - `validationError`: Contains validation errors from argument parsing if any occurred.
655
+
656
+ #### Tracking Explicitly Provided Arguments
657
+
658
+ Among the context properties, the `explicit` property deserves special attention as it allows you to determine whether an argument was explicitly provided by the user or if it's using a default value:
659
+
660
+ ```js
661
+ const command = {
662
+ name: 'deploy',
663
+ args: {
664
+ environment: {
665
+ type: 'string',
666
+ default: 'development'
667
+ },
668
+ force: {
669
+ type: 'boolean',
670
+ default: false
671
+ }
672
+ },
673
+ run: ctx => {
674
+ // Check if the user explicitly provided the environment
675
+ if (ctx.explicit.environment) {
676
+ console.log(`User specified environment: ${ctx.values.environment}`)
677
+ } else {
678
+ console.log(`Using default environment: ${ctx.values.environment}`)
679
+ }
680
+
681
+ // Useful for conditional logic based on user intent
682
+ if (ctx.explicit.force && ctx.values.force) {
683
+ console.log('User explicitly requested force mode')
684
+ }
685
+ }
686
+ }
687
+ ```
688
+
689
+ This feature is particularly useful for:
690
+
691
+ - Distinguishing between default values and user-provided values
692
+ - Implementing different behavior based on whether a user explicitly set an option
693
+ - Validation logic that needs to know if a value was user-provided
694
+ - Warning users when they're using default values for critical options
695
+
696
+ ## CLI Configuration
697
+
698
+ When calling the `cli` function, you can provide additional configuration:
699
+
700
+ ```js
701
+ await cli(process.argv.slice(2), command, {
702
+ name: 'app-name',
703
+ version: '1.0.0',
704
+ description: 'Application description'
705
+ // Additional configuration options
706
+ })
707
+ ```
708
+
709
+ ## Benefits of Declarative Configuration
710
+
711
+ Throughout this guide, you've seen how declarative configuration structures your CLI code. This approach provides several key advantages:
712
+
713
+ 1. **Separation of concerns**: As shown in our examples, command definition stays separate from implementation logic
714
+ 2. **Self-documentation**: The structure itself documents your command's capabilities, automatically generating help text
715
+ 3. **Maintainability**: Clear structure makes commands easier to understand and modify as requirements change
716
+ 4. **Consistency**: Enforces uniform patterns across all your commands, from simple to complex
717
+
718
+ ## Next Steps
719
+
720
+ Now that you understand how to configure commands declaratively, you're ready to explore how Gunshi provides full type safety for your commands.
721
+
722
+ The next section on [Type Safe](./type-safe.md) will show you how to leverage TypeScript's type system to catch errors at compile time and get excellent IDE support while building your CLI applications.
723
+
724
+ With declarative configuration as your foundation, adding type safety will make your CLI development even more robust and developer-friendly.