# Introduction to Cliffy Cliffy is a powerful and versatile library designed to help you create interactive command-line tools with ease. It offers a range of modules that simplify the process of building complex command-line interfaces and feature-rich CLI prompts. Cliffy is platform-independent and works seamlessly with [Deno](https://deno.com/), [Node.js](https://nodejs.org/en) and [bun](https://bun.sh/). ## Key Modules - **[command](https://cliffy.io/docs/v1.3.1/command/index.md)**: This module allows you to create sophisticated command-line interfaces effortlessly. It provides a robust framework for defining commands, options, and arguments, making it easy to build and manage CLI applications. - **[prompt](https://cliffy.io/docs/v1.3.1/prompt/index.md)**: The prompt module offers a variety of beautiful and highly customizable CLI prompts. It supports different types of prompts such as input, select, toggle, and more, enabling you to create interactive and user-friendly command-line experiences. ## Getting Started To get started with Cliffy, you can explore the [command](https://cliffy.io/docs/v1.3.1/command/index.md) and [prompt](https://cliffy.io/docs/v1.3.1/prompt/index.md) modules. These modules provide comprehensive documentation and examples to help you understand how to use Cliffy effectively. ## Version Information > Cliffy is exclusively published on [jsr.io](https://jsr.io/@cliffy) starting > from version > [v1.0.0-rc.5](https://github.com/c4spar/deno-cliffy/releases/tag/v1.0.0-rc.5). > The latest version available on deno.land is > [v1.0.0-rc.4](https://deno.land/x/cliffy@v1.0.0-rc.4). > > Please note that versions > [v1.0.0-rc.6](https://deno.land/x/cliffy@v1.0.0-rc.6) and > [v1.0.0-rc.7](https://deno.land/x/cliffy@v1.0.0-rc.7) published on deno.land > are not functional and should only be used from > [jsr.io](https://jsr.io/@cliffy). --- # Command The command module supports type safe options and arguments, input validation, auto generated help, built-in shell completions, and more. ## Installation ### Deno ```bash deno add jsr:@cliffy/command ``` ### Pnpm ```bash pnpm add jsr:@cliffy/command ``` or (using pnpm 10.8 or older): ```bash pnpm dlx jsr add @cliffy/command ``` ### Yarn ```bash yarn add jsr:@cliffy/command ``` or (using Yarn 4.8 or older): ```bash yarn dlx jsr add @cliffy/command ``` ### Vlt ```bash vlt install jsr:@cliffy/command ``` ### Npm ```bash npx jsr add @cliffy/command ``` ### Bun ```bash bunx jsr add @cliffy/command ``` ## Usage To create a program with cliffy you can import the `Command` class from the main module `@cliffy/command`. The `Command` class is used for main and sub-commands. The main command has two predefined options, a global help option (`-h, --help`) which is also available on all child commands and a version option (`-V, --version`) which is only available on the main command. All methods from the command class are chainable. ### The main command You should start with creating a main command and adding a name, version and description to it. It is required to define the name for your main command manually. ```typescript import { Command } from "@cliffy/command"; await new Command() .name("cliffy") .version("0.1.0") .description("Command line framework for Deno") .parse(); ``` You can run this example and print the auto-generated help by executing the following command: ```console $ deno run examples/command/usage.ts --help ``` This shows you the default help if no additional options and arguments are defined. ![](https://cliffy.io/docs/v1.3.1/command/assets/img/usage.gif) ### Defining your command You can then add options, environment variables, arguments and custom types to your command as many you want. Environment variables will be merged into the options object. They have the same naming as options. For example the option `--template-engine` and the environment variable `TEMPLATE_ENGINE` will both renamed to `templateEngine`. If both are defined at the same time the option will override the value of the environment variable. The `action` method allows you to define a callback function that will be called when the command is executed. Options and environment variables are passed as first argument to the action handler, followed by the command arguments in the same order they were defined with the `arguments` method. > [!NOTE] > Cliffy infers all types and names from all option, argument and environment > variable definitions automatically and applies them properly to the types of > the options object and arguments array 🚀. Here is an example of a simple command with some options, arguments, environment variables and types. ```typescript import { Command, EnumType } from "@cliffy/command"; const logLevelType = new EnumType(["debug", "info", "warn", "error"]); await new Command() .name("cliffy") .version("0.1.0") .description("Command line framework for Deno") .type("log-level", logLevelType) .env("DEBUG=", "Enable debug output.") .option("-d, --debug", "Enable debug output.") .option("-l, --log-level ", "Set log level.", { default: "info", }) .arguments(" [output:string]") .action((options, ...args) => {}) .parse(); ``` The type of the options object will look like this: ```ts type Options = { debug?: boolean | undefined; logLevel: "debug" | "info" | "warn" | "error"; }; ``` and the type of the arguments array will look like this: ```ts type Arguments = [string, (string | undefined)?]; ``` ### Sub commands and globals Sub commands can be added with the `command` method. You can add as many sub commands you want. There is no limit for the maximum number of sub and nested sub commands. The `.command()` method always returns the newly created subcommand. All other methods return either the main command (if the `.command()` method has not been called yet) or the last created subcommand. This means that after calling the `.command()` method to add a new subcommand, options, arguments, etc. will be added to the newly created subcommand and no longer to the main command. You can read more about sub commands [here](https://cliffy.io/docs/v1.3.1/command/sub_commands.md). ```typescript import { Command } from "@cliffy/command"; await new Command() // Main command. .name("cliffy") .version("0.1.0") .description("Command line framework for Deno") .globalOption("-d, --debug", "Enable debug output.") .action((options, ...args) => console.log("Main command called.")) // Child command 1. .command("foo", "Foo sub-command.") .option("-f, --foo", "Foo option.") .arguments("") .action((options, ...args) => console.log("Foo command called.")) // Child command 2. .command("bar", "Bar sub-command.") .option("-b, --bar", "Bar option.") .arguments(" [output:string]") .action((options, ...args) => console.log("Bar command called.")) .parse(); ``` > To make types, options and environment variables also available on child > commands you can use the [.globalOption()](https://cliffy.io/docs/v1.3.1/command/options.md#global-options), > [.globalEnv()](https://cliffy.io/docs/v1.3.1/command/environment_variables.md#global-environment-variables) and > [.globalType()](https://cliffy.io/docs/v1.3.1/command/types.md#global-types) methods. The types of `options` and `args` for the 3 action handlers will look like this. _Main command:_ ```ts type Options = { debug?: boolean | undefined; logLevel: "debug" | "info" | "warn" | "error"; }; type Args = []; ``` _Foo command:_ ```ts type Options = { debug?: boolean | undefined; logLevel: "debug" | "info" | "warn" | "error"; foo?: true | undefined; }; type Args = [string]; ``` _Bar command:_ ```ts type Options = { debug?: boolean | undefined; logLevel: "debug" | "info" | "warn" | "error"; bar?: true | undefined; }; type Args = [string, (string | undefined)?]; ``` --- # Commands The command class is used to create main and sub commands. All methods from the command class are chainable and return the current command instance. There are only two exceptions: - The `.command()` method returns the new created sub command, so you can add options, argument, environment variables and types to your sub commands in a chainable way. - The [`.reset()`](#reset) method returns the main command from your current command chain. ## Name With the `.name()` method you can define the name of your main command. The name should match the name of your program. It is displayed in the auto generated help and used for shell completions. ## Version With the `.version()` method you can define the version of your cli. The version is displayed in the auto generated help and the [version](https://cliffy.io/docs/v1.3.1/command/help.md#version-option) option. ## Description The `.description()` method adds a description for the command that will be displayed in the auto generated help. If the help option is called with the short flag `-h` only the first line is displayed. If called with the long name `--help`, the full description is displayed. For better multiline formatting, unnecessary indentations and empty leading and trailing lines will be automatically removed. For example, following description: ```ts import { Command } from "@cliffy/command"; new Command() .description(` This is a multiline description. The indentation of this line will be preserved. `); ``` is formatted as follows: ```console This is a multiline description. The indentation of this line will be preserved. ``` ## Usage With the `.usage()` method you can override the usage text that is displayed at the top of the auto generated help which defaults to the command arguments. The usage is always prefixed with the command name. ```ts import { Command } from "@cliffy/command"; await new Command() .name("script-runner") .description("Simple script runner.") .usage("[options] [script] [script options]") .parse(); ``` ## Arguments You can use the `.arguments()` and `.argument()` methods to specify the arguments for your commands. Angled brackets (e.g. ``) indicate required input and square brackets (e.g. `[optional]`) indicate optional input. A required input cannot be defined after an optional input. Arguments can be also defined with the `.command()` method. You can read more about the `.command()` method [here](#sub-commands). Optionally you can define [types](https://cliffy.io/docs/v1.3.1/command/types.md) and [completions](https://cliffy.io/docs/v1.3.1/command/shell_completions.md) for your arguments. If no type is specified the type defaults to `string`. ### `.arguments()` The arguments method accepts a string with space separated arguments. Optionally you can provide an array with descriptions for each argument. ```typescript import { Command } from "@cliffy/command"; const { args } = await new Command() .arguments(" [output:string]", [ "The input file.", "The output file.", ]) .parse(); ``` ```console $ deno run examples/command/arguments_syntax.ts Error: Missing argument(s): input ``` ### `.argument()` The argument method can be called multiple times to define arguments one by one. You can also define a default value and value handler for the argument. The value handler is a function that is called with the argument value and should return the processed value or a promise that resolves to the processed value. ```typescript import { Command } from "@cliffy/command"; const { args } = await new Command() .argument("", "The input file.") .argument("[output:string]", "The output file.", { default: "out.txt", value: (value: string) => Promise.resolve({ path: value }), }) .action((_, input, output) => { console.log("input:", input); // "input: " console.log("output:", output); // "output: { path: 'out.txt' }" }) .parse(); ``` ### Empty argument values An empty value means the argument was not provided, the same rule [options](https://cliffy.io/docs/v1.3.1/command/options.md#empty-values) follow. An optional argument falls back to its default value, and a required argument is rejected, because skipping it would shift every argument after it. ```typescript import { Command } from "@cliffy/command"; const { args } = await new Command() .argument("", "The input file.") .argument("[output:string]", "The output file.", { default: "out.txt" }) .parse(); console.log(args); ``` ```console $ deno run example.ts in.txt "" [ "in.txt", "out.txt" ] $ deno run example.ts "" out.txt Error: Missing argument: input ``` For a [variadic argument](#variadic-arguments) the position does not matter, so empty values are dropped from the list instead. `` still requires at least one non-empty value. ### Variadic arguments The last argument of a command can be variadic. To make an argument variadic you can append or prepend `...` to the argument name (`<...NAME>` or ``). Required rest arguments `<...args>` requires at least one argument, optional rest args `[...args]` are completely optional. ```typescript import { Command } from "@cliffy/command"; const { args: dirs } = await new Command() .description("Remove directories.") .arguments("") .action((_, ...rest) => console.log(`removing ${rest}`)) .parse(); for (const dir of dirs) { console.log("rmdir %s", dir); } ``` ```console $ deno run examples/command/variadic_arguments.ts dir1 dir2 dir3 removing dir1,dir2,dir3 rmdir dir1 rmdir dir2 rmdir dir3 ``` ## Option Options can be added with the `.option()` method. The first two arguments of the method are required. With the first argument you specify the flags and arguments of your option. The second argument is the description and with the third argument you can set some option specific settings. Optionally you can define [types](https://cliffy.io/docs/v1.3.1/command/types.md) and [completions](https://cliffy.io/docs/v1.3.1/command/shell_completions.md) for the arguments of the option. If no type is specified the type defaults to `string`. If no argument is specified, the type defaults to `true`. You can read more about options [here](https://cliffy.io/docs/v1.3.1/command/options.md). ```typescript import { Command } from "@cliffy/command"; const { args } = await new Command() .option("-f, --file ", "Force option.") .parse(); ``` ## Action handler The action handler is called when the command is executed and can be registered with the `.action()` method. The first arguments is the options object, which contains all options and environment variables, followed by all arguments, in the same order in which they were defined with the `.arguments()` method. Options and arguments will be automatically typed by inferring the types and names of all options, arguments and environment variables 🚀. ```typescript import { Command } from "@cliffy/command"; await new Command() .name("rm") .description("Remove directory.") .option("-r, --recursive", "Remove directory recursively.") .arguments("") .action(({ recursive }, dir) => { console.log("remove " + dir + (recursive ? " recursively" : "")); }) .parse(); ``` ```console $ deno run examples/command/action_handler.ts rm dir remove dir ``` ## Global action handler A global action handler works similar to the normal action handler but is called when the command or any child command is executed and can be registered with the `.globalAction()` method. ## Use raw args When `.useRawArgs()` is called, all options and arguments are passed as raw arguments to the action handler without validation. ```typescript import { Command } from "@cliffy/command"; await new Command() .option("-f, --foo ", "Foo option.") .option("-b, --bar ", "Bar option.") .useRawArgs() // <-- enable raw args .action((options, ...args) => { console.log("options:", options); console.log("args:", args); }) .parse(); ``` ```console $ deno run examples/command/use_raw_args.ts --foo abc --bar xyz options: {} args: [ "--foo", "abc", "--bar", "xyz" ] ``` ## Stop early If enabled, all arguments starting from the first non option argument will be interpreted as raw argument. ```typescript import { Command } from "@cliffy/command"; await new Command() .option("-d, --debug-level ", "Debug level.") .arguments("[script] [...args]") .stopEarly() // <-- enable stop early .action((options, script?, ...args) => { console.log("options:", options); console.log("script:", script); console.log("args:", args); }) .parse(); ``` ```console $ deno run examples/command/stop_early.ts -d warning server -p 80 options: { debugLevel: "warning" } script: server args: [ "-p", "80" ] ``` ## Sub commands With the `.command()` method you can add and nest sub commands as many as you want. The first argument is the command name with optional arguments. The second argument is optional and can be either the description of the command or an instance of `Command`. You can read more about sub commands [here](https://cliffy.io/docs/v1.3.1/command/sub_commands.md). ```typescript import { Command } from "@cliffy/command"; await new Command() .command("foo [val:string]") .description("Foo command.") .action(() => console.log("Foo action.")) .command("bar [val:string]", "Foo command.") .action(() => console.log("Bar action.")) .parse(); ``` ## Reset All methods are applied to the command that is currently selected in the chain. The `.command()` method selects the new sub command, so every method that follows is registered on that sub command and not on your main command. With the `.reset()` method you can select the main command of the chain again. ```typescript import { Command } from "@cliffy/command"; await new Command() .option("-f, --foo", "Registered on the main command.") .command("bar", "Bar command.") .option("-b, --baz", "Registered on the bar command.") .reset() .option("-q, --qux", "Registered on the main command.") .parse(); ``` The `.command()` method itself always adds the new sub command to the main command of the chain, so chained `.command()` calls create sibling commands. To nest sub commands, pass a `Command` instance as second argument, as shown in [sub commands](https://cliffy.io/docs/v1.3.1/command/sub_commands.md#command-instance). This is a common source of confusion with [global options](https://cliffy.io/docs/v1.3.1/command/options.md#global-options-and-method-chaining), which are registered on the selected command as well. ## Global commands To share commands with sub commands you can use the `.global()` method. ```typescript import { Command } from "@cliffy/command"; await new Command() .command("global [val:string]", "global ...") .global() .action(console.log) .command( "command1", new Command() .description("Some sub command.") .command( "command2", new Command() .description("Some nested sub command."), ), ) .parse(); ``` ```console $ deno run examples/command/global_commands.ts command1 command2 global test {} test ``` ### Disable globals With the `.noGlobals()` method you can disable inheriting global _commands_, _options_ and _environment variables_ from parent commands. > The built-in `--help` option and the `help` command are excluded from the > `.noGlobals()` method. ```ts import { Command } from "@cliffy/command"; await new Command() .globalOption("--beep", "Beep...") .command("foo", "Foo...") .command("bar", "Bar...") // disable global --foo option and all other globals for command bar: .noGlobals() .parse(); ``` ## Hidden commands To exclude sub commands from the auto generated help and shell completions you can use the `.hidden()` method. ```typescript import { Command } from "@cliffy/command"; await new Command() .command("debug", "Some internal debugging command.") .hidden() .parse(); ``` ```console $ deno run examples/command/hidden_commands.ts -h ``` ## Command alias With the `.alias()` method you can define an alias for the command name. ```ts import { Command } from "@cliffy/command"; await new Command() .command("install") .alias("i") .action(() => console.log("Install...")) .parse(); ``` ## Parse The `.parse()` method processes all arguments, leaving any options and environment variables consumed by the command in the `options` object, all arguments in the `args` array and all arguments defined after the double dash (`--`) in the `literal` array. For all unknown or invalid options, arguments and types the command will throw an error and exit the program with `Deno.exit(1)`. Read more about error handling [here](https://cliffy.io/docs/v1.3.1/command/error_handling.md). The parse method accepts optionally as first argument an array of command-line arguments that should be consumed. By default `Deno.args`, `process.argv.slice(2)` or `Bun.argv.slice(2)` is used, depending on the runtime. ```ts import { Command } from "@cliffy/command"; const { args, options, literal, cmd } = await new Command() .env("DEBUG", "Enable debugging.") .option("--debug", "Enable debugging.") .arguments("") .parse(Deno.args); ``` All types and names for options, arguments and environment variables are automatically inferred and properly typed. If the command has sub commands, options and arguments will be of type `Record` and `Array`, because cliffy can not know which sub command was executed to provide the specific types. In this case use action handlers. --- # Sub commands Sub commands can be added using the `.command()` method. The first argument specifies the name and optionally arguments for your sub command. The arguments may be `` or `[optional]` and the last argument may also be variadic. The second argument of the `.command()` method is optional and can be either the command description or an instance of a `Command` class. The description can be also defined with the `.description()` method. > [!NOTE] > The `.command()` method returns the instance of the added command. Any method > that is called after the `.command()` method, for example `.action()`, > `.option()` or `.globalOption()`, is registered on the sub command and not on > your main command. You can use the [`.reset()`](https://cliffy.io/docs/v1.3.1/command/commands.md#reset) method to > get the instance of the parent command back. There are three ways to specify sub-commands with the `.command()` method which are explained in the following section. ## Chained commands Sub-command implemented using the `.command()` method with an action handler. ```typescript import { Command } from "@cliffy/command"; await new Command() .command( "clone [destination:string]", "Clone a repository into a newly created directory.", ) .option("-r, --recursive", "Clone recursive.") .action(({ recursive }, source, destination?) => { console.log( "clone %s to %s", source, destination, recursive ? " (recursive)" : "", ); }) .parse(); ``` ## Command instance The command method accepts as second argument an instance of a command. This way you can move your sub-commands into different files. ```typescript import { Command } from "@cliffy/command"; const clone = new Command() .arguments(" [destination:string]") .description("Clone a repository into a newly created directory.") .action((options, source, destination?) => { console.log("clone command called"); }); await new Command() .command("clone", clone) .parse(); ``` ## Lazy loading The `.command()` method also accepts a function that returns a command, a promise of a command, or a module with the command as its default export. The function is called the first time the sub-command is needed, and only once, so its module is only imported when it is used. ```typescript import { Command } from "@cliffy/command"; await new Command() .command("clone", () => import("./commands/clone.ts")) .command("push", () => import("./commands/push.ts")) .parse(); ``` ```typescript // commands/clone.ts import { Command } from "@cliffy/command"; export default new Command() .arguments(" [destination:string]") .description("Clone a repository into a newly created directory.") .action((options, source, destination?) => { console.log("clone command called"); }); ``` Running a sub-command loads only that sub-command. Printing the help loads all of them, because their descriptions are part of the output. Methods chained after the `.command()` method are registered on the sub-command and applied on top of the lazy loaded command, so it can be configured without importing its module. ```typescript import { Command } from "@cliffy/command"; await new Command() .command("clone", () => import("./commands/clone.ts")) .description("Clone a repository.") .alias("cp") .parse(); ``` > [!NOTE] > Settings defined this way win over the ones from the lazy loaded command. > Aliases from both are merged. ### Returning a command directly The function can also return a command instead of a module, which defers building it without moving it into another file: ```typescript import { Command } from "@cliffy/command"; await new Command() .command("clone", () => new Command() .arguments(" [destination:string]") .description("Clone a repository into a newly created directory.") .action(() => {})) .parse(); ``` ### When lazy loading helps Lazy loading makes running a command faster, because only the commands on its path are loaded. The rest of the command tree is never built: - A function returning a command is only called when the command is needed, so the commands of every other branch are not built. The saving grows with the size of the whole tree and with how much each command defines, such as options, arguments, environment variables and examples. It also pays off when building a command is expensive, for example when it reads a config file or derives options from a schema. - A dynamic import saves the same, and additionally skips loading the code of the module and its dependencies. This saves the most when commands pull in large dependencies that other commands do not need. It has two costs: - Sub-commands at the same level load concurrently, but a lazy sub-command nested inside another one can only load after its parent. With dynamic imports, the import time of a deep chain adds up level by level. - Printing the help loads every sub-command of the command being printed, so it costs about the same as declaring them eagerly, and slightly more with dynamic imports. Sub-commands of nested commands are not loaded. Declaring sub-commands eagerly stays the simpler choice for a small command tree, or for commands that are cheap to build. ## Command Literal Arguments For arguments with `--` the following can be used. ```typescript import { Command } from "@cliffy/command"; await new Command() .name("my-command") .arguments("[...args:string]") .option("--foo", "Foo option.") .action(function (options, ...args) { console.log("Options:", options); console.log("Arguments:", args); console.log("Literal arguments:", this.getLiteralArgs()); }) .parse(); ``` ```console $ my-command --foo bar -- --baz Options: { foo: true } Arguments: [ "bar" ] Literal arguments: [ "--baz" ] ``` --- # Options Options are defined with the `.option()` method and can be accessed as properties on the options object which is passed to the `.action()` handler and returned by the `.parse()` method. With the first argument of the `.option()` method you define the option names and arguments. Each option can have multiple short and long flags, separated by comma. The name of the first long flag will be used as an option name. If no long flag is provided the first short flag will be used. Multi-word options such as `--template-engine` are camel-cased to `templateEngine` and multiple short flags may be combined as a single arg, for example `-abc` is equivalent to `-a -b -c` and `-n5` is equivalent to `-n 5` and `-n=5`. The second parameter of the `.option()` method is the description and the third parameter can be an options object. ## Arguments An option can have multiple required and optional arguments, separated by space. Required values are declared using angle brackets `` and optional values with square brackets `[hostname]`. Optionally you can define [types](https://cliffy.io/docs/v1.3.1/command/types.md) and [completions](https://cliffy.io/docs/v1.3.1/command/shell_completions.md) for the arguments of the option. If no type is specified the type defaults to `string`. If no argument is specified, the type defaults to `true`. ```typescript import { Command } from "@cliffy/command"; const { options } = await new Command() .option("-s, --silent", "disable output.") .option("-d, --debug [level]", "output extra debugging.") .option("-p, --port ", "the port number.") .option("-h, --host=[hostname]", "the host name.", { default: "localhost" }) .parse(); console.log("server running at %s:%s", options.host, options.port); ``` ```console $ deno run examples/command/options.ts -p 80 server running at localhost:80 ``` > Note: The equals sign only has an effect for options with an **optional** > value. For options with a **required** value, the `=` in the definition (and > the [`equalsSign`](https://cliffy.io/docs/v1.3.1/flags/flag_options.md#equals-sign) option) have no > effect, the option can always be called with or without an equals sign. > > For options with an **optional** value, there is a difference between defining > the value without an equals sign like `--foo [bar]` and with an equals sign > like `--foo=[bar]`: > > - If the option is defined **without** an equals sign, the option can be > called with and without an equals sign. > - If the option is defined **with** an equals sign, the option must be called > with an equals sign as well. > > The difference is, an option with an optional value which is defined with an > equals sign can be used before an argument without consuming it as the option > value: > > - `deno run --allow-env mod.ts` > - `deno run --allow-env=FOO,BAR mod.ts` ### Variadic arguments The last argument of an option can be variadic. To make an argument variadic you can append or prepend `...` to the argument name. For example: ```typescript import { Command } from "@cliffy/command"; const { options } = await new Command() .version("0.1.0") .option("-d, --dir [otherDirs...:string]", "Variadic option.") .parse(); console.log(options); ``` The variadic option is returned as an array. ```console $ deno run examples/command/variadic_options.ts -d dir1 dir2 dir3 { dir: [ "dir1", "dir2", "dir3" ] } ``` ## Dotted options Dotted options allows you to group your options together in nested objects. There is no limit for the level of nested objects. ```typescript import { Command } from "@cliffy/command"; const { options } = await new Command() .option( "-b.a, --bitrate.audio, --audio-bitrate ", "Audio bitrate", ) .option( "-b.v, --bitrate.video, --video-bitrate ", "Video bitrate", ) .parse(); console.log(options); ``` ```console $ deno run examples/command/dotted_options.ts -b.a 300 -b.v 900 { bitrate: { audio: 300, video: 900 } } $ deno run examples/command/dotted_options.ts --bitrate.audio 300 --bitrate.video 900 { bitrate: { audio: 300, video: 900 } } $ deno run examples/command/dotted_options.ts --audio-bitrate 300 --video-bitrate 900 { bitrate: { audio: 300, video: 900 } } ``` ## Wildcard options Wildcard options are options with wildcard names. A wildcard option allows any name matching the wildcard pattern. Wildcard options can be specified in following ways: - `--*`: Matches all options. - `--foo.*`: Matches options like `--foo.bar` but not `--foo` or `--foo.bar.baz`. - `--foo.*.bar`: Matches options like `--foo.any-name.bar`. - `--foo.*.*` Matches options like `--foo.bar.baz` but not `--foo` or `--foo.bar`. > The `*` means any name is allowed. ## Default option value You can specify a default value for an option with an optional value. ```typescript import { Command } from "@cliffy/command"; const { options } = await new Command() .option("-c, --cheese [type:string]", "add the specified type of cheese", { default: "blue", defaultText: "some cheese", }) .parse(); console.log(`cheese: ${options.cheese}`); ``` ```console $ deno run examples/command/default_option_value.ts cheese: blue $ deno run examples/command/default_option_value.ts --cheese mozzarella cheese: mozzarella ``` ### Default value display text By default the default value is displayed in the help text. You can change the display text in the help output with the `defaultText` option. The `defaultText` option can be a string or a function which returns a string. If the `defaultText` option is a function, the function receives the default value as argument and the return value of the function is used as display text in the help output. ```console $ deno run examples/command/default_option_value.ts --help Usage: COMMAND Options: -h, --help - Show this help. -c, --cheese [type] - add the specified type of cheese (Default: "some cheese") ``` ### Empty values An empty value means the option was not provided. Both `--cheese ""` and `--cheese=` are treated the same as leaving the option out, so the default value applies. This is what you want when the value comes from a shell variable that may be unset, as in `--cheese "$CHEESE"`. A [required option](#required-options) has no such fallback, so an empty value is rejected with the same error as a missing one. ```typescript import { Command } from "@cliffy/command"; const { options } = await new Command() .option("-c, --cheese ", "Type of cheese.", { default: "blue" }) .option("-n, --name ", "Your name.", { required: true }) .parse(); console.log(options); ``` ```console $ deno run example.ts --name Tom --cheese "" { name: "Tom", cheese: "blue" } $ deno run example.ts --name "" Error: Missing value for option "--name". ``` An empty string can therefore not be passed as a value. To let the user turn an option off from the command line, declare a [negatable option](#negatable-options). The same rule applies to [command arguments](https://cliffy.io/docs/v1.3.1/command/commands.md#empty-argument-values). ## Required options You may specify a required (mandatory) option. ```typescript import { Command } from "@cliffy/command"; await new Command() .option("-c, --cheese [type:string]", "pizza must have cheese", { required: true, }) .parse(); ``` ```console $ deno run examples/command/required_options.ts Error: Missing required option "--cheese". ``` ### Allow empty If `.allowEmpty()` is called, the command will not throw an error if the command has a required option but no argument is passed to the command. This can be used for example if you have required option but want to show the help by default if no arguments are passed to the command. ```ts import { Command } from "@cliffy/command"; new Command() .option("--foo", "...", { required: true }) .allowEmpty() .action(function ({ foo }) { if (!foo) { this.showHelp(); return; } // Do something else... }); ``` ## Environment variables With the `env` option an option falls back to an environment variable when the flag is not used, so there is no need to register a matching environment variable separately with the [`.env()`](https://cliffy.io/docs/v1.3.1/command/environment_variables.md) method. The value is read into the property of the option, with the precedence `flag > environment variable > default value`. - `true` derives the name from the long flag. `--install-root` becomes `INSTALL_ROOT`. - A string sets the name explicitly. - `{ prefix }` prepends a prefix to the derived name. ```typescript import { Command } from "@cliffy/command"; await new Command() .option("--cache ", "Cache directory.", { env: true }) .option("--token ", "Auth token.", { env: "MY_TOKEN" }) .option("--install-root ", "Set install root.", { env: { prefix: "DENO_" }, }) .option("--port ", "Port to listen on.", { env: true, default: 8080, }) .action((options) => console.log(options)) .parse(); ``` ```console $ CACHE=/tmp/cache MY_TOKEN=secret DENO_INSTALL_ROOT=foo/bar PORT=3000 deno run --allow-env example.ts { cache: "/tmp/cache", token: "secret", installRoot: "foo/bar", port: 3000 } $ PORT=3000 deno run --allow-env example.ts --port 9000 { port: 9000 } $ deno run --allow-env example.ts { port: 8080 } ``` The linked environment variable is listed in the environment variables section of the help and as a hint on the option itself: ```console --port - Port to listen on. (Default: 8080, env: PORT) ``` A required option is satisfied by its environment variable, so no error is thrown when the flag is missing but the variable is set. ### Negated environment variables A [negatable option](#negatable-options) registers a negated environment variable and inverts its value, the same way the flag does. `--no-check` reads `NO_CHECK` and stores the result in `check`. ```typescript import { Command } from "@cliffy/command"; await new Command() .option("--no-check", "Disable type checking.", { env: true }) .action((options) => console.log(options)) .parse(); ``` ```console $ NO_CHECK=true deno run --allow-env example.ts { check: false } $ deno run --allow-env example.ts { check: true } ``` ### Environment variable type An option and its linked environment variable share a type. For a flag without a value that type is boolean, which is wrong for a variable like `NO_COLOR`, where any non-empty value counts and the value itself is irrelevant. It would be parsed, so `NO_COLOR=false` would enable colors and `NO_COLOR=yes` would fail with a type error. The [presence type](https://cliffy.io/docs/v1.3.1/command/types.md#presence-type) exists for those. The `type` option of `env` sets the type of the variable on its own. ```typescript import { Command } from "@cliffy/command"; await new Command() .option("--no-color", "Disable colors.", { env: { type: "presence" } }) .action((options) => console.log(options)) .parse(); ``` ```console $ NO_COLOR=1 deno run --allow-env example.ts { color: false } $ NO_COLOR=whatever deno run --allow-env example.ts { color: false } $ deno run --allow-env example.ts { color: true } ``` The option itself is untouched, it still takes no value and the help shows no value hint for it. ```console $ deno run example.ts --no-color=true error: Option "--no-color" doesn't take a value, but got "true". ``` Any registered type works, not only `presence`, and it can be combined with `prefix`. When the type of the variable differs from the type of the option, the value of the option becomes the union of both. ```typescript import { Command } from "@cliffy/command"; await new Command() .option("--port ", "Port to listen on.", { env: { type: "number" }, }) .action((options) => console.log(options)) .parse(); // options.port is of type `string | number | undefined`. ``` ```console $ PORT=80 deno run --allow-env example.ts { port: 80 } ``` ### Restrictions - An option without a long flag needs an explicit name, for example `.option("-f", "Force.", { env: "FORCE" })`. - [Dotted options](#dotted-options) are not supported. Register the environment variable with the [`.env()`](https://cliffy.io/docs/v1.3.1/command/environment_variables.md) method instead. ## Negatable options You can specify a boolean option long name with a leading `no-` to set the option value to false when used. Defined alone this also makes the option true by default. If you define `--foo`, adding `--no-foo` does not change the default value from what it would otherwise be. This is also the way to clear a default value from the command line, since an [empty value](#empty-values) is treated as not provided. A negated option resolves to `false`, not to an empty string. Use [`.value()`](#map-option-value) if you need a different value. You can specify a default value for a flag and it can be overridden on command line. ```typescript import { Command } from "@cliffy/command"; const { options } = await new Command() // default value will be automatically set to true if no --check option exists .option("--no-check", "No check.") .option("--color ", "Color name.", { default: "yellow" }) .option("--no-color", "No color.") // no default value .option("--remote ", "Remote url.") .option("--no-remote", "No remote.") .parse(); console.log(options); ``` ```console $ deno run examples/command/negatable_options.ts { check: true, color: "yellow" } $ deno run examples/command/negatable_options.ts --no-check --no-color --no-remote { check: false, color: false, remote: false } ``` ## Global options To share options with child commands you can use the `.globalOption()` method or the `.option()` method together with the `global` option. ```typescript import { Command } from "@cliffy/command"; await new Command() .option("-l, --local [val:string]", "Only available on this command.") .globalOption( "-g, --global [val:string]", "Available on this and all nested child commands.", ) .action(console.log) .command( "command1", new Command() .description("Some sub command.") .action(console.log) .command( "command2", new Command() .description("Some nested sub command.") .action(console.log), ), ) .parse(); ``` Global options can also be placed before a sub-command. ```console $ deno run examples/command/global_options.ts -g test command1 command2 { global: "test" } $ deno run examples/command/global_options.ts command1 -g test command2 { global: "test" } $ deno run examples/command/global_options.ts command1 command2 -g test { global: "test" } ``` ### Global options and method chaining A global option is registered on the command that is currently selected in the chain, which is not always the main command. The `.command()` method selects the new sub command, so a global option that is added after a `.command()` call belongs to that sub command. It is shared with the child commands of that sub command, but not with the main command or its sibling commands. See [reset](https://cliffy.io/docs/v1.3.1/command/commands.md#reset) for how command chaining works. ```typescript import { Command } from "@cliffy/command"; await new Command() .name("my-cli") .globalOption("-a, --alpha", "Available on all commands.") .command( "foo", new Command() .description("Foo command.") .command("baz", "Baz command."), ) // Registered on the foo command, not on the main command: .globalOption("-b, --beta", "Available on foo and baz.") .command("bar", "Bar command.") .parse(); ``` `--alpha` is available on every command, but `--beta` only on `foo` and its child command `baz`: ```console $ my-cli foo baz --beta $ my-cli bar --beta error: Unknown option "--beta". Did you mean option "--help"? ``` To register a global option on the main command after a sub command was added, use the `.reset()` method to select the main command again. ```typescript import { Command } from "@cliffy/command"; await new Command() .name("my-cli") .command("foo", "Foo command.") .reset() // Registered on the main command: .globalOption("-b, --beta", "Available on all commands.") .parse(); ``` ## Hidden options To exclude options from the help and completion commands you can use the `hidden` option. ```typescript import { Command } from "@cliffy/command"; await new Command() .option("-H, --hidden [hidden:boolean]", "Nobody knows about me!", { hidden: true, }) .parse(); ``` ```console $ deno run examples/command/hidden_options.ts -h ``` ## Conditional options With the `enabled` option you can decide at runtime whether an option is registered. It defaults to `true`. A disabled option is not added to the command, so it is missing from the help output, is rejected as an unknown flag and has no property on the options object. ```typescript import { Command } from "@cliffy/command"; const isWindows = Deno.build.os === "windows"; await new Command() .option("-m, --mode ", "File mode of the created file.", { enabled: !isWindows, }) .action((options) => console.log(options)) .parse(); ``` ```console $ deno run example.ts --mode 644 { mode: "644" } # On Windows: $ deno run example.ts --mode 644 error: Unknown option "--mode". Did you mean option "--help"? ``` The type follows: the value of a conditional option is ` | undefined`, because the option may not have been registered. This also applies to a [required option](#required-options), which is otherwise never `undefined`. ```typescript import { Command } from "@cliffy/command"; const isWindows = Deno.build.os === "windows"; await new Command() .option("-m, --mode ", "File mode of the created file.", { required: true, enabled: !isWindows, }) .action(({ mode }) => console.log("mode: %s", mode)) .parse(); ``` ## Standalone options Standalone options cannot be combined with any command and option. For example the `--help` and `--version` flag. You can achieve this with the `standalone` option. ```typescript import { Command } from "@cliffy/command"; await new Command() .option("-s, --standalone [value:boolean]", "Some standalone option.", { standalone: true, }) .option("-o, --other [value:boolean]", "Some other option.") .parse(); ``` ```console $ deno run examples/command/standalone_options.ts --standalone --other Error: Option --standalone cannot be combined with other options. ``` ## Conflicting options To define options which conflicts with other options you can use the `conflicts` option by defining an array with the names of these options. Conflicts are only checked against explicitly provided options (as command line argument or environment variable). A conflicting option that only has its default value does not trigger an error. For example, an option `--table` with `conflicts: ["json"]` can be used even when a `--json` option defaults to `true`, but fails when `--json` is provided explicitly. ```typescript import { Command } from "@cliffy/command"; const { options } = await new Command() .option("-f, --file ", "read from file ...") .option("-i, --stdin [stdin:boolean]", "read from stdin ...", { conflicts: ["file"], }) .parse(); console.log(options); ``` ```console $ deno run examples/command/conflicting_options.ts -f file1 { file: "file1" } $ deno run examples/command/conflicting_options.ts -i { stdin: true } $ deno run examples/command/conflicting_options.ts -if file1 Error: Option --stdin conflicts with option: --file ``` ## Depending options To define options which depends on other options you can use the `depends` option by defining an array with the names of these options. ```typescript import { Command } from "@cliffy/command"; const { options } = await new Command() .option("-u, --audio-codec ", "description ...") .option("-p, --video-codec ", "description ...", { depends: ["audio-codec"], }) .parse(); ``` ```console $ deno run examples/command/depending_options.ts -a aac { audioCodec: "aac" } $ deno run examples/command/depending_options.ts -v x265 Error: Option "--video-codec" depends on option "--audio-codec". $ deno run examples/command/depending_options.ts -a aac -v x265 { audioCodec: "aac", videoCodec: "x265" } ``` ## Collect options An option can occur multiple times in the command line to collect multiple values. To do this, you have to activate the `collect` option. ```typescript import { Command } from "@cliffy/command"; const { options } = await new Command() .option("-c, --color ", "read from file ...", { collect: true }) .parse(); console.log(options); ``` ```console $ deno run examples/command/collect_options.ts --color yellow --color red --color blue { color: [ "yellow", "red", "blue" ] } ``` ## Map option value You may specify a function to do custom processing of option values. The callback function receives one parameter, the user specified value which is already parsed into the specified type. The return value of this method will be used as option value. If collect is enabled the function receives as second parameter the previous value. This allows you to coerce the option value to the desired type, or accumulate values, or do entirely custom processing. ```typescript import { Command, ValidationError } from "@cliffy/command"; const { options } = await new Command() .option( "-o, --object ", "map string to object", (value: string): { value: string } => { return { value }; }, ) .option("-C, --color ", "collect colors", { collect: true, value: (value: string, previous: Array = []): Array => { if (["blue", "yellow", "red"].indexOf(value) === -1) { throw new ValidationError( `Color must be one of "blue, yellow or red", but got "${value}".`, // optional you can set the exitCode which is used if .throwErrors() // is not called. Default is: 1 { exitCode: 1 }, ); } previous.push(value); return previous; }, }) .parse(); ``` ```console $ deno run examples/command/custom_option_processing.ts --object a { object: { value: "a" } } $ deno run examples/command/custom_option_processing.ts --color blue \ --color yellow \ --color red { color: [ "blue", "yellow", "red" ] } ``` ## Option action handler Options can have an action handler same as commands. You can add an action handler with the `action` option. > **Prior to v0.20.0**, when an option action was executed, the command action > was not executed. **Since v0.20.0**, this has changed. The command action is > now executed by default. Only standalone options do not execute the command > actions. ```typescript import { Command } from "@cliffy/command"; await new Command() .version("0.1.0") .option("--foo", "Foo option.", { action: () => { console.log("--foo action"); }, }) .option("--bar", "Bar option.", { standalone: true, action: () => { console.log("--bar action"); }, }) .option("--baz", "Baz option.", { action: () => { console.log("--baz action"); Deno.exit(0); }, }) .action(() => console.log("main action")) .parse(); console.log("main context"); ``` ```console $ deno run examples/command/action_options.ts --foo --foo action main action main context $ deno run examples/command/action_options.ts --bar --bar action main context $ deno run examples/command/action_options.ts --baz --baz action ``` ## Grouped options Options can be grouped with the `.group()` method to display them separately in the help text. When the `.group()` method has been called, all options which are registered after the `.group()` method will be added to this group. ```typescript import { Command } from "@cliffy/command"; await new Command() .version("0.1.0") .description("Grouped options example.") .option("--foo", "Foo option.") .group("Other options") .option("--bar", "Bar option.") .option("--baz", "Baz option.") .group("Other options 2") .option("--beep", "Beep option.") .option("--boop", "Boop option.") .parse(); ``` ```console $ deno run examples/command/grouped_options.ts --help Usage: COMMAND Version: 0.1.0 Description: Grouped options example. Options: -h, --help - Show this help. -V, --version - Show the version number for this program. --foo - Foo option. Other options: --bar - Bar option. --baz - Baz option. Other options 2: --beep - Beep option. --boop - Boop option. ``` --- # Types Cliffy provides some default types which are used to validate user input, providing shell completions and adding info to the help text. It is also possible to register custom types. You can read more about custom types [here](#custom-types). Types can be declared after the argument name, separated by colon ``. If no type is specified, the type defaults to `string`. If an option with no arguments is defined the type defaults to `true`. ## Built-in types Following types are available by default on all commands. - **boolean:** Can be one of: `true`, `false`, `1` or `0`. - **string:** Can be any value. - **number:** Can be any numeric value. - **integer:** Can be any integer value. - **file:** Same as string but adds support for path completion. - **presence:** Resolves any non-empty value to `true`, for [environment variables](https://cliffy.io/docs/v1.3.1/command/environment_variables.md) where only being set matters. See [presence type](#presence-type). - **secret:** Same as string but hides the value in the help and shell completions. ```typescript import { Command } from "@cliffy/command"; const { options } = await new Command() // Env value must be always required. .env("DEBUG=", "Enable debugging.") // Option with no value. .option("-d, --debug", "Enable debugging.") // Option with optional boolean value. .option("-s, --small [small:boolean]", "Small pizza size.") // Option with required string value. .option("-p, --pizza-type ", "Flavour of pizza.") // Option with required number value. .option("-a, --amount ", "Pieces of pizza.") // Option that hides its default value. .option("-t, --token ", "Token.", { default: () => "SECRET" }) // One required and one optional command argument. .arguments(" [output:file]") .parse(); console.log(options); ``` ```console $ deno run examples/command/common_option_types.ts -p Error: Missing value for option "--pizza-type". $ deno run examples/command/common_option_types.ts -sp vegetarian --amount 3 { small: true, pizzaType: "vegetarian", amount: 3 } ``` ## Presence type Some environment variables signal something by being set, and their value is irrelevant. [`NO_COLOR`](https://no-color.org) is the best known example: any non-empty value disables colored output, whatever the value says. Reading such a variable as a boolean gets both cases wrong, `NO_COLOR=false` would enable colors and `NO_COLOR=yes` would fail with a type error. The `presence` type resolves any non-empty value to `true`. An unset or empty variable is treated as not set and adds no value at all. ```typescript import { Command } from "@cliffy/command"; await new Command() .env("NO_CACHE=", "Disable the cache.", { negatable: true }) .action((options) => console.log(options)) .parse(); ``` ```console $ NO_CACHE=1 deno run --allow-env=NO_CACHE example.ts { cache: false } $ NO_CACHE=whatever deno run --allow-env=NO_CACHE example.ts { cache: false } $ deno run --allow-env=NO_CACHE example.ts {} ``` Options are presence based already, an option without a value is `true` when used, so this type is meant for [environment variables](https://cliffy.io/docs/v1.3.1/command/environment_variables.md). To read the environment variable of an option this way, set the type with the `env` option of the `.option()` method instead of on the option itself. ## Enum type The `EnumType` can be used to define a list of allowed values. The constructor accepts either an `Array` or an `enum`. The values are used for input validation and shell completions and displayed in the help text and types will be automatically inferred and applied to the values of the command options and arguments. ```typescript import { Command, EnumType } from "@cliffy/command"; const Animal = { Dog: "dog", Cat: "cat", } as const; type Animal = typeof Animal[keyof typeof Animal]; // Enum type with enum. const animal = new EnumType(Animal); // Enum type with array. const color = new EnumType(["blue", "yellow", "red"]); await new Command() .type("color", color) .type("animal", animal) .option( "-c, --color [name:color]", "Choose a color.", ) .option( "-a, --animal [name:animal]", "Choose an animal.", ) .action(({ color, animal }) => { console.log("color: %s", color); console.log("animal: %s", animal); }) .parse(); ``` ```console $ deno run examples/command/enum_option_type.ts --color red --animal dog color: red animal: dog $ deno run examples/command/enum_option_type.ts --color foo error: Option "--color" must be of type "color", but got "foo". Expected values: "blue", "yellow", "red" ``` ### Type inference with enums When using a TypeScript `enum`, pass the enum type explicitly as a type argument to preserve the enum type in inferred option and argument types: ```typescript import { Command, EnumType } from "@cliffy/command"; enum Animal { Dog = "dog", Cat = "cat", } // Without explicit type argument, the EnumType infers the decomposed union // `Animal.Dog | Animal.Cat` instead of `Animal`. const animal = new EnumType(Animal); await new Command() .type("animal", animal) .arguments("") .action((_options, name) => { // `name` is inferred as `Animal` (not `Animal.Dog | Animal.Cat`) const a: Animal = name; console.log("animal: %s", a); }) .parse(); ``` ## List types Each type can be used as a list. A list type accepts a `,` separated list of items with the specified type. The default separator is `,` but can be changed with the `separator` option. ```typescript import { Command } from "@cliffy/command"; const { options } = await new Command() // comma separated list .option("-l, --list ", "comma separated list of numbers.") // space separated list .option( "-o, --other-list ", "space separated list of strings.", { separator: " " }, ) .parse(); console.log(options); ``` ```console $ deno run examples/command/list_option_type.ts -l 1,2,3 { list: [ 1, 2, 3 ] } $ deno run examples/command/list_option_type.ts -o "1 2 3" { otherList: [ "1", "2", "3" ] } ``` ## Global types To make a type also available for child commands, you can use the `.globalType()` method. You can also make a type global with the `global` option in `.type()` method. ```typescript import { Command, EnumType } from "@cliffy/command"; await new Command() .globalType("color", new EnumType(["red", "blue", "yellow"])) .command("foo", "...") .option("-c, --color ", "Chose a color.") .action(console.log) .command("bar", "...") .option("-b, --background-color [name:color]", "Choose a background color.") .action(console.log) .parse(); ``` ```console $ deno run examples/command/global_custom_type.ts login --color "red" { color: "red" } ``` ## Custom types You can register custom types with the `.type()` method. The first argument is the name of the type, the second can be either a function or an instance of `Type` and the third argument can be an options object. ### Function types This example shows you how to use a function as type handler. ```typescript import { ArgumentValue, Command } from "@cliffy/command"; const colors = ["red", "blue", "yellow"]; function colorType({ label, name, value }: ArgumentValue): string { if (!colors.includes(value.toLowerCase())) { throw new Error( `${label} "${name}" must be a valid color, but got "${value}". Possible values are: ${ colors.join(", ") }`, ); } return value; } const { options } = await new Command() .type("color", colorType) .arguments("[color-name:color]") .option("-c, --color ", "...") .command("foo [color-name:color]", "...") .parse(); ``` ```console $ deno run examples/command/custom_option_type.ts -c "red" { color: "red" } $ deno run examples/command/custom_option_type.ts -c "green" Error: Option "--color" must be a valid color, but got "green". Possible values are: red, blue, yellow ``` ### Class types This example shows you how to create a custom type that extends the base `Type`. ```typescript import { ArgumentValue, Command, Type, ValidationError } from "@cliffy/command"; class ColorType extends Type { private readonly colors = ["red", "blue", "yellow"]; public parse({ label, name, value }: ArgumentValue): string { if (!this.colors.includes(value)) { throw new ValidationError( `${label} "${name}" must be a valid color, but got "${value}". Possible values are: ${ this.colors.join(", ") }`, ); } return value; } } const { options } = await new Command() .type("color", new ColorType()) .arguments("[color-name:color]") .option("-c, --color ", "...") .command("foo [color-name:color]", "...") .parse(); ``` ```console $ deno run examples/command/custom_option_type_class.ts -c "red" { color: "red" } $ deno run examples/command/custom_option_type_class.ts -c "green" Error: Option "--color" must be a valid color, but got "green". Possible values are: red, blue, yellow ``` The `ValidationError` ensures that the help is displayed before the program exits. You can read more about error handling [here](https://cliffy.io/docs/v1.3.1/command/error_handling.md). #### Shell completions You can also add shell completions to custom types by adding a `complete` method to your type. Read more about shell completions [here](https://cliffy.io/docs/v1.3.1/command/shell_completions.md#custom-type). ```ts import { ArgumentValue, Command, Type } from "@cliffy/command"; class ColorType extends Type { override complete(): Array { return ["red", "blue", "yellow"]; } parse(type: ArgumentValue): string { return type.value; } } ``` #### Override possible values in help text To override possible values listed in the auto generated help, you can add a `.values()` method to your custom type. ```ts import { ArgumentValue, Command, Type } from "@cliffy/command"; class ColorType extends Type { override values(): Array { return ["red", "blue", "yellow"]; } parse(type: ArgumentValue): string { return type.value; } } ``` --- # Environment variables > [!NOTE] > To allow deno to access environment variables the `--allow-env=` > flag is required. If the `--allow-env` flag is not provided, environment > variables will be ignored if not marked as required. Environment variables added with the `.env()` method will be validated when the command is executed. Only environment variables that are available for the executed command will be validated. Valid environment variables will be stored in the options object and for invalid or missing environment variables an error is thrown. They are also shown in the auto generated [help](https://cliffy.io/docs/v1.3.1/command/help.md). Environment variable names will be camel cased. For example `SOME_ENV_VAR=true` will be parsed to `{ someEnvVar: true }`. > [!NOTE] > If an option with the same name is defined, the option will override the > environment variable. > [!NOTE] > An option can also read its value from an environment variable with the `env` > option. See [environment variables](https://cliffy.io/docs/v1.3.1/command/options.md#environment-variables) on the > options page. ```typescript import { Command } from "@cliffy/command"; await new Command() .env("SOME_ENV_VAR=", "Description ...") .action((options) => console.log(options)) .parse(); ``` ```console $ SOME_ENV_VAR=abc deno run --allow-env=SOME_ENV_VAR examples/command/environment_variables.ts Error: Environment variable "SOME_ENV_VAR" must be of type "number", but got "abc". $ SOME_ENV_VAR=1 deno run --allow-env=SOME_ENV_VAR examples/command/environment_variables.ts { someEnvVar: 1 } ``` ## Global environment variables Global environment variables are also available on all sub commands. You can add global environment variables either with the `.env()` method and the `global` option or with the `.globalEnv()` method. ```ts import { Command } from "@cliffy/command"; await new Command() .env("SOME_ENV_VAR=", "Description ...", { global: true }) .globalEnv("SOME_OTHER_ENV_VAR=", "Description ...") .action((options) => console.log(options)) .command("hello", "world ...") .action((options) => console.log(options)) .parse(); ``` ## Required environment variables Required environment variables can be added with the `required` option. If a required environment variable is not defined on command line an error is thrown. ```ts import { Command } from "@cliffy/command"; await new Command() .env("SOME_ENV_VAR=", "Description ...", { required: true }) .action((options) => console.log(options)) .parse(); ``` ```console $ deno run examples/command/environment_variables.ts error: Missing required environment variable "SOME_ENV_VAR". $ SOME_ENV_VAR=abc deno run --allow-env=SOME_ENV_VAR examples/command/environment_variables.ts Error: Environment variable "SOME_ENV_VAR" must be of type "number", but got "abc". $ SOME_ENV_VAR=1 deno run --allow-env=SOME_ENV_VAR examples/command/environment_variables.ts { someEnvVar: 1 } ``` ## Hidden environment variables Hidden environment variables can be added with the `hidden` option and will be not displayed in the auto generated help. ```ts import { Command } from "@cliffy/command"; await new Command() .env("SOME_ENV_VAR=", "Description ...", { hidden: true }) .action((options) => console.log(options)) .parse(); ``` ## Prefix It is very common to prefix environment variables with a name like `DENO_DIR` and `DENO_INSTALL_ROOT`. With the `prefix` option you can ensure the prefix is removed before the value is added to the options object. This works also in combination with options. ```typescript import { Command } from "@cliffy/command"; await new Command() .env( "DENO_INSTALL_ROOT=", "Set install root.", { prefix: "DENO_" }, ) .option( "--install-root ", "Set install root.", ) .action((options) => console.log(options)) .parse(); ``` ```console $ DENO_INSTALL_ROOT=foo/bar deno run --allow-env=DENO_INSTALL_ROOT examples/command/environment_variables_prefix.ts { installRoot: "foo/bar" } ``` ## Negatable environment variables A variable like `NO_CACHE` expresses the negation of a value. With the `negatable` option the `NO_` prefix is stripped from the property name and the boolean is inverted, the same way a [negatable option](https://cliffy.io/docs/v1.3.1/command/options.md#negatable-options) works. `NO_CACHE=true` becomes `{ cache: false }`. ```typescript import { Command } from "@cliffy/command"; await new Command() .env("NO_CACHE=", "Disable the cache.", { negatable: true }) .action((options) => console.log(options)) .parse(); ``` ```console $ NO_CACHE=true deno run --allow-env=NO_CACHE examples/command/negatable_environment_variables.ts { cache: false } $ NO_CACHE=false deno run --allow-env=NO_CACHE examples/command/negatable_environment_variables.ts { cache: true } $ deno run --allow-env=NO_CACHE examples/command/negatable_environment_variables.ts {} ``` Unlike a negatable flag, the variable carries a value, so `NO_CACHE=false` turns the property on. The name must start with `NO_` and the value must be of type boolean, otherwise an error is thrown. This also works with the [prefix](#prefix) option, where the `NO_` has to follow the prefix, for example `MYCLI_NO_CACHE` with `{ prefix: "MYCLI_" }`. If a variable and its negated counterpart are both defined and set, the negated one wins. ```typescript import { Command } from "@cliffy/command"; await new Command() .env("CACHE=", "Enable the cache.") .env("NO_CACHE=", "Disable the cache.", { negatable: true }) .action((options) => console.log(options)) .parse(); ``` ```console $ CACHE=true NO_CACHE=true deno run --allow-env examples/command/negatable_environment_variables.ts { cache: false } ``` > [!NOTE] > The option is opt-in for backwards compatibility. Without it, `NO_CACHE=true` > is camel cased like any other name and results in `{ noCache: true }`. In v2 > this becomes the default for boolean `NO_*` environment variables and the > option is removed. > [!NOTE] > Variables that follow a presence-based convention, `NO_COLOR` and > `NODE_DISABLE_COLORS` among them, are disabled by any non-empty value, > whatever the value is. Declaring them as a boolean parses the value and > reaches the opposite conclusion for `NO_COLOR=false`. Declare them with the > [presence type](https://cliffy.io/docs/v1.3.1/command/types.md) instead, or read the color state with > `getColorEnabled()` from `@std/fmt/colors`, which is what cliffy uses for its > own [help](https://cliffy.io/docs/v1.3.1/command/help.md) output. --- # Auto generated help The help information is auto-generated based on the information you have defined on your commands. The name, version, description, meta information, options, commands, environment variables and examples are displayed in the help. To display the help you can invoke the [help option](#help-option) (`-h` or `--help`) or the [help command](https://cliffy.io/docs/v1.3.1/command/built_in_commands.md#help-command) (`help`) on the main or on one of the sub commands. The `help` command needs to be registered manually. To retrieve the help text programmatically you can use the [.showHelp()](#print-help) and [.getHelp()](#get-help) methods. ```typescript import { Command } from "@cliffy/command"; await new Command() .name("help-option-and-command") .version("0.1.0") .description("Sample description ...") .env( "EXAMPLE_ENVIRONMENT_VARIABLE=", "Environment variable description ...", ) .example( "Some example", "Example content ...\n\nSome more example content ...", ) .parse(); ``` ```console $ deno run examples/command/help.ts --help ``` ![](https://cliffy.io/docs/v1.3.1/command/assets/img/help.gif) ## Print help You can use the `.showHelp()` method to output the help to stdout manually. Commands that have sub-commands but no action handler automatically print the help when called without arguments. This behaviour can be disabled with `.help({ auto: false })`. See [Auto help for container commands](#auto-help-for-container-commands). For example, to show the help programmatically inside an action handler, you can call `.showHelp()` directly: ```ts import { Command } from "@cliffy/command"; const cmd = new Command() .name("git") .action(() => cmd.showHelp()) .command("pull", "Pull changes from remote repository.") .action(() => console.log("Pulling...")) .command("fetch", "Fetch changes from remote repository.") .action(() => console.log("Fetching...")); await cmd.parse(); ``` You can also use `this` to refer to the current command instance inside the action handler: ```ts import { Command } from "@cliffy/command"; await new Command() .name("git") .action(function () { this.showHelp(); }) .command("pull", "Pull changes from remote repository.") .action(() => console.log("Pulling...")) .command("fetch", "Fetch changes from remote repository.") .action(() => console.log("Fetching...")) .parse(); ``` ## Get help The `.getHelp()` method returns the auto generated help as string. ## Additional info You can add some additional information to the help text with the `.meta(name, value)` method. ```ts import { Command } from "@cliffy/command"; await new Command() .name("example") .version("1.0.0") .description("Example command.") .meta("deno", Deno.version.deno) .meta("v8", Deno.version.v8) .meta("typescript", Deno.version.typescript) .parse(); ``` The additional information is displayed below the command version in the auto generated help. ```console $ deno run example.ts --help Usage: example Version: 0.1.0 deno: 1.16.1 v8: 9.7.106.2 typescript: 4.4.2 Description: Example command. ``` ## Customize help The `.help()` method can be used to customize the auto generated help. The help output is fully responsive and adapts to the terminal width by default. ```typescript import { Command } from "@cliffy/command"; await new Command() .help({ // Show argument types. types: true, // default: false // Show hints. hints: true, // default: true // Enable, disable or auto-detect colors. Defaults to the global color // state of `@std/fmt/colors`. See the colors section below. colors: false, // Set the target width of the help output in columns. // Defaults to the terminal width (or 150 if not a TTY). width: 120, // Set the maximum width for the help output. maxWidth: 160, }) .option("-f, --foo [val:number]", "Some description.", { required: true, default: 2, }) .parse(); ``` ### Responsive help The help output is responsive by default: it reads the current terminal width and wraps text accordingly. The `width` option overrides the detected terminal width, and `maxWidth` caps it. ### Colors The `colors` option controls the colored output of the help. - `true` enables colors. - `false` disables colors. - `"auto"` enables colors only when they are globally enabled and standard output is a terminal, so the output stays plain when it is piped into a file or another program. Without the option, the global color state of `@std/fmt/colors` is used, which can be changed with `setColorEnabled()`. ```typescript import { Command } from "@cliffy/command"; await new Command() .help({ colors: "auto" }) .option("-f, --foo", "Some description.") .parse(); ``` > [!NOTE] > Colors are always disabled when the `NO_COLOR` environment variable is set to > a non-empty value, regardless of this option. On Node and Bun, the > `NODE_DISABLE_COLORS` environment variable has the same effect. `"auto"` > becomes the default in 2.0. Cliffy reads those variables by default. Any non-empty value disables colors, `NO_COLOR=false` included. To colorize your own output the same way the help does, read `getColorEnabled()` from `@std/fmt/colors` rather than reading the variable yourself. ### Auto help for container commands By default, when a command has sub-commands but no action handler, calling it without any arguments automatically prints the help text. This is the `auto` behaviour, which is enabled by default. You can disable it by passing `{ auto: false }` to `.help()`. ```ts import { Command } from "@cliffy/command"; await new Command() .name("git") // Disable automatic help for container commands (help must be shown manually). .help({ auto: false }) .command("pull", "Pull changes from remote repository.") .action(() => console.log("Pulling...")) .command("fetch", "Fetch changes from remote repository.") .action(() => console.log("Fetching...")) .parse(); ``` ## Override help The `.help()` method can be also used to override the help output. This overrides the output of the `.getHelp()` and `.showHelp()` methods which are used by the help option and help command. The help handler will also be used for all sub commands, but can be overridden in each sub command separately. ```typescript import { Command } from "@cliffy/command"; await new Command() .help("My custom help") // Can be also a function. .help(() => "My custom help") .parse(); ``` ## Help option The `-h` and `--help` option flag prints the auto generated help to stdout. The short flag `-h` prints only the first line of each option and command description. With the long flag (`--help`) the full description is printed for each option and command. Optionally you can also register the pre-defined [help](https://cliffy.io/docs/v1.3.1/command/built_in_commands.md#help-command) command to display the help. ### Customize help option The help option is completely customizable with the `.helpOption()` method. It has the same arguments as the normal `.option()` method. With the first argument you specify the flags followed by the description. The third argument can be an action handler or an options object. The second and third arguments are optional. ```typescript import { Command } from "@cliffy/command"; await new Command() .helpOption("-i, --info", "Print help info.", function (this: Command) { console.log("some help info ...", this.getHelp()); }) .parse(); ``` You can also override the default options of the help option. The options are the same as for the `.option()` method. ```typescript import { Command } from "@cliffy/command"; await new Command() .helpOption(" -x, --xhelp", "Print help info.", { global: true }) .parse(); ``` To disable the help option you can pass false to the `.helpOption()` method. ```typescript import { Command } from "@cliffy/command"; await new Command() .helpOption(false) .parse(); ``` ## Version option The `--version` and `-V` option flag prints the version number defined with the `version()` method. The version number will also be displayed in the auto generated help. If the long `--version` option is used, the long format will be printed including command name and all meta data defined with the [.meta()](#additional-info) method. ```typescript import { Command } from "@cliffy/command"; await new Command() .version("0.1.0") .parse(); ``` ```console $ deno run examples/command/version_options.ts -V 0.0.1 $ deno run examples/command/version_options.ts --version 0.0.1 ``` ### Customize version option The version option is completely customizable with the `.versionOption()` method. It has the same arguments as the normal `.option()` method. With the first argument you specify the flags followed by the description. The third argument can be an action handler or an options object. The second and third arguments are optional. ```typescript import { Command } from "@cliffy/command"; await new Command() .version("0.1.0") .versionOption( " -x, --xversion", "Print version info.", function (this: Command) { console.log("Version: %s", this.getVersion()); }, ) .parse(); ``` You can also override the default options of the version option. The options are the same as for the `.option()` method. ```typescript import { Command } from "@cliffy/command"; await new Command() .version("0.1.0") .versionOption(" -x, --xversion", "Print version info.", { global: true }) .parse(); ``` The version option can be also disabled. ```typescript import { Command } from "@cliffy/command"; await new Command() .versionOption(false) .parse(); ``` ## Add examples You can add some examples for your command which will be displayed in the auto generated help. ```typescript import { red } from "@std/fmt/colors"; import { Command } from "@cliffy/command"; await new Command() .name("examples") .example( "example name", `Description ...\n\nCan have multiple lines and ${red("colors")}.`, ) .parse(); ``` ```console $ deno run examples/command/examples.ts help ``` ![](https://cliffy.io/docs/v1.3.1/command/assets/img/examples.gif) --- # Shell completion Cliffy supports shell completion out of the box. To enable shell completions it is required to register the [completions](https://cliffy.io/docs/v1.3.1/command/built_in_commands.md#completions-command) command. The completions command generates a shell completions script for your specific shell environment. Currently supported shells are: - [bash](https://cliffy.io/docs/v1.3.1/command/built_in_commands.md#bash-completions) - [fish](https://cliffy.io/docs/v1.3.1/command/built_in_commands.md#fish-completions) - [zsh](https://cliffy.io/docs/v1.3.1/command/built_in_commands.md#zsh-completions) The completions command enables completions for all sub-commands, options and arguments. ## Adding shell completions There are three ways to add shell completions to types which are explained in the following sections. ### Enum type One way is to use the `EnumType`. All values defined with the `EnumType` will be used for shell completions. Read more about the enum type [here](https://cliffy.io/docs/v1.3.1/command/types.md#enum-type). ```typescript import { Command, EnumType } from "@cliffy/command"; const colorType = new EnumType(["red", "blue", "yellow"]); await new Command() .type("color", colorType) .arguments("[color-name:color]") .option("-c, --color ", "Choose a color.") .parse(); ``` ### Complete method Another way to add completions is by registering a completions action with the `.complete()` or `.globalComplete()` method. The values returned by the callback method from the `.complete()` or `.globalComplete()` method will be used for shell completions. To use these completions you can add the name of the action after the type separated by colon. > [!NOTE] > Completions defined with the `.complete()` or `.globalComplete()` method will > override completions defined with the `.type()` or `.globalType()` method. ```typescript import { Command } from "@cliffy/command"; await new Command() .complete("color", () => ["red", "blue", "yellow"]) .arguments("[color-name:string:color]") .option("-c, --color ", "Choose a color.") .parse(); ``` ### Custom type The 3rd way to add shell completions is by creating a custom type with a `.complete()` method. The values returned by the `.complete()` method will be used for shell completions. You can read more about custom types [here](https://cliffy.io/docs/v1.3.1/command/types.md#custom-types). ```typescript import { Command, StringType } from "@cliffy/command"; class ColorType extends StringType { override complete(): Array { return ["red", "blue", "yellow"]; } } await new Command() .type("color", new ColorType()) .option("-c, --color ", "Choose a color.") .parse(); ``` ## Dynamically generating shell completions You can also dynamically generate the completions shell script. To do this you can call the `generateShellCompletions()` function. The function accepts the command instance and the shell name as arguments and returns the generated shell completions script as string. Optionally you can also pass a third argument to the `generateShellCompletions()` function. The third argument is an options object with the following properties: - `name`: The name of the binary. Default is the name of the command. ```ts import { Command } from "@cliffy/command"; import { generateShellCompletions } from "@cliffy/command/completions"; const cmd = new Command() .name("mycmd") .complete("color", () => ["red", "blue", "yellow"]) .arguments("[color-name:string:color]"); await cmd.parse(); const bashCompletions = generateShellCompletions(cmd, "bash"); console.log(bashCompletions); ``` --- # Built-in commands Cliffy provides some predefined commands like `help`, `completions` and `upgrade`. These commands are optional and must be registered manually if you want to use them. ## Help command The `HelpCommand` prints the auto generated help. It is mostly the same as the `--help` option but it also accepts the name of a child command as optional argument to show the help of the given sub-command. To make the help command globally available for all child commands you can use the `.global()` method on the help command. ```typescript import { Command } from "@cliffy/command"; import { HelpCommand } from "@cliffy/command/help"; await new Command() .version("0.1.0") .description("Sample description ...") .env( "EXAMPLE_ENVIRONMENT_VARIABLE=", "Environment variable description ...", ) .command("help", new HelpCommand().global()) .parse(); ``` ```console $ deno run examples/command/help_option_and_command.ts help $ deno run examples/command/help_option_and_command.ts help completions $ deno run examples/command/help_option_and_command.ts completions help ``` ## Completions command The `CompletionsCommand` includes sub commands for all supported shell environments. The sub commands generate the shell completions script and outputs it to stdout. The completions command must be registered manually. ```ts import { Command } from "@cliffy/command"; import { CompletionsCommand } from "@cliffy/command/completions"; await new Command() .command("completions", new CompletionsCommand()) .parse(); ``` By calling ` completions `, the command will output the completions script for the specified shell to stdout. Each shell command provides also a `--name` option which allows you to override the command name: ` completions --name ` ### Bash Completions To add support for bash completions you can either register the `CompletionsCommand` or directly the `BashCompletionsCommand`. To enable bash completions add the following line to your `~/.bashrc`: ```shell source <(COMMAND completions bash) ``` > [!NOTE] > Replace `COMMAND` with the name of your cli. ### Fish Completions To add support for fish completions you can either register the `CompletionsCommand` or directly the `FishCompletionsCommand`. To enable fish completions add the following line to your `~/.config/fish/config.fish`: ```shell script source (COMMAND completions fish | psub) ``` > [!NOTE] > Replace `COMMAND` with the name of your cli. ### Zsh Completions To add support for zsh completions you can either register the `CompletionsCommand` or directly the `ZshCompletionsCommand`. To enable zsh completions add the following line to your `~/.zshrc`: ```shell script source <(COMMAND completions zsh) ``` or run following command to use **zsh fpath** completions: ```shell script COMMAND completions zsh > /path/to/zsh/site-functions/_COMMAND ``` > [!NOTE] > Replace `COMMAND` with the name of your cli. ## Upgrade command The `UpgradeCommand` can be used to upgrade your cli to a given or latest version. If the `UpgradeCommand` is registered, a hint is shown in the help and the long version output if a new version is available. ```shell COMMAND upgrade --version 1.0.2 ``` ```typescript import { Command } from "@cliffy/command"; import { UpgradeCommand } from "@cliffy/command/upgrade"; import { DenoLandProvider } from "@cliffy/upgrade/provider/deno-land"; new Command() .command( "upgrade", new UpgradeCommand({ main: "cliffy.ts", args: ["--allow-net"], provider: new DenoLandProvider(), }), ); ``` With the `provider` option you specify which registries are supported. This option is required. The `main` option is the entry file of your cli, appended to the registry url. On Deno the cli is reinstalled under the name of your main command. > ❗️ The name cannot have spaces! If you use spaces, you (or your users!) will > get an error when upgrading. If your cli needs some permissions, you can specify the permissions with the `args` option which are passed to the runtime's install command. Use the `runtime` option to pass different `args` or a different `main` per runtime. > - Deno installs with `--name`, `--global` and `--force`. > - Node and bun install with `--global` and `--force`. > - The install command is silenced (`--quiet` on Deno, `--silent` on node and > bun) unless the upgrade is run with `-v` or `--verbose`. ### Selecting a registry The `provider` option is required and takes one provider or an array of them. Cliffy ships providers for [jsr](https://jsr.io), [npm](https://www.npmjs.com/), [deno.land](https://deno.land/x), [nest.land](https://nest.land), [github](https://github.com), [gitlab](https://gitlab.com), and any [url](https://cliffy.io/docs/v1.3.1/upgrade/providers/url.md). Their options are documented in [providers](https://cliffy.io/docs/v1.3.1/upgrade/providers/index.md). With multiple providers, users select one with the `--registry` option (hidden when only one is registered). Without it, the first registered provider is used. For a standalone executable, the first provider that supports binary upgrades is used instead. ```shell COMMAND upgrade --registry github --version main ``` The package name defaults to the command name for the jsr, npm, deno.land and nest.land providers. Override it with the `name` option. The github and gitlab providers use the `repository` option instead, and the url provider builds the url itself. ### List available versions The `-l` or `--list-versions` option lists the available versions. The current version is highlighted and prefixed with a `*`. The option is only registered if at least one of the providers can list versions. ```console $ COMMAND upgrade -l * v0.2.2 v0.2.1 v0.2.0 v0.1.0 ``` What each registry lists (for example the github provider's tags and branches) is covered under [listing versions](https://cliffy.io/docs/v1.3.1/upgrade/providers/github.md#listing-versions). ### Binary upgrade For a cli distributed as a standalone executable, the `GithubProvider`, `GitlabProvider` and `UrlProvider` can replace the running binary in place instead of reinstalling from a registry. Configure the provider with the `asset` option, force the binary path with `standalone: true` when it can't be auto-detected, and let users pick an install path with the `-o` or `--output` flag (or an env var via the `outputEnv` option). The `--output` flag is only registered if at least one of the providers supports binary upgrades. See [binary upgrade](https://cliffy.io/docs/v1.3.1/upgrade/binary_upgrade.md) for the full configuration. > [!NOTE] > The `UpgradeCommand` wraps the parser-agnostic > [`@cliffy/upgrade`](https://cliffy.io/docs/v1.3.1/upgrade/index.md) package. To add an upgrade command to > a cli built with `@cliffy/flags` or another parser, use that package directly. --- # Error and exit handling Cliffy throws a `ValidationError` for invalid options, arguments and environment variables. `ValidationError`s can also be thrown manually. By default, when a `ValidationError` is thrown, cliffy prints the auto generated help and the error message and calls `Deno.exit(validationError.exitCode ?? 1)` to exit the program. This behaviour can be changed by calling [`.throwErrors()`](#throw-errors) or [`.noExit()`](#no-exit) or by adding an [error handler](#error-handler). ## Throw errors By default, cliffy prints the help text of the failed command together with the error message and calls `Deno.exit()` when a `ValidationError` is thrown. All other errors will be thrown by default. You can override this behaviour with the `.throwErrors()` method to always throw errors. ## No exit The `.noExit()` method does the same as `.throwErrors()` but also prevents the command from calling `Deno.exit()` for example when the help or version option is called. ## Error handler Errors can be caught by simply wrapping the `.parse()` method into a try catch block. But you can also register an error handler with the `.error()` method. The error handler is inherited by all nested sub-commands, at any depth. Child commands can override error handlers from ancestor commands, in which case the handler closest to the failed command is executed. If the error handler doesn't throw or call `Deno.exit`, the default error handler is executed. The first argument of the error handler is the error, the second argument is the instance of the failed command, and the third argument is an `ErrorContext` object. You can use the command instance to print the help text from this command. ```ts import { Command, ValidationError } from "@cliffy/command"; await new Command() .error((error, cmd) => { if (error instanceof ValidationError) { cmd.showHelp(); } console.error(error); Deno.exit(error instanceof ValidationError ? error.exitCode : 1); }) .action(() => { throw new ValidationError("validation error message."); }) .parse(); ``` ### ErrorContext The `ErrorContext` is the third argument of the error handler and contains the options and arguments that were parsed at the time the error occurred. This is useful when the error output should depend on the options the user passed — for example a global `--verbose` flag that switches between a slim and a detailed error message. ```ts import { Command, ValidationError } from "@cliffy/command"; await new Command() .globalOption("-v, --verbose", "Enable verbose error output.") .error((error, cmd, ctx) => { if (error instanceof ValidationError) { cmd.showHelp(); console.error(`\nerror: ${error.message}`); } else if (ctx.options.verbose) { // Detailed output: full stack trace when --verbose was passed. console.error(error); } else { // Slim output: just the message. console.error(`error: ${error.message}`); } Deno.exit(error instanceof ValidationError ? error.exitCode : 1); }) .command("run ", "Run a script.") .action((_, script) => { throw new Error(`Script not found: ${script}`); }) .parse(); ``` ```console $ deno run example.ts run missing.ts error: Script not found: missing.ts $ deno run example.ts --verbose run missing.ts Error: Script not found: missing.ts at ... ``` ## Runtime errors This example will catch only runtime errors. ```typescript import { Command } from "@cliffy/command"; const cmd = new Command() .option("-p, --pizza-type ", "Flavour of pizza.") .action(() => { throw new Error("Some error happened."); }); try { await cmd.parse(); } catch (error) { console.error("[CUSTOM_ERROR]", error); Deno.exit(1); } ``` ```console $ deno run examples/command/general_error_handling.ts -t Unknown option "-t". Did you mean option "-h"? $ deno run examples/command/general_error_handling.ts [CUSTOM_ERROR] Some error happened. ``` ## Validation errors This example will catch all errors. You can differentiate between runtime and validation errors by checking if the `error` is an instance of `ValidationError`. The validation error has a `exitCode` property that should be used to exit the program. It also provides a `cmd` property which references the failed command. ```typescript import { Command, ValidationError } from "@cliffy/command"; const cmd = new Command() .throwErrors() // <-- throw also validation errors. .option("-p, --pizza-type ", "Flavour of pizza.") .action(() => { throw new Error("Some error happened."); }); try { await cmd.parse(); } catch (error) { if (error instanceof ValidationError) { error.cmd?.showHelp(); console.error("Usage error: %s", error.message); Deno.exit(error.exitCode); } else { console.error("Runtime error: %s", error); Deno.exit(1); } } ``` ```console $ deno run examples/command/validation_error_handling.ts -t Usage error: Unknown option "-t". Did you mean option "-h"? ``` ### Custom validation errors You can throw custom validation errors by throwing an instance of `ValidationError`. Optionally you can define the exit code that should be used when `Deno.exit()` is called after displaying the auto generated help and the error message. ```typescript import { Command, ValidationError } from "@cliffy/command"; await new Command() .option("-c, --color ", "Choose a color.") .action(({ color }) => { if (color === "black") { throw new ValidationError("Black is not supported.", { exitCode: 1 }); } }) .parse(); ``` ## Throw errors outside the command context The `.throw()` method can be used to throw errors outside the command context so you have the same behaviour as when you throw an error for example within an action handler or a type. ```ts import { Command, ValidationError } from "@cliffy/command"; const { options, cmd } = await new Command() .error((_error, _cmd) => { console.error("error handler..."); // Throw or call Deno.exit() to disable the default error handler. // throw _error; // Deno.exit(_error instanceof ValidationError ? _error.exitCode : 1); }) .option("-r, --runtime-error", "Triggers a runtime error.") .option("-v, --validation-error", "Triggers a validation error.") .parse(); if (options.validationError) { cmd.throw(new ValidationError("validation error message.")); } if (options.runtimeError) { cmd.throw(new Error("runtime error message.")); } ``` ```console $ deno run examples/command/throw.ts --runtime-error error handler... error: Uncaught Error: runtime error message. cmd.throw(new Error("runtime error message.")); ^ at examples/command/throw.ts:21:13 ``` ```console $ deno run examples/command/throw.ts --validation-error error handler... Usage: COMMAND Options: -h, --help - Show this help. -r, --runtime-error - Triggers a runtime error. -v, --validation-error - Triggers a validation error. error: validation error message. ``` --- # Generic options and types Since `v0.21.0`, cliffy has strict types by default. All types, option and environment-variable names will be automatically magically inferred 🪄. > [!NOTE] > It is no longer recommended to define the types manually with the generic > parameters. The only exception where you define generics manually is when you want to organize your sub commands in separate files, then you can use the first two generic constructor parameters which are used to define required global options and types which is explained in [Generic parent types](#generic-parent-types). Another exception is, if you want to extend the command class to share it with other projects like cliffy it does with the `HelpCommand`. This is explained in [extending commands](#extending-commands). ## Generic parent types If you want to organize your sub commands into different files, you can define required global parent options and types in the constructor of the child command. The first parameter defines required global options and/or environment variables. The second parameter defines required global custom types. ```typescript import { Command, EnumType } from "@cliffy/command"; const colorType = new EnumType(["red", "blue"]); const fooCommand = new Command< { debug?: true }, { color: typeof colorType } >() .option("-b, --bar", "...") .option("-c, --color ", "...") .action((options) => { if (options.debug) { console.log("debug"); } if (options.bar) { console.log("bar"); } if (options.color) { console.log("color", options.color); } }); await new Command() .globalType("color", colorType) .globalOption("-d, --debug", "...") .command("foo", fooCommand) .parse(); ``` The types of the options object will look like this: ```ts type Options = { debug?: true | undefined; bar?: true | undefined; color?: "red" | "blue" | undefined; }; ``` ## Extending commands If you want to extend the `Command` class like cliffy it does with the `HelpCommand`. New instances of this command will have `void` types by default. In this case you can specify the types manually with the generic constructor parameters of the `Command` class. But this is completely optional. You can add `void` commands to any other command. > When you specify the types, it is recommended to return the command instance > in the constructor. This way you can ensure the constructor typings matches your command typings, because in typescript you can only return an instance in the constructor that is compatible to itself. ```ts import { Command } from "@cliffy/command"; class FooCommand extends Command< void, void, { foo?: string; bar?: number }, [string, string?] > { constructor() { super(); return this .name("foo") .description("Foo command.") .option("--foo ", "...") .option("--bar ", "...") .arguments(" [output:string]") .action(() => console.log("foo")); } } ``` --- # Others ## Did you mean Cliffy has built-in _did-you-mean_ support to improve the user and developer experience. For example, cliffy prints some suggestions, when the user executes an invalid command, or the developer has a typo in the name of a type. ```console $ deno run examples/command/demo.ts --barr error: Unknown option "--barr". Did you mean option "--bar"? ``` --- # Upgrade Parser-agnostic self-upgrade api for cli tools. `@cliffy/upgrade` downloads a newer version and replaces the current install. It handles both script installs (reinstalled from a registry through the runtime's package manager) and standalone binaries (`deno compile`, `bun build --compile`, Node single executable applications). It has no dependency on `@cliffy/command` or `@cliffy/flags`, so it can be used with any argument parser. > [!NOTE] > Most cli tools use this through `@cliffy/command`, which wraps it as the > [`UpgradeCommand`](https://cliffy.io/docs/v1.3.1/command/built_in_commands.md#upgrade-command). Reach for > `@cliffy/upgrade` directly when you build the upgrade command yourself. ## Installation ### Deno ```bash deno add jsr:@cliffy/upgrade ``` ### Pnpm ```bash pnpm add jsr:@cliffy/upgrade ``` or (using pnpm 10.8 or older): ```bash pnpm dlx jsr add @cliffy/upgrade ``` ### Yarn ```bash yarn add jsr:@cliffy/upgrade ``` or (using Yarn 4.8 or older): ```bash yarn dlx jsr add @cliffy/upgrade ``` ### Vlt ```bash vlt install jsr:@cliffy/upgrade ``` ### Npm ```bash npx jsr add @cliffy/upgrade ``` ### Bun ```bash bunx jsr add @cliffy/upgrade ``` ## Usage Call `upgrade()` with the cli name, the current and target versions, and a [provider](https://cliffy.io/docs/v1.3.1/upgrade/providers/index.md). The provider is a registry adapter that resolves versions and, for binary upgrades, release assets. ```ts import { upgrade } from "@cliffy/upgrade"; import { GithubProvider } from "@cliffy/upgrade/provider/github"; await upgrade({ name: "my-cli", from: "1.0.0", to: "latest", provider: new GithubProvider({ repository: "user/my-cli" }), }); ``` ### Versions `to` is the target version, either a concrete version or `latest`. `from` is the currently installed version and is optional. Without it, the upgrade always runs instead of comparing against the installed version, which `force: true` also does for a known version. ```ts import { upgrade } from "@cliffy/upgrade"; import { GithubProvider } from "@cliffy/upgrade/provider/github"; await upgrade({ name: "my-cli", from: "1.0.0", to: "1.2.0", force: true, provider: new GithubProvider({ repository: "user/my-cli" }), }); ``` ### Standalone binaries The install kind (script or standalone binary) is detected from the runtime. For a compiled binary on a runtime that can't report this, pass `standalone: true` to force the binary path. See [binary upgrade](https://cliffy.io/docs/v1.3.1/upgrade/binary_upgrade.md) for the asset configuration it needs. ```ts import { upgrade } from "@cliffy/upgrade"; import { GithubProvider } from "@cliffy/upgrade/provider/github"; await upgrade({ name: "my-cli", to: "latest", provider: new GithubProvider({ repository: "user/my-cli" }), standalone: true, }); ``` ### Reporting progress `upgrade()` is silent unless a `logger` is passed. It's an object with `log`, `info`, `warn` and `error` methods, so `console` works as is: ```ts import { upgrade } from "@cliffy/upgrade"; import { GithubProvider } from "@cliffy/upgrade/provider/github"; await upgrade({ name: "my-cli", from: "1.0.0", to: "latest", provider: new GithubProvider({ repository: "user/my-cli" }), logger: console, }); ``` `verbose: true` additionally keeps the output of the runtime's install command, which is silenced otherwise. ### Install arguments For a script install, `args` is passed to the runtime's install command, for example Deno permission flags, and `main` is the entrypoint module appended to the registry url. ```ts import { upgrade } from "@cliffy/upgrade"; import { JsrProvider } from "@cliffy/upgrade/provider/jsr"; await upgrade({ name: "my-cli", to: "latest", provider: new JsrProvider({ package: "@my-scope/my-cli" }), args: ["--allow-net"], main: "cli.ts", }); ``` ### Runtime specific options The `runtime` option overrides `args` and `main` per runtime, and adds `importMap` on Deno. ```ts import { upgrade } from "@cliffy/upgrade"; import { JsrProvider } from "@cliffy/upgrade/provider/jsr"; await upgrade({ name: "my-cli", to: "latest", provider: new JsrProvider({ package: "@my-scope/my-cli" }), args: ["--allow-net"], runtime: { deno: { importMap: "import_map.json" }, node: { args: [] }, }, }); ``` ### Errors Everything the upgrade throws extends `UpgradeError`: - `VersionNotFoundError`: the requested version isn't listed by the registry. - `AssetNotFoundError`: no release asset is configured or found for the build target, or the binary isn't in the downloaded archive. - `UnsupportedUpgradeError`: the provider can't perform the requested install kind. - `UnsupportedVersionListingError`: the provider has no version list. ### Driving it from a custom parser Because `upgrade()` is independent of `@cliffy/command`, a cli built with `@cliffy/flags` (or any parser) can wire up its own upgrade command: ```ts import { parseFlags } from "@cliffy/flags"; import { upgrade } from "@cliffy/upgrade"; import { GithubProvider } from "@cliffy/upgrade/provider/github"; const { flags } = parseFlags(Deno.args, { flags: [{ name: "version", type: "string", default: "latest" }], }); await upgrade({ name: "my-cli", from: "1.0.0", to: flags.version, provider: new GithubProvider({ repository: "user/my-cli", asset: { "darwin-x86_64": "my-cli-macos-amd64.tar.gz", "linux-x86_64": "my-cli-linux-amd64.tar.gz", "windows-x86_64": "my-cli-windows-amd64.zip", }, }), }); ``` ## Binary upgrades The [`GithubProvider`](https://cliffy.io/docs/v1.3.1/upgrade/providers/github.md), [`GitlabProvider`](https://cliffy.io/docs/v1.3.1/upgrade/providers/gitlab.md) and [`UrlProvider`](https://cliffy.io/docs/v1.3.1/upgrade/providers/url.md) can replace a standalone binary in place by downloading a release asset. See [binary upgrade](https://cliffy.io/docs/v1.3.1/upgrade/binary_upgrade.md) for the `asset` mapping, custom extractors, install location, and private-repository token. --- # Providers A provider is a registry adapter that tells `upgrade()` (and the [`UpgradeCommand`](https://cliffy.io/docs/v1.3.1/command/built_in_commands.md#upgrade-command)) which versions exist and where to download them. `upgrade()` takes a single provider. The `UpgradeCommand` also accepts an array: the first one is the default, the others are selectable with `--registry`. ## Available providers | Provider | Source | Script | Binary | | ------------------------------------ | ----------------- | --------- | ------ | | [`JsrProvider`](https://cliffy.io/docs/v1.3.1/upgrade/providers/jsr.md) | jsr package | yes | no | | [`NpmProvider`](https://cliffy.io/docs/v1.3.1/upgrade/providers/npm.md) | npm package | yes | no | | [`DenoLandProvider`](https://cliffy.io/docs/v1.3.1/upgrade/providers/deno_land.md) | deno.land/x | yes | no | | [`NestLandProvider`](https://cliffy.io/docs/v1.3.1/upgrade/providers/nest_land.md) | nest.land | yes | no | | [`GithubProvider`](https://cliffy.io/docs/v1.3.1/upgrade/providers/github.md) | github repository | yes | yes | | [`GitlabProvider`](https://cliffy.io/docs/v1.3.1/upgrade/providers/gitlab.md) | gitlab repository | yes | yes | | [`UrlProvider`](https://cliffy.io/docs/v1.3.1/upgrade/providers/url.md) | any url | Deno only | yes | Providers group by where the cli is distributed: - **Package registries**: [jsr](https://cliffy.io/docs/v1.3.1/upgrade/providers/jsr.md) and [npm](https://cliffy.io/docs/v1.3.1/upgrade/providers/npm.md). - **CDNs**: [deno.land](https://cliffy.io/docs/v1.3.1/upgrade/providers/deno_land.md) and [nest.land](https://cliffy.io/docs/v1.3.1/upgrade/providers/nest_land.md). - **Git repositories**: [github](https://cliffy.io/docs/v1.3.1/upgrade/providers/github.md) and [gitlab](https://cliffy.io/docs/v1.3.1/upgrade/providers/gitlab.md). - **Custom url**: [url](https://cliffy.io/docs/v1.3.1/upgrade/providers/url.md). The `GithubProvider`, `GitlabProvider` and `UrlProvider` can upgrade standalone executables by downloading a release asset. The shared configuration for that is described under [binary upgrade](https://cliffy.io/docs/v1.3.1/upgrade/binary_upgrade.md). ## Package name The package name defaults to the cli name for the [jsr](https://cliffy.io/docs/v1.3.1/upgrade/providers/jsr.md), [npm](https://cliffy.io/docs/v1.3.1/upgrade/providers/npm.md), [deno.land](https://cliffy.io/docs/v1.3.1/upgrade/providers/deno_land.md) and [nest.land](https://cliffy.io/docs/v1.3.1/upgrade/providers/nest_land.md) providers. Override it with the `name` option, or set the full `package` name on the jsr and npm providers. The [github](https://cliffy.io/docs/v1.3.1/upgrade/providers/github.md) and [gitlab](https://cliffy.io/docs/v1.3.1/upgrade/providers/gitlab.md) providers locate the source with the `repository` option instead, and [url](https://cliffy.io/docs/v1.3.1/upgrade/providers/url.md) builds the url itself. The cli name is still what the upgraded cli is installed as, and what the binary inside a release archive is expected to be called. See [binary upgrade](https://cliffy.io/docs/v1.3.1/upgrade/binary_upgrade.md) to override the latter. > [!NOTE] > `@cliffy/command/upgrade` re-exports the jsr, npm, deno.land, nest.land and > github providers, but those re-exports are deprecated and will be removed in > 2.0. Import providers from `@cliffy/upgrade/provider/*` instead. The gitlab > and url providers are only available there. --- # Jsr provider The `JsrProvider` upgrades a cli published as a [jsr](https://jsr.io) package. Set the full `package` name, or a `scope` plus an optional `name` that defaults to the cli name. ```ts import { JsrProvider } from "@cliffy/upgrade/provider/jsr"; const provider = new JsrProvider({ package: "@my-scope/my-cli" }); ``` The `scope` is written without the leading `@`, and `name` can be left out when the package is named after the cli. ```ts import { JsrProvider } from "@cliffy/upgrade/provider/jsr"; const provider = new JsrProvider({ scope: "my-scope", name: "my-cli" }); ``` > [!NOTE] > When upgrading to `latest`, the `JsrProvider` lets the runtime resolve the > concrete version instead of pinning it. This way a configured minimum > dependency age policy is respected (`minimumDependencyAge` in Deno, > `min-release-age` in npm/pnpm/bun): the upgrade installs the newest version > allowed by the policy rather than the absolute latest. --- # Npm provider The `NpmProvider` upgrades a cli published as an [npm](https://www.npmjs.com/) package. Set the full `package` name, or a `scope` plus an optional `name` that defaults to the cli name. ```ts import { NpmProvider } from "@cliffy/upgrade/provider/npm"; const provider = new NpmProvider({ package: "@my-scope/my-cli" }); ``` For an unscoped package published under the cli name, no options are required. ```ts import { NpmProvider } from "@cliffy/upgrade/provider/npm"; const provider = new NpmProvider(); ``` > [!NOTE] > When upgrading to `latest`, the `NpmProvider` lets the runtime resolve the > concrete version instead of pinning it. This way a configured minimum > dependency age policy is respected (`minimumDependencyAge` in Deno, > `min-release-age` in npm/pnpm/bun): the upgrade installs the newest version > allowed by the policy rather than the absolute latest. --- # Deno Land provider The `DenoLandProvider` upgrades a cli published to [deno.land/x](https://deno.land/x). The module name defaults to the cli name. Override it with the `name` option. ```ts import { DenoLandProvider } from "@cliffy/upgrade/provider/deno-land"; const provider = new DenoLandProvider(); ``` --- # Nest Land provider The `NestLandProvider` upgrades a cli published to [nest.land](https://nest.land). The module name defaults to the cli name. Override it with the `name` option. ```ts import { NestLandProvider } from "@cliffy/upgrade/provider/nest-land"; const provider = new NestLandProvider(); ``` --- # Github provider The `GithubProvider` upgrades from a github repository, set with the `repository` option. For a script install it reinstalls the cli source from any tag or branch. For a standalone executable it downloads a [release asset](#binary-upgrades) instead. ```ts import { GithubProvider } from "@cliffy/upgrade/provider/github"; const provider = new GithubProvider({ repository: "user/my-cli" }); ``` ## Listing versions The github provider lists all tags and branches. Lists longer than `25` entries are rendered as a table. ```console $ COMMAND upgrade --registry github --list-versions Tags: v0.18.2 v0.17.0 v0.14.1 v0.11.2 v0.8.2 v0.6.1 v0.3.0 v0.18.1 * v0.16.0 v0.14.0 v0.11.1 v0.8.1 v0.6.0 v0.2.0 v0.18.0 v0.15.0 v0.13.0 v0.11.0 v0.8.0 v0.5.1 v0.1.0 v0.17.2 v0.14.3 v0.12.1 v0.10.0 v0.7.1 v0.5.0 v0.17.1 v0.14.2 v0.12.0 v0.9.0 v0.7.0 v0.4.0 Branches: main (Protected) keypress/add-keypress-module keycode/refactoring command/upgrade-command ``` Disable branch listing with the `branches` option. ```ts import { GithubProvider } from "@cliffy/upgrade/provider/github"; new GithubProvider({ repository: "user/my-cli", branches: false }); ``` ## Binary upgrades To upgrade a standalone executable, map each `os-arch` target to its release asset filename with the `asset` option. The `os` and `arch` keys use the Deno naming convention (`darwin`/`linux`/`windows`, `x86_64`/`aarch64`) on every runtime. ```ts import { GithubProvider } from "@cliffy/upgrade/provider/github"; new GithubProvider({ repository: "user/my-cli", asset: { "darwin-x86_64": "my-cli-macos-amd64.tar.gz", "darwin-aarch64": "my-cli-macos-arm64.tar.gz", "linux-x86_64": "my-cli-linux-amd64.tar.gz", "windows-x86_64": "my-cli-windows-amd64.zip", }, }); ``` Instead of a map, `asset` also accepts a function that builds the filename per target. ```ts import { GithubProvider } from "@cliffy/upgrade/provider/github"; new GithubProvider({ repository: "user/my-cli", asset: ({ os, arch, version }) => `my-cli-${version}-${os}-${arch}.tar.gz`, }); ``` Asset formats, custom extractors, the binary name, and the install location are shared with the other binary providers. See [binary upgrade](https://cliffy.io/docs/v1.3.1/upgrade/binary_upgrade.md). ## Private repositories A github token is resolved from the `token` option, a string or a function returning one, and then from the `GITHUB_TOKEN` and `GH_TOKEN` environment variables. The function form lets you plug in any scheme, such as an OIDC exchange, `gh auth token`, or a credential manager. ```ts import { GithubProvider } from "@cliffy/upgrade/provider/github"; new GithubProvider({ repository: "user/my-cli", token: () => Deno.env.get("MY_CLI_TOKEN") ?? "", }); ``` --- # Gitlab provider The `GitlabProvider` upgrades from a gitlab repository, set with the `repository` option. For a script install it reinstalls the cli source from any tag or branch. For a standalone executable it downloads a [release asset](#binary-upgrades) instead. ```ts import { GitlabProvider } from "@cliffy/upgrade/provider/gitlab"; const provider = new GitlabProvider({ repository: "group/my-cli" }); ``` ## Self-hosted instances The `host` option points the provider at a self-hosted instance. It defaults to `https://gitlab.com`. ```ts import { GitlabProvider } from "@cliffy/upgrade/provider/gitlab"; new GitlabProvider({ repository: "group/my-cli", host: "https://gitlab.example.com", }); ``` ## Listing versions The gitlab provider lists all tags and branches. Lists longer than `25` entries are rendered as a table. ```console $ COMMAND upgrade --registry gitlab --list-versions Tags: v0.3.0 v0.2.0 v0.1.0 v0.2.1 * v0.1.1 Branches: main (Protected) feat/binary-upgrade ``` Disable branch listing with the `branches` option. ```ts import { GitlabProvider } from "@cliffy/upgrade/provider/gitlab"; new GitlabProvider({ repository: "group/my-cli", branches: false }); ``` ## Binary upgrades To upgrade a standalone executable, map each `os-arch` target to the name of a release asset link with the `asset` option. The `os` and `arch` keys use the Deno naming convention (`darwin`/`linux`/`windows`, `x86_64`/`aarch64`) on every runtime. ```ts import { GitlabProvider } from "@cliffy/upgrade/provider/gitlab"; new GitlabProvider({ repository: "group/my-cli", asset: { "darwin-x86_64": "my-cli-macos-amd64.tar.gz", "darwin-aarch64": "my-cli-macos-arm64.tar.gz", "linux-x86_64": "my-cli-linux-amd64.tar.gz", "windows-x86_64": "my-cli-windows-amd64.zip", }, }); ``` Instead of a map, `asset` also accepts a function that builds the name per target. ```ts import { GitlabProvider } from "@cliffy/upgrade/provider/gitlab"; new GitlabProvider({ repository: "group/my-cli", asset: ({ os, arch, version }) => `my-cli-${version}-${os}-${arch}.tar.gz`, }); ``` The name is matched against the release's asset links, so the release has to link the file, not just have it attached to the tag. Asset formats, custom extractors, the binary name, and the install location are shared with the other binary providers. See [binary upgrade](https://cliffy.io/docs/v1.3.1/upgrade/binary_upgrade.md). ## Private repositories A gitlab token is resolved from the `token` option, a string or a function returning one, and then from the `GITLAB_TOKEN` environment variable. It is sent as the `PRIVATE-TOKEN` header on api requests, and on the asset download when the link points at the same host. ```ts import { GitlabProvider } from "@cliffy/upgrade/provider/gitlab"; new GitlabProvider({ repository: "group/my-cli", token: () => Deno.env.get("MY_CLI_TOKEN") ?? "", }); ``` --- # Url provider The `UrlProvider` upgrades from an arbitrary url (a cdn, object storage, or a custom host). It downloads a standalone binary with the `asset` option and reinstalls a script with the `url` option. One of the two is required. ## Binary upgrades Set `asset` to the download url, or to a function that builds it per build target: ```ts import { UrlProvider } from "@cliffy/upgrade/provider/url"; const provider = new UrlProvider({ asset: ({ name, version, os, arch }) => `https://cdn.example.com/${name}/${version}/${name}-${os}-${arch}.tar.gz`, }); ``` The asset filename decides how the download is unpacked. It defaults to the last path segment of the url. When the url doesn't end in the filename, return an `{ url, name }` object instead of a string: ```ts import { UrlProvider } from "@cliffy/upgrade/provider/url"; new UrlProvider({ asset: ({ version, os, arch }) => ({ url: `https://cdn.example.com/download?v=${version}&target=${os}-${arch}`, name: `my-cli-${os}-${arch}.tar.gz`, }), }); ``` For a private host, send auth headers with the `assetHeaders` option (a `HeadersInit`, or a function of the build target returning one). ## Script upgrades Set `url` to the exact script entrypoint, or to a function of the cli name and the resolved version. Script upgrades are only supported on Deno, where the url is installed with `deno install`: ```ts import { UrlProvider } from "@cliffy/upgrade/provider/url"; new UrlProvider({ url: ({ name, version }) => `https://cdn.example.com/${name}/${version}/cli.ts`, }); ``` Because `url` already points at the exact entrypoint, the `main` option is not supported by this provider. ## Listing versions The provider has no registry to query, so the `latest` target can only be resolved from the `versions` option (a `Versions` object, or a function returning one): ```ts import { UrlProvider } from "@cliffy/upgrade/provider/url"; new UrlProvider({ asset: "https://cdn.example.com/my-cli/latest/my-cli.tar.gz", versions: async () => { const response = await fetch( "https://cdn.example.com/my-cli/versions.json", ); return await response.json(); }, }); ``` Without it, an explicit version must be requested, the `--list-versions` option is not registered, and the automatic version check is skipped. A function resolver is only run by the automatic version check if it can run without triggering a permission prompt. Add `hasRequiredPermissions` to report that, otherwise the check is skipped: ```ts import { UrlProvider } from "@cliffy/upgrade/provider/url"; new UrlProvider({ asset: "https://cdn.example.com/my-cli/latest/my-cli.tar.gz", versions: () => fetch("https://cdn.example.com/my-cli/versions.json") .then((response) => response.json()), hasRequiredPermissions: async () => (await Deno.permissions.query({ name: "net", host: "cdn.example.com" })) .state === "granted", }); ``` ## Homepage The `homepage` option sets the url shown in the upgrade success message. Asset formats, custom extractors, the binary name, and the install location are shared with the other binary providers. See [binary upgrade](https://cliffy.io/docs/v1.3.1/upgrade/binary_upgrade.md). --- # Binary upgrade The [`GithubProvider`](https://cliffy.io/docs/v1.3.1/upgrade/providers/github.md), [`GitlabProvider`](https://cliffy.io/docs/v1.3.1/upgrade/providers/gitlab.md) and [`UrlProvider`](https://cliffy.io/docs/v1.3.1/upgrade/providers/url.md) can replace a standalone executable in place by downloading a release asset, instead of reinstalling from a registry. This works for a cli compiled with `deno compile`, `bun build --compile`, or as a Node single executable application. Each provider resolves _which_ asset to download in its own way (github and gitlab with the `asset` map, url with the download url). Everything after that (detecting the mode, unpacking the asset, installing it) is shared and described below. ## Standalone detection The upgrade detects which mode to use from the runtime: a compiled standalone executable has its binary swapped, while any other install is reinstalled from the registry. Older runtimes can't report whether the process is standalone and default to a script install, so pass `standalone: true` (to the `UpgradeCommand` or to `upgrade()`) to force the binary path for a compiled cli. ## Asset formats Raw binaries, `.gz`, and `.tar.gz`/`.tgz` archives are handled built-in. For `.zip` or other formats, provide an `extract` function. Map a filename suffix to its extractor, matched by longest suffix. Suffixes you don't list fall through to the built-in handlers: ```ts import { GithubProvider } from "@cliffy/upgrade/provider/github"; declare function unzip(bytes: Uint8Array, name: string): Uint8Array; new GithubProvider({ repository: "user/my-cli", asset: { "linux-x86_64": "my-cli-linux-amd64.tar.gz", // built-in "windows-x86_64": "my-cli-windows-amd64.zip", // custom extractor }, extract: { ".zip": (bytes, asset) => unzip(bytes, asset.name), }, }); ``` A single function overrides extraction for every asset, for when one extractor handles all of your formats: ```ts import { GithubProvider } from "@cliffy/upgrade/provider/github"; declare function unzip(bytes: Uint8Array, name: string): Uint8Array; new GithubProvider({ repository: "user/my-cli", asset: { "windows-x86_64": "my-cli-windows-amd64.zip" }, extract: (bytes, asset) => unzip(bytes, asset.name), }); ``` The binary extracted from an archive defaults to the cli name (matching both `name` and `name.exe` inside the archive). Override it with the `binaryName` option, either a string or a function of the build target. ## Install location By default the upgrade replaces the running executable in place (self-replace). The provider's `location` option sets a different default, and a directory installs the cli into it by its name (with an `.exe` suffix on Windows). The resolved location follows this precedence: 1. the `UpgradeCommand`'s `-o` or `--output` flag, 2. its environment variable, enabled with the command's `outputEnv` option (`true` derives `OUTPUT`, a string sets the name, and `{ prefix: "MYCLI_" }` derives `MYCLI_OUTPUT`), 3. the provider's `location` option, 4. the running executable. The new binary is written to a temporary file next to the target and swapped in afterwards, so a failed download leaves the current install untouched. On Windows the running executable is moved aside to `.exe.old` first, and restored if the swap fails. --- # Flags Command line arguments parser with built-in validations. ## Installation ### Deno ```bash deno add jsr:@cliffy/flags ``` ### Pnpm ```bash pnpm add jsr:@cliffy/flags ``` or (using pnpm 10.8 or older): ```bash pnpm dlx jsr add @cliffy/flags ``` ### Yarn ```bash yarn add jsr:@cliffy/flags ``` or (using Yarn 4.8 or older): ```bash yarn dlx jsr add @cliffy/flags ``` ### Vlt ```bash vlt install jsr:@cliffy/flags ``` ### Npm ```bash npx jsr add @cliffy/flags ``` ### Bun ```bash bunx jsr add @cliffy/flags ``` ## Usage The `parseFlags` method takes as its first argument the arguments to be parsed, usually `Deno.args`, or a [parse context](#parse-context). As the second argument you can pass an options object. A list of all available options can be found [here](https://cliffy.io/docs/v1.3.1/flags/parse_options.md). ### Basic usage If `parseFlags` is called without defining specific flags with the options object, all arguments are parsed and added to the flags object returned by the `parseFlags` method. All non-options arguments are added to the `unknown` array and all flags specified after the double dash (`--`) are added to the `literal` array. ```typescript import { parseFlags } from "@cliffy/flags"; console.log(parseFlags()); // or: console.log(parseFlags(Deno.args)); ``` ```console $ deno run examples/flags/flags.ts -a foo -b bar { flags: { a: "foo", b: "bar" }, literal: [], unknown: [], stopEarly: false, stopOnUnknown: false } $ deno run examples/flags/flags.ts \ -x 3 \ -y.z -n5 \ -abc \ --beep=boop \ foo bar baz \ --deno.land \ --deno.com -- --cliffy { flags: { x: "3", y: { z: true }, n: "5", a: true, b: true, c: true, beep: "boop", deno: { land: true, com: true } }, literal: [ "--cliffy" ], unknown: [ "foo", "bar", "baz" ], stopEarly: false, stopOnUnknown: false } ``` ### Define flags You can specify flags with the options object. For all unknown or invalid flags an `ValidationError` is thrown. Read more about error handling [here](https://cliffy.io/docs/v1.3.1/flags/error_handling.md). A list of all available flag options can be found [here](https://cliffy.io/docs/v1.3.1/flags/flag_options.md). ```typescript import { parseFlags } from "@cliffy/flags"; const { flags } = parseFlags(Deno.args, { flags: [{ name: "help", aliases: ["h"], standalone: true, }, { name: "verbose", aliases: ["v"], collect: true, value: (val: boolean, previous = 0) => val ? previous + 1 : 0, }, { name: "file", aliases: ["f"], type: "string", }], }); console.log(flags); ``` ```console $ deno run examples/flags/options.ts -vvv -f ./example.ts { verbose: 3, file: "./example.ts" } ``` ### Positional arguments The `args` option allows you to define typed positional arguments alongside flags. Positional arguments are collected into the `args` array in the returned context and removed from the `unknown` array. ```typescript import { parseFlags } from "@cliffy/flags"; const { flags, args } = parseFlags(["--count", "3", "file.txt"], { flags: [{ name: "count", type: "number" }], args: [{ name: "file", type: "string" }], }); console.log(flags); // { count: 3 } console.log(args); // [ "file.txt" ] ``` Positional arguments can be required, optional, or variadic — the same `ArgumentOptions` interface is used for both options and positional arguments. Positional arguments that have a leading dash (`-foo`) are allowed when the argument is explicitly defined via the `args` option. ```typescript import { parseFlags } from "@cliffy/flags"; const { flags, args } = parseFlags(["--foo", "bar", "a", "b", "c"], { flags: [{ name: "foo", type: "string" }], args: [{ name: "files", type: "string", variadic: true }], }); console.log(args); // [ "a", "b", "c" ] ``` An empty value means the argument was not provided. An optional argument falls back to its default value, and a required one throws, because skipping it would shift every argument after it. Empty values are dropped from a variadic argument, which still requires at least one non-empty value unless it is optional. ```typescript import { parseFlags } from "@cliffy/flags"; const { args } = parseFlags(["a", "", "b"], { args: [{ type: "string", variadic: true }], }); console.log(args); // [ "a", "b" ] ``` ### Parse context The `parseFlags` method accepts also a parse context as first argument. The context can either be a manually created object or the result of a previously called `parseFlags` method. This can be used to parse command line flags in multiple steps, for example, when parsing options that precede a subcommand. ```ts import { parseFlags } from "@cliffy/flags"; const globalFlags = [{ name: "foo-global", alias: ["g"], collect: true, }]; const flags = [{ name: "foo", alias: ["f"], collect: true, }]; const args = ["--foo-global", "cmd1", "--foo-global", "--foo", "arg1", "--foo"]; // Parse main command args (all flags until the first unknown argument). const ctx = parseFlags(args, { flags: globalFlags, stopEarly: true, // Stop on first non option argument. stopOnUnknown: true, // Stop on first option argument. dotted: false, // Don't convert dotted option keys to nested objects. }); // Shift sub-command from arguments. const subCommand = ctx.unknown.shift(); // Parse all sub command args. parseFlags(ctx, { flags: [ ...globalFlags, ...flags, ], }); console.log("sub-command:", subCommand); // -> cmd1 console.log("options:", ctx.flags); // -> { fooGlobal: [ true, true ], foo: [ true, true ] } console.log("arguments:", ctx.unknown); // -> [ "arg1" ] ``` ### Parsed flags The parse context returned by `parseFlags` contains a `parsedFlags` property: a read-only array of the raw **flag** tokens in the order they were encountered, preserving the exact form used on the command line. Positional arguments are **not** included — only flag names and their values appear here. - **Space form** — flag and value as separate tokens: `command --port 80` → `['--port', '80']` - **Equals form** — flag and value as one token: `command --port=80` → `['--port=80']` ```typescript import { parseFlags } from "@cliffy/flags"; const { parsedFlags } = parseFlags([ "--port=80", "--host", "localhost", "file.txt", ], { flags: [ { name: "port", type: "number" }, { name: "host", type: "string" }, ], args: [{ name: "file", type: "string" }], }); console.log(parsedFlags); // [ "--port=80", "--host", "localhost" ] ``` --- # Parse options ## Flags By default the `parseFlags` method parses all flags and tries to autodetect the type. With the `flags` option you can specify an Array of flag options. If the `flags` option is set the `parseFlags` method will throw an error for all unknown or invalid flags. You can find a list of all possible flag options [here](https://cliffy.io/docs/v1.3.1/flags/flag_options.md). ## Args The `args` option accepts an array of `ArgumentOptions` objects that define the expected positional arguments. When defined, non-flag tokens are validated and typed as positional arguments instead of being added to the `unknown` array. A `Error` is thrown if more positional arguments are provided than defined. See [positional arguments](https://cliffy.io/docs/v1.3.1/flags/index.md#positional-arguments) for usage examples. ## Parse With the `parse` method you can add a custom handler for handling and parsing types. > [!NOTE] > The `parse` method will be called for all types, which means it overrides also > all built-in types! ```typescript import { ArgumentValue, parseFlags } from "@cliffy/flags"; parseFlags(Deno.args, { flags: [{ name: "foo", type: "float", }], parse: ({ label, name, value, type }: ArgumentValue) => { switch (type) { case "float": if (isNaN(Number(value))) { throw new Error( `${label} "${name}" must be of type "${type}", but got "${value}".`, ); } return parseFloat(value); default: throw new Error(`Unknown type "${type}".`); } }, }); ``` ```console $ deno run examples/flags/custom_option_processing.ts --foo 1.2 { flags: { foo: 1.2 }, unknown: [], literal: [] } $ deno run examples/flags/custom_option_processing.ts --foo abc error: Uncaught Error: Option "--foo" must be of type "float", but got "abc". ``` ## Option callback The `option` callback method is called for each parsed option. ## Stop early If `stopEarly` is enabled, all values starting from the first non option argument are no longer parsed as options. Values that match an [expected argument](#args) are typed and added to the `args` array, and all remaining values are added to the `unknown` array (can be combined with [stopOnUnknown](#stop-on-unknown)). Without expected arguments, all values are added to the `unknown` array. ```typescript import { parseFlags } from "@cliffy/flags"; const argv = ["--debug-level", "warning", "server", "--port", "80"]; const flags = [{ name: "debug-level", type: "string" }]; const withArgs = parseFlags(argv, { stopEarly: true, flags, args: [ { name: "script", type: "string" }, { name: "args", type: "string", variadic: true, optional: true }, ], }); // withArgs.args -> [ "server", "--port", "80" ] // withArgs.unknown -> [] const withoutArgs = parseFlags(argv, { stopEarly: true, flags }); // withoutArgs.unknown -> [ "server", "--port", "80" ] ``` ## Stop on unknown If `stopOnUnknown` is enabled, all values starting from the first unknown option argument are no longer parsed as options. They are matched against the expected arguments the same way as with [stopEarly](#stop-early), and everything left over is added to the `unknown` array. ## Allow empty If a required option is specified, by default an error is thrown if the command is invoked without any flags. To disable this behavior you can set `allowEmpty` to `true`. The default is `false`. ## Dotted By default, all option names that have dots in their names are converted to nested objects. For example, `{ "foo.bar": 1 }` becomes `{ "foo": { "bar": 1 } }`. You can disable this behavior by setting the `dotted` option to `false`. This is required when parsing command line arguments in multiple steps (see [parse-context](https://cliffy.io/docs/v1.3.1/flags/index.md#parse-context)). --- # Flag options ## Name With the name you can specify the name of the flag. The name is also used as property name for the options object. ## Type With the `type` you specify the type of the argument. The type can be one of the following types or any custom types. - `boolean` - `string` - `number` - `integer` The `type` option is optional. If no type is specified the flag is a boolean option without a value. If `type` is set to `boolean` the flag can have a boolean value. ## Optional value By default the value is required for all options with an explicitly defined type. You can make the value optional by setting `optionalValue` to `true`. ## Variadic value You can make an argument variadic with the `variadic` option. A variadic value can occur multiple times and is collected in an array. ## List value If `list` is set to `true`, the argument will be split by `,` and the values will be stored in an array. You can override the separator with the `separator` option. ## Aliases You can specify some aliases for the flag with the `aliases` option. ## Standalone If `standalone` is set to `true`, this option cannot be combined with other options. If the `standalone` option is set to `true`, the option cannot be combined with other options. If additionally an option action handler is defined, only the option action handler is executed and not the command action handler. ## Default The `default` option specifies the default value for the flag when the flag is not specified on commandline. ## Required If `required` is set to `true` an error is thrown if the flag is not set on commandline. ## Empty values An empty value means the flag was not provided. `--foo ""` and `--foo=` are treated the same as leaving the flag out, so the [default](#default) value applies. If [required](#required) is set to `true`, an error is thrown instead. The same applies to positional arguments defined with the [args](https://cliffy.io/docs/v1.3.1/flags/parse_options.md#args) parse option, except for variadic arguments, where empty values are dropped from the list. See [empty values](https://cliffy.io/docs/v1.3.1/command/options.md#empty-values) for an example. ## Depends You can specify flags which must be provided if a specific flag is set. If `depends` is set to `["foo", "bar"]`, an error is thrown if one of this flags are not provided on commandline. ## Conflicts This is the opposite of the `depends` option. You can specify flags which can not be provided if a specific flag is set. If `conflicts` is set to `["foo", "bar"]`, an error is thrown if one of this flags is provided on commandline. Conflicts are only checked against explicitly provided flags (as command line argument or environment variable). A conflicting flag that only has its default value does not trigger an error. ## Equals sign If `equalsSign` is set to `true` the option must be called with an equals sign `--foo=bar`. This only has an effect for options with an **optional** value. For options with a **required** value, `equalsSign` has no effect, the option can always be called with or without an equals sign. ## Collect values If `collect` is enabled, a flag can be specified multiple times on commandline. All values will be collected into an array. ```typescript import { parseFlags } from "@cliffy/flags"; const { flags } = parseFlags(Deno.args, { flags: [{ name: "color", type: "string", collect: true, }], }); console.log(flags); ``` ```console $ deno run examples/flags/collect.ts --color red --color blue { color: ["red", "blue"] } ``` ## Map values You may specify a function to do custom processing of flag values. The callback function receives one parameter, the user specified value which is already parsed into the specified type. The return value of this method will be used as flag value. If [collect](#collect-values) is enabled the function receives as second parameter the previous value. This allows you to coerce the option value to the desired type, or accumulate values, or do entirely custom processing. ```typescript import { parseFlags, ValidationError } from "@cliffy/flags"; const { flags } = parseFlags(Deno.args, { flags: [{ name: "color", type: "string", collect: true, value(value: string, previous: Array): Array | undefined { if (["foo", "bar", "baz"].includes(value)) { return [...previous, value]; } // if no value is returned, a default validation error will be thrown. // You can use the `ValidationError` to provide a custom error message. throw new ValidationError( `Option "--value" must be one of "foo", "bar" or "baz", but got "${value}".`, ); }, }], }); console.log(flags); ``` ```console $ deno run examples/flags/value.ts --value fooo error: Uncaught Error: Option "--value" must be one of "foo", "bar" or "baz", but got "fooo". $ deno run examples/flags/value.ts --value foo { value: ["foo"] } ``` ## Args The `args` array can be used if the flag has multiple values. Following options can be set for each argument: - [type](#type) - optional (see [optionalValue](#optional-value)) - [variadic](#variadic-value) - [list](#list-value) ## Ignore defaults The `ignoreDefaults` option can be used to ignore the default values from specific options. The `ignoreDefaults` option is an object. The keys have to match the option name but the value can be anything (values are ignored). --- # Error handling You can catch validation errors with the `ValidationError` class. A validation error is thrown when an invalid command is invoked by the user. ```typescript import { parseFlags, ValidationError } from "@cliffy/flags"; try { const flags = parseFlags(Deno.args, { flags: [{ name: "debug", }], }); console.log(flags); } catch (error) { // Flags validation error. if (error instanceof ValidationError) { console.log("[VALIDATION_ERROR] %s", error.message); Deno.exit(1); } // General error. throw error; } ``` ```console $ deno run examples/flags/error_handling.ts -d [VALIDATION_ERROR] Unknown option "-d". Did you mean option "--debug"? ``` --- # Prompt Cliffy's prompt module allows you to create simple yet powerful interactive prompts for your command-line applications. ## Installation ### Deno ```bash deno add jsr:@cliffy/prompt ``` ### Pnpm ```bash pnpm add jsr:@cliffy/prompt ``` or (using pnpm 10.8 or older): ```bash pnpm dlx jsr add @cliffy/prompt ``` ### Yarn ```bash yarn add jsr:@cliffy/prompt ``` or (using Yarn 4.8 or older): ```bash yarn dlx jsr add @cliffy/prompt ``` ### Vlt ```bash vlt install jsr:@cliffy/prompt ``` ### Npm ```bash npx jsr add @cliffy/prompt ``` ### Bun ```bash bunx jsr add @cliffy/prompt ``` ## Usage There are two ways of using this module. You can either use standalone (single) prompts or you can run a list of prompts that can be dynamically controlled. ### Single prompt Each prompt type is usable as standalone module and can be imported directly from the prompt specific module or from the main module. Each prompt has a static `.prompt()` method which accepts a prompt message or an options object and returns the prompt result. Execute a single prompt with a message as first argument. ```typescript import { Input } from "@cliffy/prompt"; const name = await Input.prompt(`What's your name?`); ``` Execute a single prompt with an options object as first argument. ```typescript import { Input } from "@cliffy/prompt"; const name = await Input.prompt({ message: `Choose a username`, minLength: 8, }); ``` ### Prompt list To execute a list of prompts you can use the `prompt()` method. The prompt method accepts an array of [prompt options](https://cliffy.io/docs/v1.3.1/prompt/types/index.md) combined with a `name` and `type` property. > [!NOTE] > Make sure to give each prompt a unique name to prevent overwriting values! Unlike npm's inquirer, the `type` property accepts a prompt object and not the name of the prompt. ```typescript import { Checkbox, Confirm, Input, Number, prompt } from "@cliffy/prompt"; const result = await prompt([{ name: "name", message: "What's your name?", type: Input, }, { name: "age", message: "How old are you?", type: Number, }, { name: "like", message: "Do you like animals?", type: Confirm, }, { name: "animals", message: "Select some animals", type: Checkbox, options: ["dog", "cat", "snake"], }]); console.log(result); ``` ```console $ deno run examples/prompt/prompt_list.ts ``` --- # Types ## Base Options All prompt types have the following base options. ### Prompt message With the `message` option you specify the prompt message to display. This option is required for all prompts. ### Default value You can set a default value with the `default` option. The type depends on the prompt type. ### Hide default value By default the default value is displayed in the prompt. To hide the default value, you can set the `hideDefault` option to `true`. ### Transform displayed value The `transform` callback option lets you transform the value to display. The method receives the user input and the returned value will be displayed. ### Validate value With the `validate` callback option you can validate the user input. It receives as first argument the sanitized user input. - If `true` is returned, the value is valid. - If `false` is returned, an error message is shown. - If a `string` is returned, the value will be used as error message. ### Info message With the `hint` option you can display an info message that is displayed below the prompt. ### Pointer icon With the `pointer` option the pointer icon can be changed. ```ts import { colors } from "@cliffy/ansi/colors"; import { Input } from "@cliffy/prompt/input"; const result = await Input.prompt({ message: "Say hallo!", pointer: colors.bold.brightBlue("-->"), }); console.log({ result }); ``` ### Prompt indentation With the `indent` option you can change the prompt indentation. Default is `" "`. ### OS signals > [!WARNING] > The [cbreak](https://deno.land/api@v1.31.1?s=Deno.SetRawOptions#prop_cbreak) > option works currently only on Linux and macOS and is not supported with Bun! The `cbreak` option enables pass-through of os signals to deno, allowing you to register your own signal handler. Read more about os signals [here](https://cliffy.io/docs/v1.3.1/prompt/os_signals.md). ### Prefix By default each prompt message is prefixed with `yellow("? ")`. The prefix can be changed with the `prefix` option. The value supports ansi color codes. To disable the prefix, set the `prefix` option to an empty string `""`. ### Reader With the `reader` option you can change the input stream which defaults to `Deno.stdin`. ### Writer With the `writer` option you can change the output stream which defaults to `Deno.stdout`. ### Keymap With the `keys` option you can assign custom key names to prompt actions. The available actions depend on the prompt type. All text input prompts (`Input`, `Number`, `List`, `Secret` and `Confirm`), and also the search input of the `Select` and `Checkbox` prompts, support readline-style cursor navigation and word editing with the following default key bindings: | Action | Keymap name | Default keys | | ----------------- | ----------------- | ------------------------- | | Move cursor left | `moveCursorLeft` | `left` | | Move cursor right | `moveCursorRight` | `right` | | Move word left | `moveWordLeft` | `alt+left`, `ctrl+left` | | Move word right | `moveWordRight` | `alt+right`, `ctrl+right` | | Delete char left | `deleteCharLeft` | `backspace` | | Delete char right | `deleteCharRight` | `delete` | | Delete word left | `deleteWordLeft` | `ctrl+w`, `alt+backspace` | | Delete word right | `deleteWordRight` | `alt+d` | ```ts import { Input } from "@cliffy/prompt/input"; const sentence = await Input.prompt({ message: "Enter a sentence", keys: { deleteWordLeft: ["ctrl+w", "ctrl+backspace"], }, }); console.log({ sentence }); ``` --- # Input The `Input` prompt is a simple text input with support for [auto suggestions](#auto-suggestions). ![](https://cliffy.io/docs/v1.3.1/prompt/assets/img/input.gif) The `Input` prompt can be imported from `prompt/mod.ts` or `prompt/input.ts`. ```typescript import { Input } from "@cliffy/prompt/input"; const name = await Input.prompt("What's your github user name?"); ``` ```console $ deno run examples/prompt/input.ts ``` ## Options The `Input` prompt implements all [base](https://cliffy.io/docs/v1.3.1/prompt/types/index.md) and [auto suggestion](https://cliffy.io/docs/v1.3.1/prompt/auto_suggestions.md) options and the following prompt specific options. ### Min input length The `minLength` option specifies the minimum length of the input value. Default is `0`. ### Max input length The `maxLength` option specifies the maximum length of the input value. Default is `infinity`. ### List pointer With the `listPointer` you specify the list pointer icon. Default is `❯`. ### Display usage info The `info` option enables the info bar which displays some usage information. ### Auto suggestions Tab-completions can be enabled with the `suggestions` and/or `id` option. If an `id` is provided, the value will be saved to the local storage using the `id` as local storage key. With `suggestions` you can provide some default suggestions. Both options can be defined at the same time. You can read more about auto suggestions [here](https://cliffy.io/docs/v1.3.1/prompt/auto_suggestions.md). > [!NOTE] > The `id` option requires deno >= `1.10` and the `--location` flag. Since deno > `1.16.0` the `--location` flag is optional. ```typescript import { Input } from "@cliffy/prompt/input"; const color = await Input.prompt({ message: "Choose a color", id: "", suggestions: [ "Abbey", "Absolute Zero", "Acadia", "Acapulco", "Acid Green", "Aero", "Aero Blue", "Affair", "African Violet", "Air Force Blue", ], }); console.log({ color }); ``` ```console $ deno run examples/prompt/suggestions_list_prompt.ts ``` ![](https://cliffy.io/docs/v1.3.1/prompt/assets/img/suggestions.gif) --- # Number The `Number` prompt is a simple number input with support for [auto suggestions](#auto-suggestions). ![](https://cliffy.io/docs/v1.3.1/prompt/assets/img/number.gif) ```typescript import { Number } from "@cliffy/prompt/number"; const age = await Number.prompt("How old are you?"); ``` ```console $ deno run examples/prompt/number.ts ``` ## Options The `Number` prompt implements all [base](https://cliffy.io/docs/v1.3.1/prompt/types/index.md) and [auto suggestion](https://cliffy.io/docs/v1.3.1/prompt/auto_suggestions.md) options and the following prompt specific options. ### Min input value The `min` option specifies the minimum input value. Defaults to `-Infinity`. ### Max input value The `max` option specifies the maximum input value. Defaults to `Infinity`. ### Decimal value You can allow floating point inputs with the `float` option which defaults to `false`. The `round` option specifies the number of decimals to round. Defaults to `2`. ### List pointer With the `listPointer` you specify the list pointer icon. Default is `❯`. ### Display usage info The `info` option enables the info bar which displays some usage information. ### Auto suggestions Tab-completions can be enabled with the `suggestions` and/or `id` option. If an `id` is provided, the value will be saved to the local storage using the `id` as local storage key. With `suggestions` you can provide some default suggestions. Both options can be defined at the same time. You can read more about auto suggestions [here](https://cliffy.io/docs/v1.3.1/prompt/auto_suggestions.md). > [!NOTE] > The `id` option requires deno >= `1.10` and the `--location` flag. Since deno > `1.16.0` the `--location` flag is optional. ```typescript import { Number } from "@cliffy/prompt/number"; const color = await Number.prompt({ message: "Choose a number", id: "", suggestions: [ "111", "222", "333", ], }); console.log({ color }); ``` --- # Secret The `Secret` prompt is a hidden text input which doesn't display the input value. ![](https://cliffy.io/docs/v1.3.1/prompt/assets/img/secret.gif) ```typescript import { Secret } from "@cliffy/prompt/secret"; const password = await Secret.prompt("Enter your password"); ``` ```console $ deno run examples/prompt/secret.ts ``` ## Options The `Secret` prompt has all [base options](https://cliffy.io/docs/v1.3.1/prompt/types/index.md) and the following prompt specific options. ### Secret name You can change the displayed name of secret with the `label` option. Default is `Password`. ### Min secret length The `minLength` option specifies the minimum length of the input value. Default is `0`. ### Max secret length The `maxLength` option specifies the maximum length of the input value. Default is `infinity`. ### Hide input value By default a `*` is shown for each user input. If the `hidden` option is enabled the user input is hidden during typing and a fixed number of `*` characters is shown on success. --- # Confirm The `Confirm` prompt is a simple yes or no prompt. ![](https://cliffy.io/docs/v1.3.1/prompt/assets/img/confirm.gif) ```typescript import { Confirm } from "@cliffy/prompt/confirm"; const confirmed = await Confirm.prompt("Can you confirm?"); ``` ```console $ deno run examples/prompt/confirm.ts ``` ## Options The `Confirm` prompt has all [base options](https://cliffy.io/docs/v1.3.1/prompt/types/index.md) and the following prompt specific options. ### Active label The text for the active state can be changed with the `active` option. Defaults to `'Yes'`. ### Inactive label The text for the inactive state can be changed with the `inactive` option. Defaults to `'No'`. --- # Toggle The `Toggle` prompt is a simple yes or no switch. ![](https://cliffy.io/docs/v1.3.1/prompt/assets/img/toggle.gif) ```typescript import { Toggle } from "@cliffy/prompt/toggle"; const confirmed = await Toggle.prompt("Can you confirm?"); ``` ```console $ deno run examples/prompt/toggle.ts ``` ## Options The `Toggle` prompt has all [base options](https://cliffy.io/docs/v1.3.1/prompt/types/index.md) and the following prompt specific options. ### Active label The text for the active state can be changed with the `active` option. Defaults to `'Yes'`. ### Inactive label The text for the inactive state can be changed with the `inactive` option. Defaults to `'No'`. --- # List The `List` prompt is a text input that lets you input multiple values with support for [auto suggestions](#auto-suggestions). ![](https://cliffy.io/docs/v1.3.1/prompt/assets/img/list.gif) ```typescript import { List } from "@cliffy/prompt/list"; const keywords = await List.prompt("Enter some keywords"); ``` ```console $ deno run examples/prompt/list.ts ``` ## Options The `List` prompt implements all [base](https://cliffy.io/docs/v1.3.1/prompt/types/index.md) and [auto suggestion](https://cliffy.io/docs/v1.3.1/prompt/auto_suggestions.md) options and the following prompt specific options. ### Tag separator With the `separator` option you specify the delimiter that is used for separating the tags. Default is `,`. ### Min input length The `minLength` option specifies the minimum length of a tag. Default is `0`. ### Max input length The `maxLength` option specifies the maximum length of a tag. Default is `infinity`. ### Min tags The `minTags` option specifies the minimum amount of tags. Default is `0`. ### Max tags The `maxTags` option specifies the maximum amount of tags. Default is `infinity`. ### List pointer With the `listPointer` you specify the list pointer icon. Default is `❯`. ### Display usage info The `info` option enables the info bar which displays some usage information. ### Auto suggestions Tab-completions can be enabled with the `suggestions` and/or `id` option. If an `id` is provided, the value will be saved to the local storage using the `id` as local storage key. With `suggestions` you can provide some default suggestions. Both options can be defined at the same time. You can read more about auto suggestions [here](https://cliffy.io/docs/v1.3.1/prompt/auto_suggestions.md). > [!NOTE] > The `id` option requires deno >= `1.10` and the `--location` flag. Since deno > `1.16.0` the `--location` flag is optional. ```typescript import { List } from "@cliffy/prompt/list"; const color = await List.prompt({ message: "Choose a color", id: "", suggestions: [ "Abbey", "Absolute Zero", "Acadia", "Acapulco", "Acid Green", "Aero", "Aero Blue", "Affair", "African Violet", "Air Force Blue", ], }); console.log({ color }); ``` ```console $ deno run examples/prompt/suggestions_list_prompt.ts ``` ![](https://cliffy.io/docs/v1.3.1/prompt/assets/img/suggestions_list_prompt.gif) --- # Select The `Select` prompt lets you select a option from an options list. ![](https://cliffy.io/docs/v1.3.1/prompt/assets/img/select.gif) ```typescript import { Select } from "@cliffy/prompt/select"; const color = await Select.prompt({ message: "Pick a color", options: [ { name: "Red", value: "#ff0000" }, { name: "Green", value: "#00ff00", disabled: true }, { name: "Blue", value: "#0000ff" }, Select.separator("--------"), { name: "White", value: "#ffffff" }, { name: "Black", value: "#000000" }, ], }); ``` ```console $ deno run examples/prompt/select.ts ``` ## Options The `Select` prompt has all [base options](https://cliffy.io/docs/v1.3.1/prompt/types/index.md) and the following prompt specific options. ### Select options With the `options` option you specify an array of options. An option can be either a string or an options object. Options can also be nested, see [child options](#child-options). #### Select option ##### Option value Value which will be returned as result. ##### Option name Name is displayed in the list. Defaults to `value`. ##### Disable option Disabled item. Can't be selected. ### Max rows The `maxRows` option specifies the number of options displayed per page. Defaults to `10`. ### List pointer With the `listPointer` you specify the list pointer icon. Default is `❯`. ### Enable search input You can enable a search/filter input with the `search` option. The `search` option is useful if you have a large list of options. You can change the search input label with the `searchLabel` option. The `searchMode` option controls how options are matched, ranked and highlighted while searching. Defaults to `"all"`. - `"substring"`: classic contiguous substring match. - `"fuzzy"`: match the typed characters in order, allowing gaps (e.g. `strubu` matches `structure-builder`). - `"typo"`: tolerate misspellings via edit distance (e.g. `stroberry` matches `strawberry`), without fuzzy subsequence matching. - `"all"`: combines `"substring"`, `"fuzzy"` and `"typo"`. > [!NOTE] > While searching, the single-letter navigation keys (`j`, `k`, `n`, `p`, etc.) > are typed into the search input instead of moving the selection. Use the arrow > keys, or the readline shortcuts `ctrl+n` (next) and `ctrl+p` (previous), to > move through the list without leaving the search input. ### Display usage info The `info` option enables the info bar which displays some usage information. ### Child options The `options` option allows you to group options together. It accepts an array of child options, of which you can nest as many as you want. ```ts import { Select } from "@cliffy/prompt/select"; const title = await Select.prompt({ message: "Pick a book", search: true, options: [ { name: "Harry Potter", options: [ "Harry Potter and the Philosopher's Stone", "Harry Potter and the Chamber of Secrets", "Harry Potter and the Prisoner of Azkaban", "Harry Potter and the Goblet of Fire", "Harry Potter and the Order of the Phoenix", "Harry Potter and the Half-Blood Prince", "Harry Potter and the Deathly Hallows", ], }, { name: "Middle-Earth", options: [ "The Hobbit", { name: "The Lord of the Rings", options: [ "The Fellowship of the Ring", "The Two Towers", "The Return of the King", ], }, "Silmarillion", ], }, ], }); console.log({ title }); ``` #### Max breadcrumb items The `maxBreadcrumbItems` option limits the maximum number of breadcrumb items which will be displayed. #### Breadcrumb separator With the `breadcrumbSeparator` option the breadcrumb separator can be changed. #### Back pointer With the `backPointer` option you can change the icon of the _back_ option to any string value. #### Group pointer With the `groupPointer` option you can change the pointer of _group_ options to any string value. #### Group icon With the `groupIcon` option you can change the icon of _group_ options to any string value. #### Group open icon With the `groupOpenIcon` option you can change the icon of _opened group_ options to any string value. --- # Checkbox The `Checkbox` prompt is a multi select prompt. ![](https://cliffy.io/docs/v1.3.1/prompt/assets/img/checkbox.gif) ```typescript import { Checkbox } from "@cliffy/prompt/checkbox"; const colors = await Checkbox.prompt({ message: "Pick a color", options: [ { name: "Red", value: "#ff0000" }, { name: "Green", value: "#00ff00", disabled: true }, { name: "Blue", value: "#0000ff" }, Checkbox.separator("--------"), { name: "White", value: "#ffffff" }, { name: "Black", value: "#000000" }, ], }); ``` ```console $ deno run examples/prompt/checkbox.ts ``` ## Options The `Checkbox` prompt has all [base options](https://cliffy.io/docs/v1.3.1/prompt/types/index.md) and the following prompt specific options. ### Checkbox options With the `options` option you specify an array of options. An option can be either a string or an options object. Options can also be nested, see [options](#child-options). #### Checkbox option ##### Option value Value which will be returned as result. ##### Option name Name is displayed in the list. Defaults to `value`. ##### Disable option Disabled item. Can't be selected. ##### Checked option Whether item is checked or not. Defaults to `false`. ##### Option icon Show or hide item icon. Defaults to `true`. ### Min options The `minOptions` option specifies the minimum amount of selectable options. Default is `0`. ### Max options The `maxOptions` option specifies the maximum amount of selectable options. Default is `infinity`. ### Max rows The `maxRows` option specifies the number of options displayed per page. Defaults to `10`. ### List pointer With the `listPointer` you specify the list pointer icon. Default is `❯`. ### Enable search input You can enable a search/filter input with the `search` option. The `search` option is useful if you have a large list of options. You can change the search input label with the `searchLabel` option. The `searchMode` option controls how options are matched, ranked and highlighted while searching. Defaults to `"all"`. - `"substring"`: classic contiguous substring match. - `"fuzzy"`: match the typed characters in order, allowing gaps (e.g. `strubu` matches `structure-builder`). - `"typo"`: tolerate misspellings via edit distance (e.g. `stroberry` matches `strawberry`), without fuzzy subsequence matching. - `"all"`: combines `"substring"`, `"fuzzy"` and `"typo"`. > [!NOTE] > While searching, the single-letter navigation keys (`j`, `k`, `n`, `p`, etc.) > are typed into the search input instead of moving the selection. Use the arrow > keys, or the readline shortcuts `ctrl+n` (next) and `ctrl+p` (previous), to > move through the list without leaving the search input. ### check icon With the `check` option you can change the icon of a selected option to any string value. ### uncheck icon With the `uncheck` option you can change the icon of an unselected option to any string value. ### Partial check icon With the `partialCheck` option you can change the icon of a partial selected group option to any string value. Change the uncheck icon. ### Confirm submit If `confirmSubmit` is enabled, the user needs to press enter twice to submit. Default is `true`. ### Display usage info The `info` option enables the info bar which displays some usage information. ### Child options The `options` option allows you to group options together. It accepts an array of child options, of which you can nest as many as you want. ```ts import { Checkbox } from "@cliffy/prompt/checkbox"; const title = await Checkbox.prompt({ message: "Pick some books", search: true, options: [ { name: "Harry Potter", options: [ "Harry Potter and the Philosopher's Stone", "Harry Potter and the Chamber of Secrets", "Harry Potter and the Prisoner of Azkaban", "Harry Potter and the Goblet of Fire", "Harry Potter and the Order of the Phoenix", "Harry Potter and the Half-Blood Prince", "Harry Potter and the Deathly Hallows", ], }, { name: "Middle-Earth", options: [ "The Hobbit", { name: "The Lord of the Rings", options: [ "The Fellowship of the Ring", "The Two Towers", "The Return of the King", ], }, "Silmarillion", ], }, ], }); console.log({ title }); ``` #### Max breadcrumb items The `maxBreadcrumbItems` option limits the maximum number of breadcrumb items which will be displayed. #### Breadcrumb separator With the `breadcrumbSeparator` option the breadcrumb separator can be changed. #### Back pointer With the `backPointer` option you can change the icon of the _back_ option to any string value. #### Group pointer With the `groupPointer` option you can change the pointer of _group_ options to any string value. #### Group icon With the `groupIcon` option you can change the icon of _group_ options to any string value. #### Group open icon With the `groupOpenIcon` option you can change the icon of _opened group_ options to any string value. --- # Dynamic prompts You can dynamically control the flow of the prompt list with the `before` and `after` callbacks which work like a middleware function. ```typescript import { Checkbox, Confirm, Number, prompt } from "@cliffy/prompt"; const result = await prompt([{ name: "animals", message: "Select some animals", type: Checkbox, options: ["dog", "cat", "snake"], }, { name: "like", message: "Do you like animals?", type: Confirm, after: async ({ like }, next) => { // executed after like prompt if (like) { await next(); // run age prompt } else { await next("like"); // run like prompt again } }, }, { name: "age", message: "How old are you?", type: Number, before: async ({ animals }, next) => { // executed before age prompt if (animals?.length === 3) { await next(); // run age prompt } else { await next("animals"); // begin from start } }, }]); console.log(result); ``` ```console $ deno run examples/prompt/dynamic_prompts.ts ``` ## Options Following options are available as global and/or prompt specific options. Global options will be passed as second argument to the `prompt()` method. ### Prompt name The `name` option is required for each prompt and is used as key for the results object where the answer of the prompt is stored. ### Prompt type The `type` option is required for each prompt and specifies the type of the prompt. ### Before and after hooks The `before` callback method is called before and the `after` callback method after the prompt is executed. It is available as global and prompt specific option. The first argument is the `result` object which contains all already available answers. The second argument is the `next()` method which executes the next prompt in the list (for the before callback it's the current prompt). To jump to a specific prompt you can pass the name or index of the prompt to the `next()` method. To skip this prompt you can pass `true` to the `next()` method. If `next()` isn't called all other prompts will be skipped. ### OS signals > [!WARNING] > The [cbreak](https://deno.land/api@v1.31.1?s=Deno.SetRawOptions#prop_cbreak) > option works currently only on Linux and macOS and is not supported with Bun! The `cbreak` option enables pass-through of os signals to deno, allowing you to register your own signal handler. It is available as global and prompt specific option. Read more about os signals [here](https://cliffy.io/docs/v1.3.1/prompt/os_signals.md). --- # Auto suggestions You can provide suggestions to the [input](https://cliffy.io/docs/v1.3.1/prompt/types/input.md), [number](https://cliffy.io/docs/v1.3.1/prompt/types/number.md) and [list](https://cliffy.io/docs/v1.3.1/prompt/types/list.md) prompt to enable tab-completions with the [suggestions](#suggestions) and/or [id](#local-storage-id) option. If an `id` is provided, the values will be saved to the local storage using the `id` as local storage key. With `suggestions` you can provide some default suggestions. Both options can be defined at the same time. ```shell deno install you/cli.ts --location https://example.com # or deno run you/cli.ts --location https://example.com ``` ## Options ### Suggestions The `suggestions` option specifies a list of default suggestions. ```typescript import { Input } from "@cliffy/prompt/input"; const color = await Input.prompt({ message: "Choose a color", suggestions: [ "Abbey", "Absolute Zero", "Acadia", "Acapulco", "Acid Green", "Aero", "Aero Blue", "Affair", "African Violet", "Air Force Blue", ], }); console.log({ color }); ``` ```console $ deno run examples/prompt/suggestions.ts ``` ![](https://cliffy.io/docs/v1.3.1/prompt/assets/img/suggestions.gif) ### Local storage id If the `id` option is provided, values are stored in the local storage using the id as local storage key. The stored values are used as suggestions the next time the prompt is used. > [!NOTE] > The `id` option requires deno >= `1.10` and the `--location` flag. Since deno > `1.16.0` the `--location` flag is optional. ### Path completions To enable path completions for relative and absolute files, set the `files` option to `true`. Path completions is only available for `Input` and `List` prompts. ### Suggestions list With the `list` option you can display a list of suggestions. Matched suggestions are highlighted in the list. Press `tab` to complete the highlighted suggestion, or `enter` to accept and submit it (see [Complete on submit](#complete-on-submit)). You can also display the info bar with the `info` option to show the number of available suggestions and usage information. ```typescript import { Input } from "@cliffy/prompt/input"; const color = await Input.prompt({ message: "Choose a color", list: true, info: true, suggestions: [ "Abbey", "Absolute Zero", "Acadia", "Acapulco", "Acid Green", "Aero", "Aero Blue", "Affair", "African Violet", "Air Force Blue", ], }); console.log({ color }); ``` ```console $ deno run examples/prompt/suggestions_list.ts ``` ![](https://cliffy.io/docs/v1.3.1/prompt/assets/img/suggestions_list.gif) #### Max suggestions With the `maxRows` option you specify the number of suggestions displayed per page. Defaults to `10`. ### Search mode The `searchMode` option controls the matching strategy used to filter, rank and highlight suggestions. Defaults to `"all"`. - `"substring"`: classic contiguous substring match. - `"fuzzy"`: match the typed characters in order, allowing gaps (e.g. `strubu` matches `structure-builder`). - `"typo"`: tolerate misspellings via edit distance (e.g. `stroberry` matches `strawberry`), without fuzzy subsequence matching. - `"all"`: combines `"substring"`, `"fuzzy"` and `"typo"`. Substring matches always rank above fuzzy matches, which always rank above typo-tolerant matches, regardless of their inner scores. ```ts import { Input } from "@cliffy/prompt/input"; const color = await Input.prompt({ message: "Choose a color", list: true, searchMode: "substring", suggestions: ["Abbey", "Acadia", "Aero"], }); console.log({ color }); ``` ### Complete on submit The `completeOnSubmit` option controls whether the highlighted suggestion is accepted when the prompt is submitted. When enabled and a suggestion is highlighted, pressing `enter` completes the highlighted suggestion and submits it instead of the raw input value. If no suggestion matches the current input, the typed value is submitted. A custom `complete` handler and [path completions](#path-completions) are respected. The default depends on the `list` option: - With `list: true`, suggestions are shown as a highlighted list and `completeOnSubmit` defaults to `true`, so `enter` accepts the highlighted entry (menu-like behavior). - For inline suggestions (`list` disabled), it defaults to `false`, so `enter` submits the typed value, like fish/zsh autosuggestions. Set the option explicitly to override the per-mode default: ```ts import { Input } from "@cliffy/prompt/input"; const color = await Input.prompt({ message: "Choose a color", list: true, suggestions: ["Abbey", "Acadia", "Aero"], completeOnSubmit: false, }); console.log({ color }); ``` To submit a value that is a prefix of a suggestion (for example `Ab` while `Abbey` is highlighted), press `escape` to dismiss the highlighted suggestion, then `enter` to submit the typed value. Typing or navigating the list re-activates suggestions. The dismiss key can be changed with the `deselect` keymap (`keys: { deselect: ["escape"] }`). --- # OS signals > [!WARNING] > The [cbreak](https://deno.land/api@v1.31.1?s=Deno.SetRawOptions#prop_cbreak) > option works currently only on Linux and macOS and is not supported with Bun! By default, cliffy will call `Deno.exit(0)` after the user presses `ctrl+c`. If you need to use a custom signal handler, you can enable the `cbreak` option on your prompt. This will enable pass-through of os signals to deno, allowing you to register your own signal handler. > When using prompts like `Select` or `Toggle` with the `cbreak` option enabled, > you have to show the cursor and clear the stdout before calling `Deno.exit()` > manually. Maybe this will be improved somehow in the future. ```typescript import { tty } from "@cliffy/ansi/tty"; import { Toggle } from "@cliffy/prompt/toggle"; Deno.addSignalListener("SIGINT", () => { tty.cursorLeft.eraseDown.cursorShow(); console.log("interrupted!"); Deno.exit(1); }); const confirmed = await Toggle.prompt({ message: "Please confirm", cbreak: true, }); console.log({ confirmed }); ``` ```console $ deno run examples/prompt/os_signals.ts ``` --- # Keycode ANSI key code parser. ## Installation ### Deno ```bash deno add jsr:@cliffy/keycode ``` ### Pnpm ```bash pnpm add jsr:@cliffy/keycode ``` or (using pnpm 10.8 or older): ```bash pnpm dlx jsr add @cliffy/keycode ``` ### Yarn ```bash yarn add jsr:@cliffy/keycode ``` or (using Yarn 4.8 or older): ```bash yarn dlx jsr add @cliffy/keycode ``` ### Vlt ```bash vlt install jsr:@cliffy/keycode ``` ### Npm ```bash npx jsr add @cliffy/keycode ``` ### Bun ```bash bunx jsr add @cliffy/keycode ``` ## Usage The `keycode` module exports a `parse()` method which accepts an ansi string and returns an array of [`KeyCode`](https://cliffy.io/docs/v1.3.1/keycode/keycode.md). ```typescript import { parse } from "@cliffy/keycode"; console.log( parse( "\x1b[A\x1b[B\x1b[C\x1b[D\x1b[E\x1b[F\x1b[H", ), ); ``` ```console $ deno run examples/keycode/example.ts ``` **Output:** ```json [ { name: "up", sequence: "\x1b[A", code: "[A", ctrl: false, meta: false, shift: false }, { name: "down", sequence: "\x1b[B", code: "[B", ctrl: false, meta: false, shift: false }, { name: "right", sequence: "\x1b[C", code: "[C", ctrl: false, meta: false, shift: false }, { name: "left", sequence: "\x1b[D", code: "[D", ctrl: false, meta: false, shift: false }, { name: "clear", sequence: "\x1b[E", code: "[E", ctrl: false, meta: false, shift: false }, { name: "end", sequence: "\x1b[F", code: "[F", ctrl: false, meta: false, shift: false }, { name: "home", sequence: "\x1b[H", code: "[H", ctrl: false, meta: false, shift: false } ] ``` ## Example ### Deno ```typescript import { KeyCode, parse } from "@cliffy/keycode"; async function* keypress(): AsyncGenerator { while (true) { const data = new Uint8Array(8); Deno.stdin.setRaw(true); const nread = await Deno.stdin.read(data); Deno.stdin.setRaw(false); if (nread === null) { return; } const keys: Array = parse(data.subarray(0, nread)); for (const key of keys) { yield key; } } } console.log("Hit ctrl + c to exit."); for await (const key of keypress()) { if (key.ctrl && key.name === "c") { console.log("exit"); break; } console.log(key); } ``` ### Deno ```console $ deno run examples/keycode/read_key.ts ``` --- # Keycode The `KeyCode` interface represents a parsed ansi sequence: ```json { "name": "up", "sequence": "\x1b[A", "code": "[A", "ctrl": false, "meta": false, "shift": false } ``` ### Name The `name` property defines the name of the key code. The type is `string | undefined`. ### Char The `char` property defines the pressed character of the key code. The type is `string | undefined`. ### Sequence The `sequence` property defines the ansi sequence of the key code. The type is `string | undefined`. ### Code The `code` property defines the ansi code of the key code. The type is `string | undefined`. ### Ctrl The `ctrl` property defines whether the ctrl key is pressed or not. The type is `boolean | undefined`. ### Meta The `meta` property defines whether the meta key is pressed or not. The type is `boolean | undefined`. ### Shift The `shift` property defines whether the shift key is pressed or not. The type is `boolean | undefined`. --- # Keypress Keypress module with promise, async iterator and event target API. ## Installation ### Deno ```bash deno add jsr:@cliffy/keypress ``` ### Pnpm ```bash pnpm add jsr:@cliffy/keypress ``` or (using pnpm 10.8 or older): ```bash pnpm dlx jsr add @cliffy/keypress ``` ### Yarn ```bash yarn add jsr:@cliffy/keypress ``` or (using Yarn 4.8 or older): ```bash yarn dlx jsr add @cliffy/keypress ``` ### Vlt ```bash vlt install jsr:@cliffy/keypress ``` ### Npm ```bash npx jsr add @cliffy/keypress ``` ### Bun ```bash bunx jsr add @cliffy/keypress ``` ## Usage There are two ways to use this module. You can use the `keypress()` method, which returns a global instance of the `KeyPress` class, or you can create a new instance of the `KeyPress` class. There is no difference between these two ways, except that the `keypress()` method always returns the same instance, unless the `.dispose()` method is called. In this case a new instance is returned. ### Promise The keypress module can be used as promise. It reads one chunk from stdin and returns a `KeyPressEvent` for the first parsed character. ```typescript import { keypress, KeyPressEvent } from "@cliffy/keypress"; const event: KeyPressEvent = await keypress(); console.log( "type: %s, key: %s, ctrl: %s, meta: %s, shift: %s, alt: %s, repeat: %s", event.type, event.key, event.ctrlKey, event.metaKey, event.shiftKey, event.altKey, event.repeat, ); ``` ```console $ deno run --reload examples/keypress/promise.ts ``` ### Async Iterator The keypress module can be used as async iterator to iterate over all keypress events. The async iterator reads chunk by chunk from stdin. On each step, it reads one chunk from stdin and emits for each character a keypress event. It pauses reading from stdin before emitting the events, so stdin is not blocked inside the for loop. ```typescript import { keypress, KeyPressEvent } from "@cliffy/keypress"; for await (const event of keypress()) { console.log( "type: %s, key: %s, ctrl: %s, meta: %s, shift: %s, alt: %s, repeat: %s", event.type, event.key, event.ctrlKey, event.metaKey, event.shiftKey, event.altKey, event.repeat, ); if (event.ctrlKey && event.key === "c") { console.log("exit"); break; } } ``` ```console $ deno run --reload examples/keypress/async_iterator.ts ``` ### Event Target The Keypress class extends the EventTarget class that provides a `.addEventListener()` method that can be used to register event listeners. The addEventListener method starts an event loop in the background that reads from stdin and emits an event for each input. > [!NOTE] > As long as the event loop is running, stdin is blocked for other resources. You can stop the event loop with `keypress().dispose()`. > [!NOTE] > The promise and async iterator based solution does not start an event loop in > the background. ```typescript import { keypress, KeyPressEvent } from "@cliffy/keypress"; keypress().addEventListener("keydown", (event: KeyPressEvent) => { console.log( "type: %s, key: %s, ctrl: %s, meta: %s, shift: %s, alt: %s, repeat: %s", event.type, event.key, event.ctrlKey, event.metaKey, event.shiftKey, event.altKey, event.repeat, ); if (event.ctrlKey && event.key === "c") { console.log("exit"); keypress().dispose(); } }); ``` ```console $ deno run --reload examples/keypress/event_target.ts ``` --- # KeyPressEvent The `KeyPressEvent` represents a keypress event and it inherits most properties from the [`KeyCode`](https://cliffy.io/docs/v1.3.1/keycode/keycode.md) interface. ```json { "name": "up", "sequence": "\x1b[A", "code": "[A", "ctrlKey": false, "metaKey": false, "shiftKey": false, "altKey": false } ``` ### key The `key` property defines the name of the key code. The type is `string | undefined`. ### char The `char` property defines the pressed character of the key code. The type is `string | undefined`. ### sequence The `sequence` property defines the ansi sequence of the key code. The type is `string | undefined`. ### code The `code` property defines the ansi code of the key code. The type is `string | undefined`. ### ctrlKey The `ctrlKey` property defines whether the ctrl key is pressed or not. The type is `boolean`. ### metaKey The `metaKey` property defines whether the meta key is pressed or not. The type is `boolean`. ### shiftKey The `shiftKey` property defines whether the shift key is pressed or not. The type is `boolean`. ### altKey The `altKey` property defines whether the alt key is pressed or not. The type is `boolean`. ### repeat The `repeat` property indicates how many times the key was pressed repeatedly. --- # Table Fast and customizable table module to render unicode tables on the command line. ## Installation ### Deno ```bash deno add jsr:@cliffy/table ``` ### Pnpm ```bash pnpm add jsr:@cliffy/table ``` or (using pnpm 10.8 or older): ```bash pnpm dlx jsr add @cliffy/table ``` ### Yarn ```bash yarn add jsr:@cliffy/table ``` or (using Yarn 4.8 or older): ```bash yarn dlx jsr add @cliffy/table ``` ### Vlt ```bash vlt install jsr:@cliffy/table ``` ### Npm ```bash npx jsr add @cliffy/table ``` ### Bun ```bash bunx jsr add @cliffy/table ``` ## Usage ### Basic Usage To create a table you can simply create an instance of the `Table` class and pass the rows as arguments to the constructor. The example below will output a simple table with three rows and without any styles. The only default option is `padding` which is set to `1`. ```ts import { Table } from "@cliffy/table"; const table: Table = new Table( ["Baxter Herman", "Oct 1, 2020", "Harderwijk", "Slovenia"], ["Jescie Wolfe", "Dec 4, 2020", "Alto Hospicio", "Japan"], ["Allegra Cleveland", "Apr 16, 2020", "Avernas-le-Bauduin", "Samoa"], ["Aretha Gamble", "Feb 22, 2021", "Honolulu", "Georgia"], ); console.log(table.toString()); ``` ```console $ deno run examples/table/basic_usage.ts ``` ![](https://cliffy.io/docs/v1.3.1/table/assets/img/basic_usage.gif) ### Using as Array Since the `Table` class is an `Array`, you can call all the methods of the array class like `.from()`, `.sort()`, `.push()`, `.unshift()` and friends. ```ts import { Table } from "@cliffy/table"; const table: Table = Table.from([ ["Baxter Herman", "Oct 1, 2020", "Harderwijk", "Slovenia"], ["Jescie Wolfe", "Dec 4, 2020", "Alto Hospicio", "Japan"], ["Allegra Cleveland", "Apr 16, 2020", "Avernas-le-Bauduin", "Samoa"], ]); table.push(["Aretha Gamble", "Feb 22, 2021", "Honolulu", "Georgia"]); table.sort(); table.render(); ``` ```console $ deno run examples/table/using_as_array.ts ``` ![](https://cliffy.io/docs/v1.3.1/table/assets/img/using_as_array.gif) --- # Table options To customize the table, the table class provides a few chainable option methods. To see a list of all available options go to the [Table](#table) API section. ```ts import { Table } from "@cliffy/table"; new Table() .header(["Name", "Date", "City", "Country"]) .body([ ["Baxter Herman", "Oct 1, 2020", "Harderwijk", "Slovenia"], ["Jescie Wolfe", "Dec 4, 2020", "Alto Hospicio", "Japan"], ["Allegra Cleveland", "Apr 16, 2020", "Avernas-le-Bauduin", "Samoa"], ["Aretha Gamble", "Feb 22, 2021", "Honolulu", "Georgia"], ]) .maxColWidth(10) .padding(1) .indent(2) .border() .render(); ``` ```console $ deno run examples/table/table_options.ts ``` ![](https://cliffy.io/docs/v1.3.1/table/assets/img/table_options.gif) ## Header and Body To define a table header you can use the `.header()` method. The header is not affected by any `Array` method like `.sort()` because it is stored as a separate property and not in the array stack. The `.body()` method adds an array of rows to the table and removes all existing rows. The first argument of the `.header()` method can be an `Array` of `string` and/or `Cell`. The first argument of the `.body()` can be an `Array` of rows and a row can be an `Array` of `string` and `Cell`. You can read more about rows and cells [here](https://cliffy.io/docs/v1.3.1/table/rows_and_cells.md). ```ts import { Table } from "@cliffy/table"; new Table() .header(["Name", "Date", "City", "Country"]) .body([ ["Baxter Herman", "Oct 1, 2020", "Harderwijk", "Slovenia"], ["Jescie Wolfe", "Dec 4, 2020", "Alto Hospicio", "Japan"], ["Allegra Cleveland", "Apr 16, 2020", "Avernas-le-Bauduin", "Samoa"], ["Aretha Gamble", "Feb 22, 2021", "Honolulu", "Georgia"], ]) .render(); ``` ```console $ deno run examples/table/header_and_body.ts ``` ![](https://cliffy.io/docs/v1.3.1/table/assets/img/header_and_body.gif) ## Columns The `.columns(columns)` method can be used to set column options of multiple columns. All available column options can be found [here](https://cliffy.io/docs/v1.3.1/table/columns.md). ```ts import { Column, Table } from "@cliffy/table"; new Table() .body([ ["Baxter Herman", "Oct 1, 2020", "Harderwijk", "Slovenia"], ["Jescie Wolfe", "Dec 4, 2020", "Alto Hospicio", "Japan"], ["Allegra Cleveland", "Apr 16, 2020", "Avernas-le-Bauduin", "Samoa"], ["Aretha Gamble", "Feb 22, 2021", "Honolulu", "Georgia"], ]) .columns([ { border: true }, new Column().align("right"), ]) .render(); ``` ## Column With the `.column(index, options)` method you can set options for a single column at a specific index. All available column options can be found [here](https://cliffy.io/docs/v1.3.1/table/columns.md). ```ts import { Column, Table } from "@cliffy/table"; new Table() .body([ ["Baxter Herman", "Oct 1, 2020", "Harderwijk", "Slovenia"], ["Jescie Wolfe", "Dec 4, 2020", "Alto Hospicio", "Japan"], ["Allegra Cleveland", "Apr 16, 2020", "Avernas-le-Bauduin", "Samoa"], ["Aretha Gamble", "Feb 22, 2021", "Honolulu", "Georgia"], ]) .column(0, { border: true, }) .column(1, new Column().align("right")) .render(); ``` ## Render The `.render()` method outputs the table to stdout. If you need the output as string you can use the `.toString()` method to return the table as string. ## Column width You can set the min/max width of columns with the `.minColWidth()` and `.maxColWidth()` methods. ## Max width The `.maxWidth(width)` method sets the maximum total width of the table in columns. This is required for `flexGrow`, `flexShrink`, and `flex` to work. Without a finite max width the layout has no target and flex is a no-op. ```ts import { Table } from "@cliffy/table"; new Table() .body([["foo", "bar"]]) .maxWidth(80) .flexShrink([0, 1]) .render(); ``` ## Responsive flex The `.flexGrow()`, `.flexShrink()`, and `.flex()` table-level methods set a default flex weight for all columns. They accept either a single number (applied to every column) or an array of per-column weights. > [!NOTE] > A finite `.maxWidth()` must be set on the table for flex to have any effect. ```ts import { Table } from "@cliffy/table"; // Grow the second column to fill available space, keep others rigid. new Table() .body([["Name", "Description", "Version"]]) .maxWidth(100) .flexGrow([0, 1, 0]) .render(); // Shrink only the second column on overflow. new Table() .body([["Name", "Description", "Version"]]) .maxWidth(60) .flexShrink([0, 1, 0]) .render(); // Shorthand: grow and shrink together. new Table() .body([["Name", "Description", "Version"]]) .maxWidth(80) .flex([0, 1, 0]) .render(); ``` For per-column control use the `Column` class — see [Flex grow](https://cliffy.io/docs/v1.3.1/table/columns.md#flex-grow), [Flex shrink](https://cliffy.io/docs/v1.3.1/table/columns.md#flex-shrink), and [Flex (shorthand)](https://cliffy.io/docs/v1.3.1/table/columns.md#flex-shorthand) in the column docs. ## Cell padding The `.padding()` method adds padding to all cells. ## Border You can enable border by using the `.border()` method. ### Border style With the `.chars()` method you can change the border style. Here is an example of the default border characters: ```json { "top": "─", "topMid": "┬", "topLeft": "┌", "topRight": "┐", "bottom": "─", "bottomMid": "┴", "bottomLeft": "└", "bottomRight": "┘", "left": "│", "leftMid": "├", "mid": "─", "midMid": "┼", "right": "│", "rightMid": "┤", "middle": "│" } ``` ## Align content The content can be aligned with the `.align()` method. The first argument is the direction. Possible values are: - `"left"` - `"right"` - `"center"` ## Table indent With the `.indent()` method you can add indentation to the table. ## Clone The `.clone()` method clones the entire table. --- # Column The `Column` class is used to set table column options. A `Column` class can be added to the `Table` with the [.columns()](https://cliffy.io/docs/v1.3.1/table/options.md) and [.column()](https://cliffy.io/docs/v1.3.1/table/options.md) method. ```ts import { Column, Table } from "@cliffy/table"; new Table() .body([ ["Baxter Herman", "Oct 1, 2020", "Harderwijk", "Slovenia"], ["Jescie Wolfe", "Dec 4, 2020", "Alto Hospicio", "Japan"], ["Allegra Cleveland", "Apr 16, 2020", "Avernas-le-Bauduin", "Samoa"], ["Aretha Gamble", "Feb 22, 2021", "Honolulu", "Georgia"], ]) .columns([ { border: true }, new Column().align("right"), ]) .render(); ``` ## Column options ### Border You can enable/disable the border by using the `.border()` method which accepts an optional boolean value. ### Align column content The content can be aligned with the `.align()` method. The first argument is the direction. Possible values are: - `"left"` - `"right"` - `"center"` ### Column width You can set the min and max width of columns with the `.minColWidth()` and `.maxColWidth()` methods. ### Cell padding The `.padding()` method adds padding to all cells in this column. ### Flex grow The `.flexGrow(weight)` method allows a column to expand into available slack. Follows CSS `flex-grow` semantics: available space is distributed proportionally by weight, so a column with weight `2` receives twice the extra space of one with weight `1`. The default is `0` (no grow). > [!NOTE] > Flex only takes effect when `maxWidth` is set to a finite value on the table. > Without it the layout has no target width and flex is a no-op. ```ts import { Column, Table } from "@cliffy/table"; new Table() .body([ ["Name", "Description"], ["foo", "A short value"], ["bar", "A much longer description that needs more space"], ]) .maxWidth(80) // required — flex is a no-op without a finite maxWidth .columns([ new Column().minWidth(10), new Column().flexGrow(1), // expand to fill up to 80 columns ]) .render(); ``` ### Flex shrink The `.flexShrink(weight)` method allows a column to give up space when the table is wider than `maxWidth`. Follows CSS `flex-shrink` semantics: each column's share of the reduction is proportional to `weight × width`, so a wider column or one with a higher weight absorbs more of the overflow. The default is `0` (rigid — never shrinks). > [!NOTE] > Flex only takes effect when `maxWidth` is set to a finite value on the table. > Without it the layout has no target width and flex is a no-op. ```ts import { Column, Table } from "@cliffy/table"; new Table() .body([ ["Name", "Description"], ["foo", "A short value"], ["bar", "A much longer description that can be compressed"], ]) .maxWidth(40) // required — triggers shrink when content exceeds this width .columns([ new Column().minWidth(10), new Column().flexShrink(1), // absorb overflow proportionally ]) .render(); ``` ### Flex (shorthand) The `.flex(weight)` method is a shorthand that sets both `.flexGrow` and `.flexShrink` to the same weight. The column will both expand into and contract out of available space. > [!NOTE] > Flex only takes effect when `maxWidth` is set to a finite value on the table. > Without it the layout has no target width and flex is a no-op. ```ts import { Column, Table } from "@cliffy/table"; new Table() .body([ ["Name", "Description"], ["foo", "A short value"], ["bar", "A much longer description"], ]) .maxWidth(80) // required — flex is a no-op without a finite maxWidth .columns([ new Column().minWidth(10), new Column().flex(1), // grow and shrink proportionally ]) .render(); ``` ### Set multiple options The `.options()` method allows you to set multiple options at once by passing an options bag to the `.options()` method. --- # Rows and cells It is also possible to customize single rows and cells. To do this you can use the `Row` and `Cell` class. The `Row` class is also an `Array` class like the `Table` class. ```ts import { Cell, Row, Table } from "@cliffy/table"; new Table() .header(Row.from(["Name", "Date", "City", "Country"]).border()) .body([ [ "Baxter Herman", new Cell("Oct 1, 2020").border(), "Row 1 Column 3", "Harderwijk", "Slovenia", ], new Row("Jescie Wolfe", "Dec 4, 2020", "Alto Hospicio", "Japan").border( true, ), ["Allegra Cleveland", "Apr 16, 2020", "Avernas-le-Bauduin", "Samoa"], ["Aretha Gamble", "Feb 22, 2021", "Honolulu", "Georgia"], ]) .render(); ``` ```console $ deno run examples/table/rows_and_cells.ts ``` ![](https://cliffy.io/docs/v1.3.1/table/assets/img/rows_and_cells.gif) ## Rows ### Row border To enable row border you can use the `.border()` method. ### Align row content The `.align()` method aligns the content of all cells in the row. The first argument is the direction. Possible values are: - `"left"` - `"right"` - `"center"` ### Clone row The `.clone()` method clones the entire row. ## Cells ### Cell border With the `.border()` method you can add border to a cell. ### Align cell content The `.align()` method aligns the content of the cell. The first argument is the direction. Possible values are: - `"left"` - `"right"` - `"center"` ### Colspan and rowspan `.colSpan()` and `.rowSpan()` allows a single table cell to span the width/height of more than one column and/or row. With `.colSpan()` and `.rowSpan()` the next or lower cell is moved to the right if the next or lower cell is not of type `undefined`. If it's of type `undefined` the cell is overridden. The following examples both have the same output. **Override undefined values** ```ts import { Cell, Table } from "@cliffy/table"; Table.from([ [ new Cell("Row 1 & 2 Column 1").rowSpan(2), "Row 1 Column 2", "Row 1 Column 3", ], [undefined, new Cell("Row 2 Column 2 & 3").colSpan(2), undefined], [ new Cell("Row 3 & 4 Column 1").rowSpan(2), "Row 3 Column 2", "Row 3 Column 3", ], [undefined, new Cell("Row 4 Column 2 & 3").colSpan(2), undefined], [ "Row 5 Column 1", new Cell("Row 5 & 6 Column 2 & 3").rowSpan(2).colSpan(2), undefined, ], ["Row 6 Column 1", undefined, undefined], ]) .border() .render(); ``` **Omit undefined values** ```ts import { Cell, Table } from "@cliffy/table"; Table.from([ [ new Cell("Row 1 & 2 Column 1").rowSpan(2), "Row 1 Column 2", "Row 1 Column 3", ], [new Cell("Row 2 Column 2 & 3").colSpan(2)], [ new Cell("Row 3 & 4 Column 1").rowSpan(2), "Row 3 Column 2", "Row 3 Column 3", ], [new Cell("Row 4 Column 2 & 3").colSpan(2)], ["Row 5 Column 1", new Cell("Row 5 & 6 Column 2 & 3").rowSpan(2).colSpan(2)], ["Row 6 Column 1"], ]) .border() .render(); ``` ```console $ deno run examples/table/colspan_and_rowspan.ts ``` ![](https://cliffy.io/docs/v1.3.1/table/assets/img/colspan_and_rowspan.gif) ### Clone cell To clone a single cell you can use the `.clone()` method. --- # Ansi Chainable ansi escape sequences. ![](https://cliffy.io/docs/v1.3.1/ansi/assets/img/demo.gif) ## Installation ### Deno ```bash deno add jsr:@cliffy/ansi ``` ### Pnpm ```bash pnpm add jsr:@cliffy/ansi ``` or (using pnpm 10.8 or older): ```bash pnpm dlx jsr add @cliffy/ansi ``` ### Yarn ```bash yarn add jsr:@cliffy/ansi ``` or (using Yarn 4.8 or older): ```bash yarn dlx jsr add @cliffy/ansi ``` ### Vlt ```bash vlt install jsr:@cliffy/ansi ``` ### Npm ```bash npx jsr add @cliffy/ansi ``` ### Bun ```bash bunx jsr add @cliffy/ansi ``` ## Usage ### Ansi escape sequences The [ansi](https://cliffy.io/docs/v1.3.1/ansi/ansi.md) and [tty](https://cliffy.io/docs/v1.3.1/ansi/tty.md) module can be used to generate or write ansi escape sequences to stdout. ```typescript import { tty } from "@cliffy/ansi/tty"; tty.cursorSave .cursorHide .cursorTo(0, 0) .eraseScreen(); ``` ### Colors The [colors](https://cliffy.io/docs/v1.3.1/ansi/colors.md) module is a simple and tiny chainable wrapper for [@std/fmt/colors](https://jsr.io/@std/fmt@1.0.3/doc/colors) module and works similarly to node's [chalk](https://github.com/chalk/chalk) module. ```typescript import { colors } from "@cliffy/ansi/colors"; console.log( colors.bold.underline.rgb24("Welcome to Deno.Land!", 0xff3333), ); ``` --- # Ansi The ansi module exports an `ansi` object with chainable methods and properties for generating ansi escape sequence strings. The last property must be invoked as a method to generate the ansi string. ```typescript import { ansi } from "@cliffy/ansi"; console.log( ansi.cursorUp.cursorLeft.eraseDown(), ); ``` ## Arguments If the last method takes some arguments, you have to invoke the `.toString()` method to generate the ansi string. ```typescript import { ansi } from "@cliffy/ansi"; console.log( ansi.cursorUp(2).cursorLeft.eraseDown(2).toString(), ); ``` ## Uint8Array Convert to `Uint8Array`: ```typescript import { ansi } from "@cliffy/ansi"; await Deno.stdout.write( ansi.cursorUp.cursorLeft.eraseDown.bytes(), ); ``` ## Functional You can also directly import the ansi escape methods from the `ansi_escapes.ts` module. ```typescript import { cursorTo, eraseDown, image, link } from "@cliffy/ansi/ansi-escapes"; const response = await fetch( "https://raw.githubusercontent.com/c4spar/deno-cliffy/main/logo.png", ); const imageBuffer: ArrayBuffer = await response.arrayBuffer(); console.log( cursorTo(0, 0) + eraseDown() + image(imageBuffer, { width: 29, preserveAspectRatio: true, }) + "\n " + link("Deno", "https://deno.com") + "\n", ); ``` --- # Tty The tty module exports a `tty` object which works almost the same way as the `ansi` module. The only difference is, the `tty` module writes the ansi escape sequences directly to stdout. ```typescript import { tty } from "@cliffy/ansi/tty"; tty.cursorSave .cursorHide .cursorTo(0, 0) .eraseScreen(); ``` Create a new instance. ```typescript import { tty } from "@cliffy/ansi/tty"; const myTty = tty(); myTty.cursorSave .cursorHide .cursorTo(0, 0) .eraseScreen(); ``` Create a new instance with custom writer and reader. ### Deno ```typescript import { tty } from "@cliffy/ansi/tty"; const myTty = tty({ writer: Deno.stdout, reader: Deno.stdin, }); myTty.cursorSave .cursorHide .cursorTo(0, 0) .eraseScreen(); ``` --- # Colors The colors module is a simple and tiny chainable wrapper around [@std/fmt/colors](https://jsr.io/@std/fmt@1.0.3/doc/colors) module and works similarly to node's [chalk](https://github.com/chalk/chalk) module. ```typescript import { colors } from "@cliffy/ansi/colors"; console.log( colors.bold.underline.rgb24("Welcome to Deno.Land!", 0xff3333), ); ``` ```console $ deno run examples/ansi/colors.ts ``` ## Themes You can create your own re-usable themes just by storing your styles into a variable. ```typescript import { colors } from "@cliffy/ansi/colors"; // Define theme colors. const error = colors.bold.red; const warn = colors.bold.yellow; const info = colors.bold.blue; // Use theme colors. console.log(error("[ERROR]"), "Some error!"); console.log(warn("[WARN]"), "Some warning!"); console.log(info("[INFO]"), "Some information!"); // Override theme colors. console.log(error.underline("[ERROR]"), "Some error!"); console.log(warn.underline("[WARN]"), "Some warning!"); console.log(info.underline("[INFO]"), "Some information!"); ``` ```console $ deno run examples/ansi/color_themes.ts ``` --- # Testing Experimental testing utilities for command line applications. > [!WARNING] > The testing module currently only supports Deno. ## Installation ### Deno ```bash deno add jsr:@cliffy/testing ``` ### Pnpm ```bash pnpm add jsr:@cliffy/testing ``` or (using pnpm 10.8 or older): ```bash pnpm dlx jsr add @cliffy/testing ``` ### Yarn ```bash yarn add jsr:@cliffy/testing ``` or (using Yarn 4.8 or older): ```bash yarn dlx jsr add @cliffy/testing ``` ### Vlt ```bash vlt install jsr:@cliffy/testing ``` ### Npm ```bash npx jsr add @cliffy/testing ``` ### Bun ```bash bunx jsr add @cliffy/testing ``` --- # Snapshot testing The `snapshotTest` method can be used to test `stdin`, `stdout` and `stderr` of a single test case. It injects data to stdin and snapshots the `stdout` and `stderr` output of each test case separately. ## Usage The `snapshotTest` method behaves like a combination of `Deno.test()` and the `assertSnapshot` method from the deno std library. The `name`, `meta` and `fn` options are required. ### Basic usage This example snapshots the output of `console.log` and `console.error`. ```ts import { snapshotTest } from "@cliffy/testing"; await snapshotTest({ name: "should log to stdout and stderr", meta: import.meta, async fn() { console.log("foo"); console.error("bar"); }, }); ``` To update the snapshots, run `deno test -- --update`. This creates a snapshot file at `__snapshots__/[filename].snap` with the following content: ```js export const snapshot = {}; snapshot[`should log to stdout and stderr 1`] = ` "stdout: foo stderr: bar " `; ``` If you now run `deno test` (without `-- --update`), it will check if your test function still has the same output as the snapshot file content has. ### Script arguments Arguments defined with the `args` option are injected into the test method as script args. You can simply use `Deno.args` as you normally would to get the script arguments. ```ts import { snapshotTest } from "@cliffy/testing"; await snapshotTest({ name: "should log Deno.args", meta: import.meta, args: ["--foo", "bar"], async fn() { console.log(Deno.args); }, }); ``` You can use this to create snapshot tests for commands. ```ts import { snapshotTest } from "@cliffy/testing"; import { Command } from "@cliffy/command"; await snapshotTest({ name: "should execute the command with the --foo option", meta: import.meta, args: ["--foo", "bar"], async fn() { await new Command() .name("example") .description("Example command.") .option("-f, --foo ", "Example option.") .action(({ foo }) => { console.log("foo: %s", foo); }) .parse(); }, }); ``` ### Stdin The `snapshotTest` method can inject data to the test function with the `stdin` option. You can simply read the data from `Deno.stdin` as you normally would when reading data from stdin. ```ts import { snapshotTest } from "@cliffy/testing"; await snapshotTest({ name: "should read cliffy from stdin", meta: import.meta, stdin: ["cliffy"], async fn() { let name = ""; const decoder = new TextDecoder(); for await (const chunk of Deno.stdin.readable) { name += decoder.decode(chunk); if (name === "cliffy") { break; } } console.log("name:", name); }, }); ``` You can use this to create snapshot tests for prompts. The `ansi` module can be used to generate escape sequences to control the prompt. ```ts import { snapshotTest } from "@cliffy/testing"; import { Select } from "@cliffy/prompt"; import { ansi } from "@cliffy/ansi"; await snapshotTest({ name: "should select a color", meta: import.meta, stdin: ansi .cursorDown .cursorDown .text("\n") .toArray(), async fn() { const name = await Select.prompt({ message: "Select a color", options: ["red", "green", "blue"], }); console.log("name:", name); }, }); ``` ### Test steps You can also add multiple steps to the test function. The `snapshotTest` method then calls the test function once for each step within a separate test step by calling `t.step()` from the test context. Each step can have separate options for `stdin`, `args`, and `env`. ```ts import { snapshotTest } from "@cliffy/testing"; await snapshotTest({ name: "should log to stdout and stderr", meta: import.meta, steps: { "step 1": { args: ["foo"], stdin: ["bar"] }, "step 2": { args: ["beep"], stdin: ["boop"] }, }, async fn() { console.log(Deno.args); }, }); ``` You can use the `env` option to inject environment variables into each step: ```ts import { snapshotTest } from "@cliffy/testing"; await snapshotTest({ name: "should use env vars per step", meta: import.meta, steps: { "step 1": { env: { MY_VAR: "hello" } }, "step 2": { env: { MY_VAR: "world" } }, }, async fn() { console.log(Deno.env.get("MY_VAR")); }, }); ``` You can also run only specific steps by setting `only: true` on a step. When any step has `only: true`, all other steps are skipped and the test suite is marked as failed (same semantics as `Deno.test`'s `only` option). ```ts import { snapshotTest } from "@cliffy/testing"; await snapshotTest({ name: "run only specific steps", meta: import.meta, steps: { "step 1": { args: ["foo"] }, "step 2": { args: ["bar"], only: true }, // only this step runs }, async fn() { console.log(Deno.args); }, }); ``` --- # Options ## name The name of the test. ## meta The `meta` option is required and needs to be set to `import.meta`. This is required for executing the snapshot tests. ## fn Test function that executes your test code. A snapshot is taken of the `stdout` and `stderr` outputs of this function and stored in the snapshot file. ## args Script arguments injected into the test function. Read them with `Deno.args` as you normally would. Can be set at the top level or on individual steps. (see [Script arguments](https://cliffy.io/docs/v1.3.1/testing/snapshot/index.md#script-arguments)) ## stdin Data injected into `Deno.stdin`. Read it from `Deno.stdin` as you normally would when reading from stdin. Useful for snapshotting prompts. Can be set at the top level or on individual steps. (see [Stdin](https://cliffy.io/docs/v1.3.1/testing/snapshot/index.md#stdin)) ## steps With the `steps` option you can add multiple steps to the test function. The `snapshotTest` method then calls the test function once for each step within a separate test step by calling `t.step()` from the test context. Each step can have separate options for `stdin`, `args`, and `env`. (see [Test steps](https://cliffy.io/docs/v1.3.1/testing/snapshot/index.md#test-steps)) ## denoArgs Arguments passed to the `deno test` command when executing the snapshot tests. Use this to grant your CLI the permissions it needs at runtime (e.g. `--allow-read`, `--allow-env`). `--allow-env=SNAPSHOT_TEST_NAME` is always added on top of whatever you pass. Adding `--quiet` keeps Deno's own output out of the snapshot. ## dir Snapshot output directory. Snapshot files will be written to this directory. This can be relative to the test directory or an absolute path. If both `dir` and `path` are specified, the `dir` option will be ignored and the `path` option will be handled as normal. ## path Snapshot output path. The snapshot will be written to this file. This can be a path relative to the test directory or an absolute path. If both `dir` and `path` are specified, the `dir` option will be ignored and the `path` option will be handled as normal. ## osSuffix Operating system snapshot suffix. This is useful when your test produces different output on different operating systems. `osSuffix` is an array of `typeof Deno.build.os`. ## colors Enable/disable colors. Default is `true`. ## timeout Timeout in milliseconds to wait until the input stream data is buffered before writing the next data to the stream. This ensures that each user input is rendered as separate line in the snapshot file. If your test gets flaky, try to increase the timeout. The default timeout is `600`. ## env Environment variables to inject into the test process. Can be set at the top-level test options or on individual steps. (see [Test steps](https://cliffy.io/docs/v1.3.1/testing/snapshot/index.md#test-steps)) ## ignore If truthy the current test step will be ignored. It is a quick way to skip over a step, but also can be used for conditional logic, like determining if an environment feature is present. ## only If at least one test has `only` set to `true`, only run tests that have `only` set to `true` and fail the test suite. The `only` option can be set at the top level or on individual steps inside the `steps` object. When set on a step, only that step runs and the others are skipped (the test suite is still marked as failing due to the `only` filter). (see [Test steps](https://cliffy.io/docs/v1.3.1/testing/snapshot/index.md#test-steps)) --- # Examples ## Prompt snapshot You can use the `ansi` module to generate some control sequences to control a prompt in your test. ```ts import { snapshotTest } from "@cliffy/testing"; import { Checkbox } from "@cliffy/prompt/checkbox"; import { ansi } from "@cliffy/ansi"; await snapshotTest({ name: "should check an option", meta: import.meta, stdin: ansi .cursorDown .cursorDown .text(" ") .text("\n") .toArray(), async fn() { await Checkbox.prompt({ message: "Select an option", options: [ { name: "Foo", value: "foo" }, { name: "Bar", value: "bar" }, { name: "Baz", value: "baz" }, ], }); }, }); ``` ## Command snapshot A simple example with two steps and different arguments to snapshot the output of a command. ```ts import { snapshotTest } from "@cliffy/testing"; import { Command } from "@cliffy/command"; await snapshotTest({ name: "command", meta: import.meta, ignore: Deno.build.os === "windows", colors: true, steps: { "should delete a file": { args: ["/foo/bar"], }, "should delete a directory recursively": { args: ["--recursive", "/foo/bar"], }, }, async fn() { await new Command() .version("1.0.0") .name("rm") .option("-r, --recursive", "Delete recursive.") .arguments("") .action(({ recursive }, path) => { if (recursive) { console.log("Delete recursive: %s", path); } else { console.log("Delete: %s", path); } }) .parse(); }, }); ``` ## Smoke test A single snapshot can guard your entire help and usage surface against accidental changes. Each step runs `--help` on a different command, so one test covers the root command and every subcommand at once. ```ts import { snapshotTest } from "@cliffy/testing"; import { Command } from "@cliffy/command"; const cli = new Command() .name("shop") .version("1.0.0") .description("Example shop CLI.") .globalOption("-v, --verbose", "Enable verbose output.") .command( "search", new Command() .description("Search for products.") .arguments("") .option("-l, --limit ", "Limit the number of results."), ) .command( "product", new Command() .description("Show a single product.") .arguments(""), ); await snapshotTest({ name: "smoke", meta: import.meta, colors: false, steps: { "--help": { args: ["--help"] }, "search --help": { args: ["search", "--help"] }, "product --help": { args: ["product", "--help"] }, }, async fn() { await cli.parse(Deno.args); }, }); ``` In a real project you would import the command from your entry file instead of defining it inline, and use `denoArgs` to grant your CLI the permissions it needs at runtime. ```ts import { snapshotTest } from "@cliffy/testing"; import type { Command } from "@cliffy/command"; // import { cli } from "./main.ts"; declare const cli: Command; await snapshotTest({ name: "smoke", meta: import.meta, denoArgs: ["--quiet", "--allow-env", "--allow-read"], steps: { "--help": { args: ["--help"] }, "search --help": { args: ["search", "--help"] }, "product --help": { args: ["product", "--help"] }, }, async fn() { await cli.parse(Deno.args); }, }); ```