--- url: /guide/introduction/what-is-gunshi.md --- # What's Gunshi? Gunshi is a modern JavaScript command-line library designed to simplify the creation of command-line interfaces (CLIs). ## Origin of the Name The name "gunshi" (่ปๅธซ) refers to a position in ancient Japanese samurai battles where samurai devised strategies and gave orders. This name is inspired by the word "command", reflecting the library's purpose of handling command-line commands. ## Key Features Gunshi is designed with several powerful features to make CLI development easier and more maintainable: * ๐Ÿ“ **Simple & Universal**: Run commands with simple API and support for universal runtime (Node.js, Deno, Bun). * โš™๏ธ **Declarative & Type Safe**: Configure commands declaratively with full TypeScript support and type-safe argument parsing by [args-tokens](https://github.com/kazupon/args-tokens) * ๐Ÿงฉ **Composable & Lazy**: Create modular sub-commands with context sharing and lazy loading for better performance. * ๐ŸŽจ **Flexible Rendering**: Customize usage generation, validation errors, and help messages with pluggable renderers. * ๐ŸŒ **Internationalization**: Built with global users in mind, featuring locale-aware design, resource management, and multi-language support. * ๐Ÿ”Œ **Pluggable**: Extensible plugin system with dependency management and lifecycle hooks for modular CLI development. ## Why Gunshi? Gunshi provides a modern approach to building command-line interfaces in JavaScript and TypeScript. It's designed to be: * **Developer-friendly**: Simple API with TypeScript support * **Flexible**: Compose commands and customize behavior as needed * **Maintainable**: Declarative configuration makes code easier to understand and maintain * **Performant**: Lazy loading ensures resources are only loaded when needed Whether you're building a simple CLI tool or a complex command-line application with multiple sub-commands, Gunshi provides the features you need to create a great user experience. ## Next Steps Now that you understand what Gunshi is and its key features, here's how to proceed with the documentation: ### Documentation Structure The Gunshi documentation is organized into three main sections: * **Essentials**: Learn the fundamental concepts of Gunshi through a step-by-step tutorial format. This section covers everything from basic usage to composable commands and lazy loading. * **Advanced**: Explore specialized features organized by topic. Each chapter focuses on a specific advanced capability like internationalization, custom rendering, or type system extensions. * **Plugin**: Understand the plugin system through a tutorial approach. Learn how to create, configure, and distribute plugins for extending Gunshi's functionality. ### Where to Go Next > \[!TIP] > Start with the **Setup** guide to install Gunshi in your project, then proceed through the **Essentials** section in order. Each chapter builds upon previous concepts. 1. **[Setup](./setup.md)** - Install and configure Gunshi in your project 2. **[Getting Started](../essentials/getting-started.md)** - Create your first CLI application 3. Continue through the **Essentials** section to learn core concepts After completing the essentials, you can explore the **Advanced** section based on your specific needs. The chapters there are independent and can be read in any order. If you're interested in extending Gunshi with plugins, the **Plugin** section provides comprehensive guidance on creating and using plugins. ## Credits Gunshi project is made possible by the open-source community and the many innovative tools in the JavaScript and TypeScript ecosystem. We extend our gratitude to all contributors and maintainers whose work has laid the foundation for this project. * [`citty`](https://github.com/unjs/citty), Provided the original inspiration for modern JavaScript command-line library. * [`ordana`](https://github.com/sapphi-red/ordana), Inspired documentation generation in Gunshi * [`@bomb.sh/tab`](https://github.com/bombshell-dev/tab), Powered by shell auto-completion for the JavaScript ecosystem --- --- url: /guide/introduction/setup.md --- # Setup Gunshi can be installed in various JavaScript environments. Choose the installation method that matches your project setup. ## Install ### Stable ::: code-group ```sh [npm] npm install --save gunshi ``` ```sh [pnpm] pnpm add gunshi ``` ```sh [yarn] yarn add gunshi ``` ```sh [deno] # For deno projects, you can add Gunshi from JSR: deno add jsr:@gunshi/gunshi ``` ```sh [bun] bun add gunshi ``` ::: ## LLM-Assisted Development Gunshi provides tooling to integrate with AI coding assistants such as [Claude Code](https://claude.ai/code). ### Automatic Setup (Recommended) You can quickly set up LLM-assisted development for your project using the CLI tool: ::: code-group ```sh [npm] npx @gunshi/docs ``` ```sh [pnpm] pnpm dlx @gunshi/docs ``` ```sh [yarn] yarn dlx @gunshi/docs ``` ```sh [bun] bun x @gunshi/docs ``` ::: This command automatically configures [Claude Code skills](https://docs.anthropic.com/en/docs/claude-code/skills) for Claude Code. ### Manual Setup If you prefer manual configuration, install the documentation package as a dev dependency: ::: code-group ```sh [npm] npm install --save-dev @gunshi/docs ``` ```sh [pnpm] pnpm add -D @gunshi/docs ``` ```sh [yarn] yarn add -D @gunshi/docs ``` ```sh [bun] bun add -D @gunshi/docs ``` ::: This package includes guide content and API references as markdown files in [llms.txt](https://llmstxt.org/) format. Then add the following to your AI agent's configuration file (e.g., `CLAUDE.md`): ```md # CLI Development with Gunshi Use the Gunshi library for creating CLI applications instead of other libraries such as cac, yargs, commander, etc. Gunshi is a modern JavaScript command-line library. For more information, refer to the Gunshi API documentation in `node_modules/@gunshi/docs/**.md`. ``` ## Requirements Gunshi requires: * **JavaScript Runtime**: * **Node.js**: v22 or later * **Deno**: v2 or later * **Bun**: v1.1 or later * **ES Modules**: `"type": "module"` in `package.json` (if using Node.js and Bun) * **TypeScript**: Version 5.0 or higher (if using TypeScript) --- --- url: /guide/essentials/getting-started.md --- # Getting Started This guide will help you create your first command-line application with Gunshi. We'll start with a simple "Hello World" example and gradually explore more features. ## Hello World Example Let's create a simple CLI application that greets the user. Create a new file (e.g., `cli.js` or `cli.ts`) and add the following code: ```js [cli.js] import { cli } from 'gunshi' // Run a simple command await cli(process.argv.slice(2), () => { console.log('Hello, World!') }) ``` > \[!TIP] > The example fully code is [here](https://github.com/kazupon/gunshi/tree/main/playground/essentials/getting-started/hello). This minimal example demonstrates the core concept of Gunshi. The `cli` function takes command-line arguments and a function to execute. ## Running Your CLI You can run your CLI application with: ```sh node cli.js ``` You should see the output: ```sh Hello, World! ``` ## Adding Command-Line Arguments Let's enhance our example to accept a name as an argument. The function receives a `CommandContext` object (abbreviated as `ctx`) as its parameter. This context object contains parsed command-line arguments, options, and other execution information: ```js [cli.js] import { cli } from 'gunshi' await cli(process.argv.slice(2), ctx => { // Access positional arguments const name = ctx.positionals[0] || 'World' console.log(`Hello, ${name}!`) }) ``` > \[!TIP] > The example fully code is [here](https://github.com/kazupon/gunshi/tree/main/playground/essentials/getting-started/context). Now you can run: ```sh node cli.js Alice ``` And you'll see: ```sh Hello, Alice! ``` ## Adding Command Options Let's add some options to our command: ```js [cli.js] import { cli, define } from 'gunshi' const command = define({ name: 'greeter', description: 'A simple greeting CLI', args: { name: { type: 'string', short: 'n', description: 'Name to greet' }, uppercase: { type: 'boolean', short: 'u', description: 'Convert greeting to uppercase' } }, run: ctx => { const { name = 'World', uppercase } = ctx.values let greeting = `Hello, ${name}!` if (uppercase) { greeting = greeting.toUpperCase() } console.log(greeting) } }) await cli(process.argv.slice(2), command) ``` > \[!TIP] > The example fully code is [here](https://github.com/kazupon/gunshi/tree/main/playground/essentials/getting-started/options). Now you can run: ```sh node cli.js --name Alice --uppercase # or with short options node cli.js -n Alice -u ``` And you'll see: ```sh HELLO, ALICE! ``` ## Built-in Help Gunshi automatically generates help information for your commands through its built-in plugin system. Run: ```sh node cli.js --help ``` You'll see a help message that includes: Here's an example of the generated help output: ```sh USAGE: COMMAND OPTIONS: -h, --help Display this help message -v, --version Display this version -n, --name Name to greet -u, --uppercase Convert greeting to uppercase ``` The help message automatically includes: * Command description * Available options * Option descriptions The standard `cli()` function automatically includes these built-in plugins: * `@gunshi/plugin-global` - Provides global options like `--help` and `--version` * `@gunshi/plugin-renderer` - Handles formatted output for help messages, error messages, and usage information These plugins are included by default when you use `cli()` from the main `gunshi` package. If you use `cli()` from [`@gunshi/bone`](./plugin-system.md#minimal-setup) instead, which includes no plugins, you'll need to manually configure these plugins to get help and version functionality. > \[!TIP] > Want to learn more about Gunshi's plugin architecture? Check out the [Plugin System guide](./plugin-system.md) to understand how plugins work, explore the built-in plugins in detail, and learn how to create your own custom plugins to extend your CLI's functionality. ## Using Gunshi with Different Runtimes Gunshi is designed to work seamlessly across multiple JavaScript runtimes. Here's how to use it with each supported environment: ### Node.js For Node.js applications, use `process.argv.slice(2)` to pass command-line arguments: ```js [cli.js] import { cli } from 'gunshi' function entry() { console.log('Hello, Gunshi!') } await cli(process.argv.slice(2), entry) ``` ### Deno In Deno, use `Deno.args` to access command-line arguments: ```ts [cli.ts] import { cli } from '@gunshi/gunshi' function entry() { console.log('Hello, Gunshi with Deno!') } await cli(Deno.args, entry) ``` ### Bun Bun also provides `Bun.argv` similar to Node.js: ```ts [cli.ts] import { cli } from 'gunshi' function entry() { console.log('Hello, Gunshi with Bun!') } await cli(Bun.argv.slice(2), entry) // or use process.argv.slice(2) in Bun ``` Note that while the argument passing differs slightly between runtimes, the Gunshi API remains consistent across all environments. ## Next Steps You've successfully created your first Gunshi CLI application! You've learned the fundamentals: creating basic commands, handling arguments and options, using the built-in help system, and running your CLI across different JavaScript runtimes. Now it's time to explore the essential features that will help you build powerful, production-ready CLI applications. The following chapters will guide you through each topic: * **[Declarative Configuration](./declarative.md)** - Organize commands with clear, maintainable declarative structures * **[Type Safety](./type-safe.md)** - Leverage TypeScript for automatic type inference and compile-time checking * **[Composable Sub-commands](./composable.md)** - Build complex CLIs with modular sub-commands like `git commit` or `npm install` * **[Lazy & Async Command Loading](./lazy-async.md)** - Optimize startup performance by loading commands only when needed * **[Auto Usage Generation](./auto-usage.md)** - Create self-documenting CLIs with automatic help and usage information * **[Plugin System](./plugin-system.md)** - Extend your CLI with modular plugins for features like i18n and shell completion Each chapter builds upon the previous ones, introducing more sophisticated patterns and techniques. Start with [Declarative Configuration](./declarative.md) to learn how to structure your commands in a clean, maintainable way as your CLI grows in complexity. --- --- url: /guide/essentials/declarative.md --- # Declarative Configuration Gunshi allows you to configure your commands declaratively, making your CLI code more organized and maintainable. This approach separates the command definition from its execution logic. ## Basic Declarative Structure If you've followed the Getting Started guide, you've seen how simple it is to create a basic CLI with Gunshi. 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. A declaratively configured command in Gunshi follows this structure: ```js import { define } from 'gunshi' const command = define({ // Command metadata name: 'command-name', description: 'Command description', // Command arguments args: { // Argument definitions }, // Command examples examples: 'Example usage', // Command execution function run: ctx => { // Command implementation } }) ``` Let's see how this structure works in practice with a complete example. ## Complete Example Let's start with a simple example that demonstrates the declarative approach. We'll build a greeting command with a few basic options: ```js [cli.js] import { cli, define } from 'gunshi' // Define a command with declarative configuration const command = define({ // Command metadata name: 'greet', description: 'A greeting command with declarative configuration', // Command arguments with descriptions args: { name: { type: 'string', short: 'n', description: 'Name to greet', default: 'World' }, greeting: { type: 'string', short: 'g', default: 'Hello', description: 'Greeting to use' }, uppercase: { type: 'boolean', short: 'u', description: 'Display greeting in uppercase' } }, // Command examples examples: ` # Basic usage with default values $ node cli.js # Specify a name $ node cli.js --name Alice # Use custom greeting and name $ node cli.js -n Bob -g "Good morning" # Display in uppercase $ node cli.js --name Charlie --uppercase `, // Command execution function run: ctx => { const { name, greeting, uppercase } = ctx.values let message = `${greeting}, ${name}!` if (uppercase) { message = message.toUpperCase() } console.log(message) } }) // Run the command with the declarative configuration await cli(process.argv.slice(2), command, { name: 'greet-cli', version: '1.0.0', description: 'A simple greeting CLI' }) ``` > \[!TIP] > The example fully code is [here](https://github.com/kazupon/gunshi/tree/main/playground/essentials/declarative). This example demonstrates the key components of declarative configuration: * **Command metadata** (`name`, `description`) identifies your command * **Arguments definition** (`args`) specifies what options the command accepts * **Examples** show users how to use the command * **Run function** contains the command's execution logic When you run this command with `--help`, Gunshi automatically generates comprehensive help text from your declarative configuration. ## Command Configuration Options ### Command Metadata * `name`: The name of the command * `description`: A description of what the command does ### Additional Command Properties Since v0.27.0, commands support additional configuration properties: * `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. * `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. * `rendering`: Object to customize how the command displays help, usage, and error messages. This allows fine-grained control over command-level rendering output. > \[!TIP] > For detailed information about customizing rendering output including headers, usage text, and validation errors, see the [Rendering Customization guide](../advanced/custom-rendering.md). ### Command Options Each option can have the following properties: * `type`: The data type: 'string', 'number', 'boolean', 'enum', 'positional' or 'custom' * `short`: A single-character alias for the option (e.g., `-n` as a shorthand for `--name`), making commands quicker to type for frequent use. > \[!TIP] Multiple boolean short option flags can be grouped together. > (e.g., `-Vb` is equivalent to `-V -b`). Options requiring values (like `string`, `number`, `enum`) cannot be part of a group. * `description`: A description of what the option does * `default`: Default value if the option is not provided * `required`: Set to `true` if the option is required. For single positional arguments, omit this field to keep the compatibility default of required, or set `required: false` to make the positional argument optional. * `multiple`: Set to `true` if multiple option values are allowed * `toKebab`: Set to `true` to convert camelCase argument names to kebab-case in help text and command-line usage * `hidden`: Set to `true` to hide the argument from generated help/usage output while keeping it parseable. * `parse`: A function to parse and validate the argument value. Required when `type` is 'custom' * `choices`: The values that an `enum` option accepts. Required when `type` is 'enum', unless the option has a `parse` function, which then receives the value as it would for a 'string' option * `conflicts`: Specify mutually exclusive options that cannot be used together > \[!NOTE] > For a more type-safe and composable way to define arguments, see [Parser Combinators](../advanced/parser-combinators.md). #### Positional Arguments To define arguments that are identified by their position rather than a name/flag (like `--name`), set their `type` to `'positional'`. The *key* you use for the argument in the `args` object serves as its name for accessing the value later. Here's how you define positional arguments in your command configuration: ```js import { define } from 'gunshi' const command = define({ args: { // ... other options // 'source' is the key and the name used to access the value source: { type: 'positional', description: 'The source file path' }, // 'destination' is the key and the name used to access the value destination: { type: 'positional', description: 'The destination file path' }, // optional positional argument, useful when the value may come from stdin query: { type: 'positional', required: false, description: 'SQL query to execute' } // ... potentially more positional arguments } }) ``` * **Required by Default**: A single positional argument without `required: false` or `default` remains required for compatibility. Set `required: false` when the value can be omitted, such as a command that can read from stdin. * **Default Values**: A single positional argument with `default` uses that value when the command-line value is missing, unless `required: true` is set. * **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. Optional positional arguments leave enough input values for later required positional arguments before consuming a value. * **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`). * **`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.` is generally preferred for clarity and consistency. * **Descriptions**: The `description` property is used for generating help/usage messages. * **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.` within your `run` function based on your application's needs. #### Custom Type Arguments While the built-in types (`string`, `number`, `boolean`, `positional`) cover most use cases, you may need more complex parsing logic for certain arguments. 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`. To define a custom argument type: ```js import { define } from 'gunshi' import { z } from 'zod' // custom schema with `zod` const config = z.object({ debug: z.boolean(), mode: z.string() }) const command = define({ name: 'example', description: 'Example command with custom argument types', args: { // CSV parser example tags: { type: 'custom', short: 't', description: 'Comma-separated list of tags', parse: value => value.split(',').map(tag => tag.trim()) }, // JSON parser example with `zod` config: { type: 'custom', short: 'c', description: 'JSON configuration', parse: value => { return config.parse(JSON.parse(value)) } }, // Custom validation example port: { type: 'custom', short: 'p', description: 'Port number (1024-65535)', parse: value => { const port = Number(value) if (Number.isNaN(port) || port < 1024 || port > 65_535) { throw new TypeError(`Invalid port: ${value}. Must be a number between 1024 and 65535`) } return port } } }, run: ctx => { // Access the parsed values console.log('Tags:', ctx.values.tags) // Array of strings console.log('Config:', ctx.values.config) // Parsed JSON object console.log('Port:', ctx.values.port) // Validated port number } }) ``` Custom type arguments support: * **Type safety**: The return type of the `parse` function is properly inferred in TypeScript * **Validation**: Throw an error from the `parse` function to indicate invalid input * **Default values**: Set a `default` property to provide a value when the argument is not specified * **Multiple values**: Set `multiple: true` to allow multiple instances of the argument * **Short aliases**: Set a `short` property to provide a single-character alias #### Argument Names That Plugins Also Use Plugins add global options, which are merged into the arguments of every command. `@gunshi/plugin-global`, installed by default, adds `help` and `version`; `@gunshi/plugin-dryrun` adds `dryRun`, under a name you can change with its `name` option. None of those names is reserved. A command may declare an argument of its own under any of them, and the command's argument wins โ€” gunshi merges the arguments of the command over the global options: ```js const release = define({ name: 'release', args: { // shadows the global `--version` for this command only version: { type: 'string', description: 'Version to release' } }, run: ctx => console.log(`releasing ${ctx.values.version}`) }) ``` Taking the name means taking it whole. `my-cli release --version` no longer prints the version of the CLI, and `-v` is no longer defined either, because the global schema is replaced rather than extended. Declare a `short` of your own if you want one. Other commands of the same CLI keep the global option. A short name is taken the same way, and on its own. When an argument of the command declares a `short` that a global option also uses, the letter belongs to the command, and the global option keeps its long name: ```js const build = define({ name: 'build', args: { // takes `-v` from the global `--version`, for this command only verbose: { type: 'boolean', short: 'v', description: 'Verbose output' } }, run: ctx => console.log(`building${ctx.values.verbose ? ' verbosely' : ''}`) }) ``` `my-cli build -v` sets `verbose`, while `my-cli build --version` still prints the version of the CLI, and its help lists it as `--version` alone. Other commands of the same CLI keep `-v` for `--version`. > \[!NOTE] > Declaring the name is what takes a global option away, even with a schema identical, field for field, to the one the plugin registered. A plugin is told which of its own options are still in effect; how each one reacts is part of that plugin's documentation. See [Plugin System](../plugin/introduction.md). #### Option Values That Start with a Dash An option that takes a value, such as a `string`, `number` or `enum` option, takes the next argument as its value, as in `--port 5000`. The exception is an argument that starts with a dash, since it looks like an option: `--port -5` does not take `-5`, and reports `--port` as missing a value (`err:arg:missing-value`). With a `string` option `name`, `--name --foo` is reported the same way. A lone `-` is still taken as a value. This only concerns options that take a value. A `boolean` option takes no value, so `--verbose --foo` sets `verbose` and goes on to read `--foo` as an option. To pass a value that starts with a dash, write it after `=`, or right after a short option: ```sh app --port=-5 app -p=-5 app -p-5 ``` The error message also suggests the form with `=`, such as `(to pass '-5' as its value, write '--port=-5')`. With the i18n plugin, the suggestion follows the translated error, and is translated by the built-in resource key `err:arg:missing-value:hint`. #### Strict Unknown Option Validation By default, Gunshi keeps backward compatibility by ignoring option tokens that are not declared in the resolved command's `args` or installed global options. Enable `strict` when you want typos or unsupported options to fail before the command runner executes: ```js await cli(process.argv.slice(2), command, { strict: true }) ``` With `strict: true`, an input such as `--alow-reload` is reported as an argument validation error: ```txt Unknown option: --alow-reload ``` The error uses the `err:arg:unknown-option` resource key, so the message follows the same renderer and i18n behavior as other argument validation errors. Suggestions such as `Did you mean --allow-reload?` are intentionally left to suggestion plugins. #### Options before sub-commands Option values are removed before Gunshi chooses a sub-command. A string option can therefore appear before or after the command without turning its value into a command name: ```ts await cli(['--config', 'prod.json', 'deploy'], entry, { subCommands }) await cli(['--config=prod.json', 'deploy'], entry, { subCommands }) await cli(['deploy', '--config', 'prod.json'], entry, { subCommands }) await cli(['-c', 'prod.json', 'deploy'], entry, { subCommands }) ``` All four calls select `deploy` and resolve `ctx.values.config` to `prod.json`. This also applies to nested paths such as `remote --config prod.json add origin`. A value that happens to equal a command name remains a value, so `--config clean deploy` selects `deploy` when `config` is a global string option. The command's own argument definition takes precedence when it declares the same long or short name. For example, a command that declares `config: { type: 'boolean' }` interprets `--config deploy` as `config: true` and selects `deploy`. If a lazy command needs an option before its name, declare the option in the lazy metadata as well; otherwise place the option after the command name. Ambiguous or inconsistent interpretations produce a command-resolution error and do not run a command. Use `--` to stop command exploration; values after it are passed through as `ctx.rest`. #### Command Not Found Handling When a sub-command cannot be resolved, Gunshi reports it as a structured `CommandNotFoundError` inside the validation error pipeline. This avoids brittle checks such as `error.message.startsWith('Command not found:')`. ```js import { cli, define, isCommandNotFoundError } from 'gunshi' try { await cli(process.argv.slice(2), define({ name: 'app', run: () => {} }), { subCommands: { deploy: define({ name: 'deploy', run: () => {} }) } }) } catch (error) { if (error instanceof AggregateError) { const commandError = error.errors.find(isCommandNotFoundError) if (commandError) { console.error(`Unknown command: ${commandError.commandName}`) console.error(`Available commands: ${commandError.candidates.join(', ')}`) } } } ``` > \[!NOTE] > Use `isCommandNotFoundError` and `isArgsValidationError` instead of `instanceof` checks. `gunshi`, `@gunshi/plugin`, and `@gunshi/bone` each bundle their own copy of the error classes, so `instanceof` fails when an error is thrown by one package and checked with a class imported from another. The guards recognize errors from every copy, including argument errors such as a missing required option or an invalid value, whose `name` is overridden with the argument name. Keep all `gunshi` and `@gunshi/*` packages on the same version. Older copies do not mark those argument errors, so the guards cannot recognize them when an older copy throws them. Unknown option and command not found errors from older copies are still recognized. `commandName` is the command that could not be resolved. `candidates` contains command names from the same command level, which can be used by suggestion plugins. `commandPath` points to the parent command path. For example, `git remote ad` sets `commandName` to `ad` and `commandPath` to `['remote']`. The command context is created from the parent command, not the missing command. This means `ctx.command`, `ctx.name`, and `ctx.commandPath` describe the parent command. The actual missing command is available from `CommandNotFoundError.commandName`. The error uses the `err:cmd:not-found` resource key, so i18n plugins can localize the message while the fallback message remains compatible with `Command not found: `. #### Kebab-Case Argument Names > \[!TIP] > 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. 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. However, it's common practice in CLI applications to use kebab-case for multi-word argument names (e.g., `--user-name` instead of `--userName`). Gunshi supports automatic conversion of camelCase argument names to kebab-case with the `toKebab` property, which can be set at two levels: 1. **Command level**: Apply to all arguments in the command To apply kebab-case conversion to all arguments in a command, set the `toKebab` property at the command level: ```js import { define } from 'gunshi' const command = define({ name: 'example', description: 'Example command', toKebab: true, // Apply to all arguments args: { userName: { type: 'string' }, // Will be displayed as --user-name maxRetries: { type: 'number' } // Will be displayed as --max-retries }, run: ctx => { /* ... */ } }) ``` 2. **Argument level**: Apply to specific arguments only Alternatively, you can apply kebab-case conversion to specific arguments only by setting it at the argument level: ```js import { define } from 'gunshi' const command = define({ name: 'example', description: 'Example command', args: { userName: { type: 'string', toKebab: true // Will be displayed as --user-name }, maxRetries: { type: 'number' } // Will remain as --maxRetries }, run: ctx => { /* ... */ } }) ``` When `toKebab` is enabled: * Argument names are converted from camelCase to kebab-case in help text and usage information * Parameter placeholders are also displayed in kebab-case (e.g., `--user-name `) * Negatable boolean options use kebab-case (e.g., `--no-auto-save` for `autoSave: { type: 'boolean', negatable: true, toKebab: true }`) > \[!NOTE] > 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. #### Negatable Boolean Options Sometimes you need to explicitly disable a boolean option that might be enabled by default or through configuration files. Negatable options solve this by providing both positive and negative forms of the same option. To enable this (e.g., allowing both `--verbose` and `--no-verbose`), add the `negatable: true` property to the option's definition. * If you define an option like `verbose: { type: 'boolean', negatable: true }`, Gunshi will recognize both `--verbose` and `--no-verbose`. * If `-V` or `--verbose` is passed, the value will be `true`. * If `--no-verbose` is passed, the value will be `false`. * If neither is passed, the value will be `undefined` (unless a `default` is specified). * The negative form does not take a value: `--no-verbose=false` is reported as an `err:arg:unexpected-value` error. Without `negatable: true`, only the positive form (e.g., `--verbose`) is recognized, and passing it sets the value to `true`. The positive form of any boolean option, negatable or not, can also be given a value with `=`: `--verbose=true` sets the value to `true`, and `--verbose=false` sets it to `false`. Any other value, such as `--verbose=0` or `--verbose=`, is reported as an `err:arg:invalid-type` error. 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-` key (e.g., `arg:no-verbose`). `no-` is not reserved: an argument may be named `no-cache` or `no-emoji`, and it is then an option of its own, described by its own `description`. Declaring both `cache: { type: 'boolean', negatable: true }` and a separate `no-cache` argument is best avoided, though โ€” a single `--no-cache` on the command line then sets a value for both of them. The name that decides this is the one the argument is written under on the command line, not the key it is declared with. With `toKebab`, `noCache` is written `--no-cache` as well, so it takes the name from the negated form of `cache` the same way a literal `no-cache` does. #### Conflicting Options You can define mutually exclusive options using the `conflicts` property. This ensures that conflicting options cannot be used together: ```js import { define } from 'gunshi' const command = define({ name: 'server', description: 'Server configuration', args: { // These options are mutually exclusive verbose: { type: 'boolean', short: 'v', description: 'Enable verbose output', conflicts: 'quiet' // Cannot be used with --quiet }, quiet: { type: 'boolean', short: 'q', description: 'Suppress all output', conflicts: 'verbose' // Cannot be used with --verbose }, // Multiple conflicts development: { type: 'boolean', description: 'Development mode', conflicts: ['production', 'staging'] // Cannot be used with either }, production: { type: 'boolean', description: 'Production mode', conflicts: ['development', 'staging'] }, staging: { type: 'boolean', description: 'Staging mode', conflicts: ['development', 'production'] } }, run: ctx => { // Only one of the conflicting options can be true if (ctx.values.verbose) { console.log('Verbose mode enabled') } else if (ctx.values.quiet) { // Minimal output } } }) ``` When conflicting options are used together, Gunshi will throw an error: * Bidirectional conflict detection works with both long and short option forms * Clear error messages indicate which options are conflicting * Helps prevent invalid command configurations ### Examples The `examples` property provides example commands showing how to use the CLI. This helps users understand how to use your command correctly and is displayed in the help output. #### Basic Examples You can provide examples as a simple string: ```js import { define } from 'gunshi' const command = define({ name: 'copy', description: 'Copy files', args: { source: { type: 'positional', description: 'Source file' }, destination: { type: 'positional', description: 'Destination file' }, recursive: { type: 'boolean', short: 'r', description: 'Copy recursively' } }, examples: 'copy file1.txt file2.txt', run: ctx => { // Implementation } }) ``` #### Multiple Examples For multiple examples, use a multi-line string with clear formatting: ```js import { define } from 'gunshi' const command = define({ name: 'deploy', description: 'Deploy application', args: { environment: { type: 'string', short: 'e', required: true, description: 'Target environment' }, tag: { type: 'string', short: 't', description: 'Version tag' }, dryRun: { type: 'boolean', description: 'Perform a dry run' } }, examples: ` # Deploy to production with a specific tag deploy --environment production --tag v1.2.3 # Deploy to staging with dry run deploy -e staging --dry-run # Deploy to development (using short option) deploy -e development # Deploy with multiple options deploy --environment production --tag latest --dry-run `.trim(), run: ctx => { // Implementation } }) ``` #### Dynamic Examples You can also provide examples as a function that returns a string. This is useful when examples need to be generated dynamically or localized: ```js import { define } from 'gunshi' const command = define({ name: 'serve', description: 'Start development server', args: { port: { type: 'number', short: 'p', default: 3000, description: 'Port number' }, host: { type: 'string', short: 'h', default: 'localhost', description: 'Host address' } }, examples: ctx => { // Generate examples dynamically based on context const appName = ctx.name || 'serve' return ` # Start server with default settings ${appName} # Start server on custom port ${appName} --port 8080 # Start server on all interfaces ${appName} --host 0.0.0.0 --port 3000 # Using short options ${appName} -h 192.168.1.100 -p 8080 `.trim() }, run: ctx => { console.log(`Server starting on ${ctx.values.host}:${ctx.values.port}`) } }) ``` #### Formatted Examples with Descriptions For better readability, you can include descriptions with your examples: ```js import { define } from 'gunshi' const command = define({ name: 'git-flow', description: 'Git flow commands', args: { feature: { type: 'string', description: 'Feature name' }, hotfix: { type: 'string', description: 'Hotfix name' }, release: { type: 'string', description: 'Release version' } }, examples: ` Examples: # Start a new feature $ git-flow --feature user-authentication # Create a hotfix for production $ git-flow --hotfix critical-bug-fix # Prepare a new release $ git-flow --release 2.0.0 Notes: - Feature branches are created from develop - Hotfixes are created from master - Releases merge into both master and develop `.trim(), run: ctx => { // Implementation } }) ``` #### Async Examples For complex CLIs that need to load examples from external files or generate them based on runtime conditions, you can use async functions. When using the function form, you can return a Promise for async example generation: ```js import { define } from 'gunshi' const command = define({ name: 'config', description: 'Manage configuration', args: { set: { type: 'string', description: 'Set configuration value' }, get: { type: 'string', description: 'Get configuration value' } }, examples: async ctx => { // Could load examples from external source const examples = await loadExamplesFromFile('config-examples.txt') return ( examples || ` # Set a configuration value config --set "api.key=abc123" # Get a configuration value config --get "api.key" `.trim() ) }, run: ctx => { // Implementation } }) ``` The examples are displayed when users run the command with `--help` flag, making it easier for them to understand the correct usage. ### Command Execution The `run` function receives a command context object (`ctx`) with: * `args`: The command arguments configuration (`ArgSchema` object). * `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. * `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. * `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.` is generally recommended. * `rest`: An array of strings containing arguments that appear after the `--` separator. * `_`: The raw argument array passed to the `cli` function (original command line arguments). * `tokens`: The raw tokens parsed by `args-tokens`. * `omitted`: A boolean indicating if the command was run without specifying a subcommand name. * `name`: The name of the *currently executing* command. * `description`: The description of the *currently executing* command. * `env`: The command environment settings (version, logger, renderers, etc.). * `callMode`: Command call mode ('entry', 'subCommand', or 'unexpected') indicating how the command was invoked. * `toKebab`: Boolean indicating whether camelCase argument names should be converted to kebab-case. * `log`: Function for outputting messages that respects the `usageSilent` setting. Output that must still appear when `usageSilent` is set should use `console.log` / `console.warn`. * `extensions`: Command context extensions for plugin functionality (available in v0.27.0+). * `validationError`: Contains validation errors from argument parsing if any occurred. #### Tracking Explicitly Provided Arguments 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: ```js import { define } from 'gunshi' const command = define({ name: 'deploy', args: { environment: { type: 'string', default: 'development' }, force: { type: 'boolean', default: false } }, run: ctx => { // Check if the user explicitly provided the environment if (ctx.explicit.environment) { console.log(`User specified environment: ${ctx.values.environment}`) } else { console.log(`Using default environment: ${ctx.values.environment}`) } // Useful for conditional logic based on user intent if (ctx.explicit.force && ctx.values.force) { console.log('User explicitly requested force mode') } } }) ``` This feature is particularly useful for: * Distinguishing between default values and user-provided values * Implementing different behavior based on whether a user explicitly set an option * Validation logic that needs to know if a value was user-provided * Warning users when they're using default values for critical options ## CLI Configuration When calling the `cli` function, you can provide additional configuration: ```js await cli(process.argv.slice(2), command, { name: 'app-name', version: '1.0.0', description: 'Application description' // Additional configuration options }) ``` ## Benefits of Declarative Configuration Throughout this guide, you've seen how declarative configuration structures your CLI code. This approach provides several key advantages: 1. **Separation of concerns**: As shown in our examples, command definition stays separate from implementation logic 2. **Self-documentation**: The structure itself documents your command's capabilities, automatically generating help text 3. **Maintainability**: Clear structure makes commands easier to understand and modify as requirements change 4. **Consistency**: Enforces uniform patterns across all your commands, from simple to complex ## Next Steps Now that you understand how to configure commands declaratively, you're ready to explore how Gunshi provides full type safety for your commands. 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. With declarative configuration as your foundation, adding type safety will make your CLI development even more robust and developer-friendly. --- --- url: /guide/essentials/type-safe.md --- # Type Safe In the previous chapter, we learned how to create commands using declarative configuration with plain JavaScript objects. While this approach works well, TypeScript users can benefit from enhanced type safety and better development experience using Gunshi's `define` function. The `define` function wraps your command configuration and provides automatic type inference, ensuring that your command handlers receive properly typed context objects without manual type annotations. ## Benefits of Type Safety Using TypeScript with Gunshi's `define` function provides CLI-specific advantages: * **Autocompletion for command options**: IDE suggests available options when accessing `ctx.values` * **Prevent runtime errors**: Catch typos in option names before your CLI ships * **Self-documenting commands**: Types show exactly what arguments your command accepts * **Safe refactoring**: Rename options across your codebase with confidence ## Type Safety Levels in Gunshi Gunshi provides different levels of type safety to match your needs: 1. **Basic type inference** (covered in this chapter): Automatic typing of command arguments 2. **Plugin extension typing**: Type-safe access to plugin functionality 3. **Full type parameters**: Complete control over all types using `GunshiParams` This chapter focuses on the first level, which covers most common use cases. Advanced patterns are available when you need them. ## Using `define` for Type Safety The `define` function transforms your command configuration to provide: * **Automatic type inference**: No need to manually type `ctx` parameters * **IDE autocompletion**: Get suggestions for `ctx.values` properties * **Compile-time validation**: TypeScript catches typos and type mismatches before runtime * **Simplified imports**: No need to import type definitions like `Command` or `CommandContext` Let's transform the greeting command from the previous chapter to use `define` for full type safety. The `define` function is a simple wrapper that preserves your command's type information, enabling TypeScript to automatically infer types for your command options and provide IDE autocompletion: ```ts [cli.ts] import { cli, define } from 'gunshi' // Define a command using the `define` function const command = define({ name: 'greet', args: { // Define a string option 'name' with a short alias 'n' name: { type: 'string', short: 'n', description: 'Your name' }, // Define a number option 'age' with a default value age: { type: 'number', short: 'a', description: 'Your age', default: 30 }, // Define a boolean flag 'verbose' verbose: { type: 'boolean', short: 'V', description: 'Enable verbose output' } }, // The 'ctx' parameter is automatically typed based on the args run: ctx => { // `ctx.values` is fully typed! const { name, age, verbose } = ctx.values // TypeScript knows the types: // - name: string | undefined (undefined if not provided) // - age: number (always a number due to the default) // - verbose: boolean | undefined (undefined if not provided, true if --verbose flag is used) let greeting = `Hello, ${name || 'stranger'}!` // age always has a value due to the default greeting += ` You are ${age} years old.` console.log(greeting) if (verbose) { console.log('Verbose mode enabled.') console.log('Parsed values:', ctx.values) } } }) // Execute the command await cli(process.argv.slice(2), command) ``` > \[!TIP] > The example fully code is [here](https://github.com/kazupon/gunshi/tree/main/playground/essentials/type-safe). With `define`: * You don't need to import types like `Command` or `CommandContext`. * The `ctx` parameter in the `run` function automatically gets the correct type, derived from the `args` definition. * Accessing `ctx.values.optionName` provides type safety and autocompletion based on the option's `type` and whether it has a `default`. * Options without a `default` (like `name`) are typed as `T | undefined`. * Options with a `default` (like `age`) are typed simply as `T`. * Boolean flags without a `default` (like `verbose`) are typed as `boolean | undefined`. > \[!NOTE] > For boolean options that need both positive and negative forms (e.g., `--verbose` and `--no-verbose`), see the [Negatable Boolean Options](./declarative.md#negatable-boolean-options) section in the declarative configuration guide. This approach significantly simplifies creating type-safe CLIs with Gunshi. > \[!TIP] > For even stronger type inference with composable argument schemas, check out [Parser Combinators](../advanced/parser-combinators.md). > Parser combinators provide functional factory functions like `string()`, `integer()`, `withDefault()`, and `required()` that automatically infer precise types without manual annotations. ## When to Use `define` Use the `define` function when: * You're writing TypeScript and want automatic type inference * You need IDE autocompletion for command context * You want to catch type-related errors at compile time Use plain objects (as shown in the previous chapter) when: * You're writing plain JavaScript * You prefer explicit type annotations * You're integrating with existing type definitions ## Advanced Type Parameters While the examples above show the simplest form of the `define` function, Gunshi provides more advanced type parameter patterns for complex scenarios: * **Plugin extensions**: Type-safe access to plugin-provided functionality * **Explicit argument types**: Fine-grained control over type inference * **GunshiParams utility**: Combined typing of arguments and extensions These advanced patterns are covered in detail in the [Advanced Type System](../advanced/type-system.md) documentation. For most commands, the basic `define` usage shown above provides sufficient type safety. ## Next Steps Now that you understand how to create type-safe commands with `define`, you're ready to explore more advanced features: * **Composable Sub-commands**: Learn how type safety extends to multi-command CLIs * **Plugin System**: Discover how plugins maintain type safety across extensions * **Advanced Type System**: For complex scenarios, Gunshi offers additional type parameters and patterns (covered in the [Advanced Type System](../advanced/type-system.md) documentation) In the next chapter, we'll explore how to create [composable sub-commands](./composable.md) while maintaining the type safety we've established here. --- --- url: /guide/essentials/composable.md --- # Composable Sub-commands In [the previous chapter](./type-safe.md), you learned how to create type-safe commands using the `define` function. Now, let's extend that knowledge to build CLIs with multiple sub-commands while maintaining the same type safety benefits. Gunshi's composable sub-command system allows you to create modular, organized CLIs similar to tools like Git (with commands like `git commit` and `git push`). ## Why Use Sub-commands? Sub-commands are useful when your CLI needs to perform different operations that warrant separate commands. Benefits include: * **Organization**: Group related functionality logically * **Scalability**: Add new commands without modifying existing ones * **User experience**: Provide a consistent interface for different operations * **Help system**: Each sub-command can have its own help documentation * **Plugin integration**: Plugins are shared across all sub-commands for consistent functionality ## Basic Structure A CLI with sub-commands typically has this structure: ```sh cli [command options] ``` For example: ```sh your-cli create --name my-resource ``` ## Creating Type-Safe Sub-commands Building on the `define` function from the previous chapter, let's create a CLI with multiple sub-commands: ```ts [cli.ts] import { cli, define } from 'gunshi' // Define type-safe sub-commands const createCommand = define({ name: 'create', description: 'Create a new resource', args: { name: { type: 'string', short: 'n', required: true } }, run: ctx => { // ctx.values is fully typed console.log(`Creating resource: ${ctx.values.name}`) } }) const listCommand = define({ name: 'list', description: 'List all resources', run: () => { console.log('Listing all resources...') } }) // Define the main command const mainCommand = define({ name: 'manage', description: 'Manage resources', run: ctx => { // This runs when no sub-command is provided console.log('Available commands: create, list') console.log('Run "manage --help" for more information') } }) // Run the CLI with composable sub-commands await cli(process.argv.slice(2), mainCommand, { name: 'my-app', version: '1.0.0', subCommands: { create: createCommand, list: listCommand } }) ``` > \[!TIP] > The example fully code is [here](https://github.com/kazupon/gunshi/tree/main/playground/essentials/composable/basic). This structure provides: * Full type safety for all commands and sub-commands * Automatic help generation for each command level * Shared configuration across the command hierarchy ## Automatic Help for Sub-commands Gunshi automatically generates help documentation for your sub-commands. Using the code from the previous section, you can see the help for each command level: ```sh # Show main command help $ npx tsx cli.ts --help # Show sub-command help $ npx tsx cli.ts create --help ``` > \[!TIP] > [`tsx`](https://github.com/privatenumber/tsx) is a TypeScript execution tool that allows you to run TypeScript files directly without compilation. Use it directly with `npx tsx`. > \[!NOTE] > On Node.js v22.6.0, you can run TypeScript with `--experimental-strip-types`: > > ```sh > node --experimental-strip-types cli.ts --help > ``` > > From Node.js v23.6.0 and newer, type stripping is enabled by default (no flag needed for erasable TS). Features requiring transformation (e.g., `enum`) still need `--experimental-transform-types`. Each sub-command's help includes its description, available options, and usage examples. ## Organizing Your Commands As your CLI grows, organizing commands in separate files improves maintainability. Here's a recommended project structure: ```sh my-cli/ โ”œโ”€โ”€ src/ โ”‚ โ”œโ”€โ”€ commands/ โ”‚ โ”‚ โ”œโ”€โ”€ create.ts # Create command implementation โ”‚ โ”‚ โ””โ”€โ”€ list.ts # List command implementation โ”‚ โ”œโ”€โ”€ main.ts # Main command definition โ”‚ โ””โ”€โ”€ cli.ts # CLI entry point โ”œโ”€โ”€ package.json โ””โ”€โ”€ tsconfig.json ``` > \[!TIP] > The example fully code is [here](https://github.com/kazupon/gunshi/tree/main/playground/essentials/composable/organizing). This structure provides: * Clear separation of concerns * Shared utilities across commands * Centralized type definitions * Easy testing of individual components ### Individual Command Files ```ts [commands/create.ts] import { define } from 'gunshi' export default define({ name: 'create', description: 'Create a new resource', args: { name: { type: 'string', short: 'n', required: true, description: 'Name of the resource' }, type: { type: 'string', short: 't', default: 'default', description: 'Type of resource' } }, run: ctx => { console.log(`Creating ${ctx.values.type} resource: ${ctx.values.name}`) } }) ``` ```ts [commands/list.ts] import { define } from 'gunshi' export default define({ name: 'list', description: 'List all resources', args: { filter: { type: 'string', short: 'f', description: 'Filter resources' } }, run: ctx => { const filter = ctx.values.filter || 'all' console.log(`Listing resources with filter: ${filter}`) } }) ``` ### Main Command File ```ts [main.ts] import { define } from 'gunshi' export default define({ name: 'manage', description: 'Manage resources', run: () => { console.log('Use a sub-command') console.log('Run "resource-manager --help" for available commands') } }) ``` ### Entry Point > \[!NOTE] > Some code examples in this guide include TypeScript file extensions (`.ts`) in import/export statements. If you use this pattern in your application, you'll need to enable `allowImportingTsExtensions` in your `tsconfig.json`. ```ts [cli.ts] import { cli } from 'gunshi' import main from './main.ts' import create from './commands/create.ts' import list from './commands/list.ts' await cli(process.argv.slice(2), main, { name: 'resource-manager', version: '1.0.0', subCommands: { create, list } }) ``` ## Handling Unknown Sub-commands By default, Gunshi shows an error when users provide an unknown sub-command. You can customize this behavior using the `fallbackToEntry` option: ```ts [cli.ts] await cli(process.argv.slice(2), main, { name: 'resource-manager', version: '1.0.0', fallbackToEntry: true, subCommands: { create, list } }) ``` This option enables flexible command handling: ```sh # Runs the create sub-command npx tsx src/cli.ts create --name resource resource-manager (resource-manager v1.0.0) Creating default resource: resource # Runs the list sub-command npx tsx src/cli.ts list --filter active resource-manager (resource-manager v1.0.0) Listing resources with filter: active # Falls back to main command when "unknown" sub-command is not found npx tsx src/cli.ts unknown --flag value resource-manager (resource-manager v1.0.0) Use a sub-command Run "resource-manager --help" for available commands # Runs the main command directly npx tsx src/cli.ts --help resource-manager (resource-manager v1.0.0) USAGE: resource-manager [COMMANDS] COMMANDS: [manage] Manage resources create Create a new resource list List all resources For more info, run any command with the `--help` flag: resource-manager --help resource-manager create --help resource-manager list --help OPTIONS: -h, --help Display this help message -v, --version Display this version ``` This approach is particularly useful for CLIs that: * Need to handle file paths or patterns as direct arguments * Want to provide a default action when no sub-command matches * Implement dynamic command resolution based on context ## Nested Sub-Commands Gunshi also supports nested sub-commands for building hierarchical command trees like `git remote add`. You can add a `subCommands` property to any command definition: ```ts [cli.ts] import { cli, define } from 'gunshi' const addCommand = define({ name: 'add', description: 'Add a remote', args: { url: { type: 'string', required: true } }, run: ctx => console.log(`Adding: ${ctx.values.url}`) }) const remoteCommand = define({ name: 'remote', description: 'Manage remotes', subCommands: { add: addCommand }, run: () => console.log('Use: remote add') }) const entry = define({ name: 'main', description: 'Git-like CLI', run: () => console.log('Run --help for available commands') }) await cli(process.argv.slice(2), entry, { name: 'git', subCommands: { remote: remoteCommand } }) ``` For more details on nested sub-commands, including lazy loading, intermediate command handling, and `commandPath`, see the [Nested Sub-Commands](../advanced/nested-sub-commands.md) guide. ## Next Steps Throughout this guide, you've learned how to build composable sub-commands that scale from simple to complex CLI applications. You've seen how Gunshi maintains type safety across nested command structures, enables powerful routing patterns with default commands, and supports both synchronous and asynchronous command execution. Now that you understand how to compose commands into well-organized hierarchies, you're ready to explore how to optimize their performance. The next section on [Lazy & Async Command Loading](./lazy-async.md) will show you how to significantly improve your CLI's startup time by loading commands only when they're actually needed. With composable sub-commands as your foundation, adding lazy loading will make your CLI applications both powerful and performant, especially as they grow to include many commands with varying resource requirements. --- --- url: /guide/essentials/lazy-async.md --- # Lazy & Async Command Loading > \[!NOTE] > This chapter continues using TypeScript for code examples, building upon the type safety concepts introduced in the [previous chapter](./type-safe.md). While all Gunshi features work with JavaScript, the TypeScript examples provide better IDE support and compile-time checking. When building CLI applications with many commands or commands that require heavy dependencies, you may encounter slow startup times that frustrate users. Even simple operations like displaying help text can take seconds if your CLI loads all command implementations upfront. This guide shows you how to use Gunshi's lazy loading and asynchronous execution features to create fast, responsive CLI applications that only load what they need, when they need it. ## Benefits of Lazy Loading Lazy loading provides immediate improvements to your CLI application: * **Faster Startup Time**: Commands are only loaded when actually executed, not when displaying help or running other commands * **Reduced Memory Usage**: Unexecuted commands and their dependencies stay unloaded, keeping memory footprint minimal * **Better Code Splitting**: When bundling your CLI, each command can be a separate chunk that loads on demand * **Improved User Experience**: Users see instant help text and quick responses for simple commands These benefits become especially valuable as your CLI grows to include dozens or hundreds of commands, each potentially requiring different libraries or complex initialization. ## Basic Lazy Loading Let's start with a simple example that demonstrates the core concept. The following code shows how to create a lazy-loaded command that only loads its implementation when executed: ```ts [cli.ts] import { cli, define, lazy } from 'gunshi' import type { CommandRunner, CommandContext } from 'gunshi' // Define the command without command runner separately const helloDefinition = define({ name: 'hello', description: 'A greeting command', args: { name: { type: 'string', description: 'Name to greet', default: 'world' } } }) // Create a loader function that returns the command implementation const helloLoader = async (): Promise => { console.log('Loading hello command runner ...') // The actual command runner logic is defined here const run = (ctx: CommandContext) => { console.log(`Hello, ${ctx.values.name}!`) } return run // Return the runner function } // Combine them using the lazy helper const lazyHello = lazy(helloLoader, helloDefinition) // Use the lazy command in your CLI const subCommands = { [lazyHello.commandName]: lazyHello } await cli( process.argv.slice(2), { description: 'My CLI application', run: () => console.log('Run a sub-command with --help for more info') }, { name: 'my-app', version: '1.0.0', subCommands } ) ``` > \[!TIP] > The example fully code is [here](https://github.com/kazupon/gunshi/tree/main/playground/essentials/lazy-async/basic). In this example, when a user runs `npx tsx cli.ts --help`, Gunshi displays help text using only the metadata from `helloDefinition`. The `helloLoader` function is never called. Only when the user runs `npx tsx cli.ts hello` does Gunshi execute the loader and run the command. When an option appears before a lazy command name, the second argument to `lazy()` must declare the option information needed to identify the command. Keep the routing fields (`type`, `short`, `negatable`, and `toKebab`) in the metadata; value conversion and validation still happen after the loader runs: ```ts const deploy = lazy(loadDeploy, { name: 'deploy', args: { config: { type: 'string', short: 'c' } } }) // Selects deploy, then loads it once. await cli(['--config', 'prod.json', 'deploy'], entry, { subCommands: { deploy } }) ``` If the loaded command changes the schema used to route a leading option, Gunshi reports a lazy schema-mismatch error instead of retrying another command. Moving the option after the command name is the compatibility path when the option is loader-only. > \[!TIP] > The command name is accessed via `commandName` property (not `name`) when using lazy commands. This is because lazy commands are functions, and the `name` property is reserved by the JavaScript runtime. A lazy command can also be used as the entry command without a definition name. Gunshi still uses the arguments from the definition and runs the loader before the returned runner: ```ts const entry = lazy( async () => ctx => { console.log(`Building ${ctx.values.target}`) }, { args: { target: { type: 'string', required: true } } } ) await cli(['--target', 'prod'], entry, { name: 'my-cli' }) ``` When a lazy command has no definition name, `commandName` is `undefined`. This is different from the CLI name in `CliOptions.name`; the latter identifies the application, while the lazy command is the default entry used when no sub-command is selected. ## Dynamic Imports for Code Splitting For real-world applications, you'll typically want to use dynamic imports to load command implementations from separate files. This enables bundlers to create separate chunks for each command: ```ts [cli.ts] import { cli, define, lazy } from 'gunshi' import type { CommandRunner } from 'gunshi' // Command metadata stays in the main bundle const buildDefinition = define({ name: 'build', description: 'Build the project', args: { watch: { type: 'boolean', short: 'w', description: 'Watch for changes' }, minify: { type: 'boolean', short: 'm', description: 'Minify output' } } }) // Loader uses dynamic import to load the command from a separate file const buildLoader = async (): Promise => { // This creates a separate chunk in your bundle const { run } = await import('./commands/build.ts') return run } const lazyBuild = lazy(buildLoader, buildDefinition) // Add more commands following the same pattern const deployDefinition = define({ name: 'deploy', description: 'Deploy to production', args: { environment: { type: 'string', short: 'e', default: 'production' } } }) const deployLoader = async (): Promise => { const { run } = await import('./commands/deploy.ts') return run } const lazyDeploy = lazy(deployLoader, deployDefinition) // Register all lazy commands const subCommands = { [lazyBuild.commandName]: lazyBuild, [lazyDeploy.commandName]: lazyDeploy } await cli( process.argv.slice(2), { description: 'Development tools CLI', run: () => console.log('Use --help to see available commands') }, { name: 'dev-tools', version: '2.0.0', subCommands } ) ``` > \[!TIP] > The example fully code is [here](https://github.com/kazupon/gunshi/tree/main/playground/essentials/lazy-async/dynamic-import). > \[!IMPORTANT] > The examples above use `.ts` extensions in dynamic import paths (`await import('./commands/build.ts')`). This will work in the following scenarios: > > * **During development**: When using TypeScript execution tools like `tsx`, `ts-node`, or Bun's native TypeScript support > * **Node.js with experimental support**: When running Node.js v22.6.0+ with the `--experimental-strip-types` flag (renamed to `--experimental-transform-types` in v22.7.0+) > > **For production builds with TypeScript compilation**: > TypeScript does NOT rewrite import paths during compilation. If you plan to compile your TypeScript: > > * Use `.js` extensions in your source code: `await import('./commands/build.js')` > * These `.js` imports will correctly resolve to the compiled JavaScript files > * Alternatively, configure your bundler (rolldown, rollup, esbuild, webpack) to handle `.ts` extensions > > For standard JavaScript projects, use `.js` extensions: > > ```ts > const { run } = await import('./commands/build.js') > ``` With this approach, your bundler (rolldown, rollup, esbuild, webpack, etc.) will automatically create separate chunks for `./commands/build.ts` and `./commands/deploy.ts`. Users only download and parse the code for commands they actually use. > \[!NOTE] > On Node.js v23.6.0 and newer, type stripping is enabled by default so erasable TypeScript runs without flags; transformations (e.g., `enum`) still require `--experimental-transform-types`. ## Async Command Execution Gunshi seamlessly supports asynchronous command execution, which is essential for commands that perform I/O operations, network requests, or other async tasks. Building on lazy loading, your command runners can be async functions: ```ts [cli.ts] import { cli, define, lazy } from 'gunshi' import type { Command, CommandContext, CommandRunner, GunshiParams } from 'gunshi' // Mock implementations that simulate async file operations // These work without requiring actual files on disk async function readFile(path: string): Promise { // Simulate async file read with a small delay await new Promise(resolve => setTimeout(resolve, 100)) return `Sample data from ${path}` } async function transform(data: string): Promise { // Simulate async data transformation await new Promise(resolve => setTimeout(resolve, 150)) return data.toUpperCase() + '\n[TRANSFORMED]' } async function writeFile(path: string, data: string): Promise { // Simulate async file write await new Promise(resolve => setTimeout(resolve, 100)) console.log(` Written to ${path}: "${data}"`) } // Alternative: Use real Node.js file operations // import { readFile as fsReadFile, writeFile as fsWriteFile } from 'node:fs/promises' // // async function readFile(path: string): Promise { // return await fsReadFile(path, 'utf-8') // } // // async function transform(data: string): Promise { // // Your actual transformation logic here // return data.toUpperCase() // } // // async function writeFile(path: string, data: string): Promise { // await fsWriteFile(path, data, 'utf-8') // } const processDataDefinition = define({ name: 'process', description: 'Process data asynchronously', args: { input: { type: 'string', short: 'i', description: 'Input file path', required: true }, output: { type: 'string', short: 'o', description: 'Output file path', required: true } } }) const processDataLoader = async (): Promise< CommandRunner> > => { // Return an async runner function return async ctx => { // TypeScript knows ctx.values has 'input' (string) and 'output' (string) const { input, output } = ctx.values console.log(`Processing ${input}...`) // Simulate async operations const data = await readFile(input) const processed = await transform(data) await writeFile(output, processed) console.log(`Successfully processed to ${output}`) } } const lazyProcess = lazy(processDataLoader, processDataDefinition) // The CLI handles async execution automatically const subCommands = { [lazyProcess.commandName]: lazyProcess } await cli( process.argv.slice(2), { description: 'Data processing CLI', run: () => console.log('Use process command to transform data') }, { name: 'data-cli', version: '1.0.0', subCommands } ) ``` > \[!TIP] > The example fully code is [here](https://github.com/kazupon/gunshi/tree/main/playground/essentials/lazy-async/async). Gunshi automatically handles the asynchronous execution, including proper error handling and process exit codes. You don't need any special configurationโ€”just return an async function from your loader. ## Alternative Loader Return Types While the examples above show loaders returning runner functions, loaders can also return full Command objects. This is useful when you want to dynamically construct the entire command structure: ```ts [cli.ts] import { cli, define, lazy } from 'gunshi' const configLoader = async () => { // Simulate loading configuration (in practice, read from file/API) const isDebug = process.env.DEBUG === 'true' return define({ description: `Config command (debug: ${isDebug})`, args: { verbose: { type: 'boolean', description: isDebug ? 'Verbose output (DEBUG mode)' : 'Verbose output' } }, run: ctx => { console.log(isDebug ? 'Running in DEBUG mode' : 'Running in normal mode') if (ctx.values.verbose) { console.log('Verbose:', ctx.values.verbose) } } }) } // The definition (2nd parameter) is used without calling the loader, e.g. for the command list. // Once loaded, the loaded command takes precedence, and the definition is the fallback const config = lazy(configLoader, { name: 'config', description: 'Dynamically configured command' }) await cli( process.argv.slice(2), { description: 'CLI with dynamic commands', run: () => {} }, { name: 'my-cli', version: '1.0.0', subCommands: { config } } ) ``` > \[!TIP] > The example fully code is [here](https://github.com/kazupon/gunshi/tree/main/playground/essentials/lazy-async/dynamic-command) This pattern is particularly useful when command structure depends on runtime configuration or external data sources. When the loader returns a Command object, Gunshi resolves the command from both the loaded command and the definition passed as the second parameter of `lazy`: * **The loaded command takes precedence**: A property that the loaded command defines is used, even if the definition has it too * **The definition is the fallback**: A property that the loaded command does not define is taken from the definition, so a loader that returns only `{ run }` behaves the same as a loader that returns the runner function * **Properties are not merged**: If both of them define a property such as `args` or `rendering`, the whole value of the loaded command is used In the example above, `my-cli config --help` shows the description `Config command (debug: false)` and the `--verbose` option, which come from the loaded command. On the other hand, `my-cli --help` lists the command as `Dynamically configured command`, because the command list is rendered from the definition without calling the loader. ## Performance Considerations To maximize the benefits of lazy loading: 1. **Keep loaders lightweight**: The loader function itself should be minimal. Put heavy imports and initialization inside the returned runner function or use dynamic imports. 2. **Group related commands**: If multiple commands share dependencies, consider loading them together to avoid redundant imports. 3. **Monitor bundle sizes**: Use your bundler's analysis tools to verify that commands are properly code-split into separate chunks. 4. **Consider startup frequency**: Commands that are frequently used together might benefit from being in the same chunk, while rarely-used commands should definitely be lazy-loaded. ## Next Steps Now that you understand lazy loading and async execution in Gunshi, you've learned how to optimize your CLI's performance by loading commands only when needed. You can create fast, responsive CLIs that handle complex asynchronous operations efficiently while keeping startup times minimal. The next section on [Auto Usage Generation](./auto-usage.md) will show you how Gunshi automatically creates comprehensive help documentation for all your commandsโ€”including lazy-loaded ones. You'll learn how to enhance your CLI's user experience with well-structured usage information, examples, and command descriptions. With lazy loading keeping your CLI fast and auto-generated usage making it user-friendly, you'll have all the essential tools to build professional command-line applications with Gunshi. --- --- url: /guide/essentials/auto-usage.md --- # Auto Usage Generation Gunshi automatically generates comprehensive usage information for your commands, ensuring your CLI is self-documenting and user-friendly without additional effort. ## How Auto Usage Works When you define a command with Gunshi, the library automatically: * Adds `--help` and `-h` flags to every command * Adds `--version` and `-v` flags when you provide a version in the CLI options * Generates formatted usage text based on your command configuration * Displays usage information when users request help or provide invalid arguments * Includes descriptions, examples, and type information you provide The generated usage follows standard CLI conventions, making your application immediately familiar to users. ## Understanding Bracket Notation Gunshi uses consistent bracket notation throughout the generated usage to indicate whether elements are required or optional: ### Angle Brackets `<>` - Required Elements Angle brackets indicate required elements or parameters without default values: * `` - The command has required options (at least one option without a default value) * `` - An option parameter that must be provided (no default value) * `` - A required positional argument Example: ```sh USAGE: app OPTIONS: -n, --name Name to use (required) ``` ### Hiding Internal Options If you need to support options for backward compatibility or internal workflows without showing them in `--help`, set `hidden: true` on the argument schema (or apply the `hidden()` combinator). The option continues to be accepted and parsed at runtime, but it is omitted from the `OPTIONS` section and usage symbols. It is left out of the other places a user could discover it, too: shell completion does not offer it and does not complete its value, and the `Did you mean` hint for an unknown option does not suggest it. ### Square Brackets `[]` - Optional Elements Square brackets indicate optional elements or parameters with default values: * `[name]` - An option parameter with a default value * `[]` - An optional positional argument * `[ ...]` - Optional multiple positional arguments * `[COMMANDS]` - Sub-command selection (when multiple commands exist) * `[commandName]` - A default command that runs when no sub-command is specified Example: ```sh USAGE: app [COMMANDS] COMMANDS: [manage] Default command for managing resources OPTIONS: -t, --type [type] Resource type (default: standard) ``` This notation provides immediate visual feedback about what users must provide versus what they can omit, making your CLI more intuitive to use. > \[!TIP] > Auto-usage generation is powered by the `@gunshi/plugin-renderer` plugin, which is automatically included when you use the standard `cli()` function. This plugin handles all help text rendering and formatting. ## Generated Help Output Gunshi transforms your command definitions into professional help documentation. Here's what users see when they request help. ### Basic Command Help For a simple command with basic configuration (see [Getting Started](./getting-started.md) for implementation details), running with `--help` displays: ```sh A greeting CLI (greet-cli v1.0.0) USAGE: greet-cli OPTIONS: -h, --help Display this help message -v, --version Display this version -n, --name Name to greet -u, --uppercase Convert greeting to uppercase ``` > \[!NOTE] > The `--help` flag is automatically added - you never need to define it manually. ### Help with Arguments and Examples When your command includes argument definitions and examples (see [Declarative Configuration](./declarative.md) for how to define these), the generated help becomes more comprehensive: ```sh app (app v1.0.0) USAGE: app OPTIONS: -p, --path File or directory path to operate on -r, --recursive Operate recursively on directories --no-recursive Negatable of -r, --recursive -o, --operation Operation to perform: list, copy, move, or delete -h, --help Display this help message -v, --version Display this version EXAMPLES: # List files in current directory $ app --operation list # Copy files recursively $ app --operation copy --path ./source --recursive # Delete files $ app --operation delete --path ./temp ``` > \[!IMPORTANT] > Boolean options automatically receive a negatable version with the `--no-` prefix. This allows users to explicitly disable boolean flags. ## Customizing Help Output ### Displaying Option Types Enable type display in the usage output to help users understand what value each option expects: ```js await cli(process.argv.slice(2), command, { name: 'app', version: '1.0.0', usageOptionType: true // Enable type display }) ``` This adds type information to each option: ```sh OPTIONS: -p, --path [string] File or directory path to operate on -r, --recursive [boolean] Operate recursively on directories --no-recursive [boolean] Negatable of -r, --recursive -o, --operation [string] Operation to perform: list, copy, move, or delete -h, --help [boolean] Display this help message -v, --version [boolean] Display this version ``` ## Sub-command Help Generation For CLIs with sub-commands (see [Composable Sub-commands](./composable.md) for implementation), Gunshi generates hierarchical help documentation. ### Main Command Help When users run the main command with `--help`: ```sh resource-manager (resource-manager v1.0.0) USAGE: resource-manager [COMMANDS] COMMANDS: [manage] Manage resources create Create a new resource list List all resources For more info, run any command with the `--help` flag: resource-manager --help resource-manager create --help resource-manager list --help OPTIONS: -h, --help Display this help message -v, --version Display this version ``` > \[!NOTE] > The brackets in `[manage]` indicate it's the default command that runs when no sub-command is specified. > Each row describes the arguments that its own command declares, so a command with no options of its own > has no `` next to it. ### Sub-command Specific Help Each sub-command has its own help, accessible via `command --help`. The below `create --help`: ```sh resource-manager (resource-manager v1.0.0) Create a new resource USAGE: resource-manager create OPTIONS: -h, --help Display this help message -v, --version Display this version -n, --name Name of the resource -t, --type [type] Type of resource (default: default) ``` ### Positional Arguments Display When commands accept positional arguments (arguments without flags), they appear in the `ARGUMENTS` line: ```sh resource-manager (resource-manager v1.0.0) Manage resources USAGE: resource-manager manage ARGUMENTS: resource Type of resource to manage (e.g., user, project) OPTIONS: -h, --help Display this help message -v, --version Display this version ``` Positional arguments are displayed with clear, descriptive names that indicate their purpose. Required positional arguments use angle brackets (e.g., ``), while optional positional arguments use square brackets (e.g., `[]`). Multiple positional arguments are shown as `[ ...]`, or ` [ ...]` when at least one value is required. ## Automatic Features Gunshi provides several automatic features without requiring any configuration: ### Help Flag (`--help`, `-h`) * Automatically added to all commands * Displays usage information and exits * Works at every command level (main and sub-commands) ### Version Flag (`--version`, `-v`) * Automatically added when you provide a `version` in CLI options * Displays the version and exits * Available at all command levels ### Negatable Boolean Options * Boolean options automatically get `--no-` prefixed versions * Allows explicit disabling of boolean flags * Example: `--recursive` automatically creates `--no-recursive` ### Invalid Argument Handling * Usage is automatically displayed when users provide invalid arguments * Helps users understand what went wrong * Provides immediate guidance for correct usage ## Key Points When working with auto-generated usage: * The `--help` flag is automatically added to all commands - you don't need to define it * Usage is displayed when users provide invalid arguments or explicitly request help * Descriptions in your `args` configuration become the help text for each option * The `examples` field accepts both single strings and multi-line strings for multiple examples * Sub-command help is accessible both from the main help and individually via `command --help` * Required options are shown with angle brackets `