{"id":13393953,"url":"https://github.com/vadimdemedes/pastel","last_synced_at":"2025-04-09T01:22:31.484Z","repository":{"id":40005143,"uuid":"182489651","full_name":"vadimdemedes/pastel","owner":"vadimdemedes","description":"🎨 Next.js-like framework for CLIs made with Ink","archived":false,"fork":false,"pushed_at":"2024-08-10T20:57:34.000Z","size":145,"stargazers_count":2235,"open_issues_count":17,"forks_count":39,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-04-05T16:38:10.347Z","etag":null,"topics":["cli","command-line","ink","react"],"latest_commit_sha":null,"homepage":"https://term.ink/pastel","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vadimdemedes.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"license","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"open_collective":"vadimdemedes","custom":"https://www.comebackalive.in.ua"}},"created_at":"2019-04-21T04:30:30.000Z","updated_at":"2025-04-05T05:17:01.000Z","dependencies_parsed_at":"2022-06-26T06:01:44.456Z","dependency_job_id":"c2ded16b-d094-4170-b792-9224a4a90c59","html_url":"https://github.com/vadimdemedes/pastel","commit_stats":{"total_commits":43,"total_committers":12,"mean_commits":"3.5833333333333335","dds":0.4883720930232558,"last_synced_commit":"57392dee3b0a1d73d20de38915b66914bc7dadbb"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vadimdemedes%2Fpastel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vadimdemedes%2Fpastel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vadimdemedes%2Fpastel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vadimdemedes%2Fpastel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vadimdemedes","download_url":"https://codeload.github.com/vadimdemedes/pastel/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247369913,"owners_count":20927927,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","command-line","ink","react"],"created_at":"2024-07-30T17:01:03.586Z","updated_at":"2025-04-09T01:22:31.441Z","avatar_url":"https://github.com/vadimdemedes.png","language":"TypeScript","funding_links":["https://opencollective.com/vadimdemedes","https://www.comebackalive.in.ua"],"categories":["JavaScript","TypeScript","cli","\u003e 1K ⭐️"],"sub_categories":[],"readme":"[![](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)\n\n# Pastel [![test](https://github.com/vadimdemedes/pastel/actions/workflows/test.yml/badge.svg)](https://github.com/vadimdemedes/pastel/actions/workflows/test.yml)\n\n\u003e Next.js-like framework for CLIs made with [Ink](https://github.com/vadimdemedes/ink).\n\n## Features\n\n- Create files in `commands` folder to add commands.\n- Create folders in `commands` to add subcommands.\n- Define options and arguments via [Zod](https://zod.dev).\n- Full type-safety of options and arguments thanks to Zod.\n- Auto-generated help message for commands, options and arguments.\n- Uses battle-tested [Commander](https://github.com/tj/commander.js) package under the hood.\n\n## Install\n\n```console\nnpm install pastel ink react zod\n```\n\n## Getting started\n\nUse [create-pastel-app](https://github.com/vadimdemedes/create-pastel-app) to quickly scaffold a Pastel app with TypeScript, linter and tests set up.\n\n```console\nnpm create pastel-app hello-world\nhello-world\n```\n\n\u003cdetails\u003e\u003csummary\u003eManual setup\u003c/summary\u003e\n\u003cp\u003e\n\n1. Set up a new project.\n\n```console\nmkdir hello-world\ncd hello-world\nnpm init --yes\n```\n\n2. Install Pastel and TypeScript.\n\n```console\nnpm install pastel\nnpm install --save-dev typescript @sindresorhus/tsconfig\n```\n\n3. Create a `tsconfig.json` file to set up TypeScript.\n\n```json\n{\n\t\"extends\": \"@sindresorhus/tsconfig\",\n\t\"compilerOptions\": {\n\t\t\"moduleResolution\": \"node16\",\n\t\t\"module\": \"node16\",\n\t\t\"outDir\": \"build\",\n\t\t\"sourceMap\": true,\n\t\t\"tsx\": \"react\"\n\t},\n\t\"include\": [\"source\"]\n}\n```\n\n4. Create a `source` folder for the source code.\n\n```console\nmkdir source\n```\n\n5. Create a `source/cli.ts` file with the following code, which will be CLI's entrypoint:\n\n```js\n#!/usr/bin/env node\nimport Pastel from 'pastel';\n\nconst app = new Pastel({\n\timportMeta: import.meta,\n});\n\nawait app.run();\n```\n\n6. Create `source/commands` folder for defining CLI's commands.\n\n```console\nmkdir source/commands\n```\n\n7. Create an `source/commands/index.tsx` file for a default command, with the following code:\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\n\nexport const options = zod.object({\n\tname: zod.string().describe('Your name'),\n});\n\ntype Props = {\n\toptions: zod.infer\u003ctypeof options\u003e;\n};\n\nexport default function Index({options}: Props) {\n\treturn \u003cText\u003eHello, {options.name}!\u003c/Text\u003e;\n}\n```\n\n8. Build your CLI.\n\n```console\nnpx tsc\n```\n\n9. Set up an executable file.\n\n9.1. Add `bin` field to `package.json`, which points to the compiled version of `source/cli.ts` file.\n\n```diff\n\t\"bin\": \"build/cli.js\"\n```\n\n9.2. Make your CLI available globally.\n\n```console\nnpm link --global\n```\n\n10. Run your CLI.\n\n```console\nhello-world --name=Jane\n```\n\n```\nHello, Jane!\n```\n\n```console\nhello-world --help\n```\n\n```\nUsage: hello-world [options]\n\nOptions:\n  --name         Your name\n  -v, --version  Show version number\n  -h, --help     Show help\n\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n## Table of contents\n\n- [Commands](#commands)\n  - [Index commands](#index-commands)\n  - [Default commands](#default-commands)\n  - [Subcommands](#subcommands)\n  - [Aliases](#aliases)\n- [Options](#options)\n  - [Types](#types)\n    - [String](#string)\n    - [Number](#number)\n    - [Boolean](#boolean)\n    - [Enum](#enum)\n    - [Array](#array)\n    - [Set](#set)\n  - [Optional or required options](#optional-or-required-options)\n  - [Default value](#default-value)\n  - [Alias](#alias)\n- [Arguments](#arguments)\n  - [Types](#types-1)\n    - [String](#string-1)\n    - [Number](#number-1)\n    - [Enum](#enum-1)\n- [Custom app](#custom-app)\n- [Custom program name](#custom-program-name)\n- [Custom description](#custom-description)\n- [Custom version](#custom-version)\n- [API](#api)\n\n## Commands\n\nPastel treats every file in the `commands` folder as a command, where filename is a command's name (excluding the extension). Files are expected to export a React component, which will be rendered when command is executed.\n\nYou can also nest files in folders to create subcommands.\n\nHere's an example, which defines `login` and `logout` commands:\n\n```\ncommands/\n\tlogin.tsx\n\tlogout.tsx\n```\n\n**login.tsx**\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\n\nexport default function Login() {\n\treturn \u003cText\u003eLogging in\u003c/Text\u003e;\n}\n```\n\n**logout.tsx**\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\n\nexport default function Logout() {\n\treturn \u003cText\u003eLogging out\u003c/Text\u003e;\n}\n```\n\nGiven that your executable is named `my-cli`, you can execute these commands like so:\n\n```\n$ my-cli login\n$ my-cli logout\n```\n\n### Index commands\n\nFiles named `index.tsx` are index commands. They will be executed by default, when no other command isn't specified.\n\n```\ncommands/\n\tindex.tsx\n\tlogin.tsx\n\tlogout.tsx\n```\n\nRunning `my-cli` without a command name will execute `index.tsx` command.\n\n```\n$ my-cli\n```\n\nIndex command is useful when you're building a single-purpose CLI, which has only one command. For example, [`np`](https://github.com/sindresorhus/np) or [fast-cli](https://github.com/sindresorhus/fast-cli).\n\n### Default commands\n\nDefault commands are similar to index commands, because they too will be executed when an explicit command isn't specified. The difference is default commands still have a name, just like any other command, and they'll show up in the help message.\n\nDefault commands are useful for creating shortcuts to commands that are used most often.\n\nLet's say there are 3 commands available: `deploy`, `login` and `logout`.\n\n```\ncommands/\n\tdeploy.tsx\n\tlogin.tsx\n\tlogout.tsx\n```\n\nEach of them can be executed by typing their name.\n\n```\n$ my-cli deploy\n$ my-cli login\n$ my-cli logout\n```\n\nChances are, `deploy` command is going to be used a lot more frequently than `login` and `logout`, so it makes sense to make `deploy` a default command in this CLI.\n\nExport a variable named `isDefault` from the command file and set it to `true` to mark that command as a default one.\n\n```diff\nimport React from 'react';\nimport {Text} from 'ink';\n\n+ export const isDefault = true;\n\nexport default function Deploy() {\n\treturn \u003cText\u003eDeploying...\u003c/Text\u003e;\n}\n```\n\nNow, running `my-cli` or `my-cli deploy` will execute a `deploy` command.\n\n```\n$ my-cli\n```\n\n[Vercel's CLI](https://github.com/vercel/vercel/tree/main/packages/cli) is a real-world example of this approach, where both `vercel` and `vercel deploy` trigger a new deploy of your project.\n\n### Subcommands\n\nAs your CLI grows and more commands are added, it makes sense to group the related commands together.\n\nTo do that, create nested folders in `commands` folder and put the relevant commands inside to create subcommands. Here's an example for a CLI that triggers deploys and manages domains for your project:\n\n```\ncommands/\n\tdeploy.tsx\n\tlogin.tsx\n\tlogout.tsx\n\tdomains/\n\t\tlist.tsx\n\t\tadd.tsx\n\t\tremove.tsx\n```\n\nCommands for managing domains would be executed like so:\n\n```\n$ my-cli domains list\n$ my-cli domains add\n$ my-cli domains remove\n```\n\nSubcommands can even be deeply nested within many folders.\n\n### Aliases\n\nCommands can have an alias, which is usually a shorter alternative name for the same command. Power users prefer aliases instead of full names for commands they use often. For example, most users type `npm i` instead of `npm install`.\n\nAny command in Pastel can assign an alias by exporting a variable named `alias`:\n\n```diff\nimport React from 'react';\nimport {Text} from 'ink';\n\n+ export const alias = 'i';\n\nexport default function Install() {\n\treturn \u003cText\u003eInstalling something...\u003c/Text\u003e;\n}\n```\n\nNow the same `install` command can be executed by only typing `i`:\n\n```\n$ my-cli i\n```\n\n## Options\n\nCommands can define options to customize their default behavior or ask for some additional data to run properly.\nFor example, a command that creates a new server might specify options for choosing a server's name, an operating system, memory size or a region where that server should be spin up.\n\nPastel uses [Zod](https://zod.dev) to define, parse and validate command options. Export a variable named `options` and set a Zod [object schema](https://zod.dev/?id=objects). Pastel will parse that schema and automatically set these options up. When command is executed, option values are passed via `options` prop to your component.\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\n\nexport const options = zod.object({\n\tname: zod.string().describe('Server name'),\n\tos: zod.enum(['Ubuntu', 'Debian']).describe('Operating system'),\n\tmemory: zod.number().describe('Memory size'),\n\tregion: zod.enum(['waw', 'lhr', 'nyc']).describe('Region'),\n});\n\ntype Props = {\n\toptions: zod.infer\u003ctypeof options\u003e;\n};\n\nexport default function Deploy({options}: Props) {\n\treturn (\n\t\t\u003cText\u003e\n\t\t\tDeploying a server named \"{options.name}\" running {options.os} with memory\n\t\t\tsize of {options.memory} MB in {options.region} region\n\t\t\u003c/Text\u003e\n\t);\n}\n```\n\nWith options set up, here's an example `deploy` command:\n\n```\n$ my-cli deploy --name=Test --os=Ubuntu --memory=1024 --region=waw\nDeploying a server named \"Test\" running Ubuntu with memory size of 1024 MB in waw region.\n```\n\nHelp message is auto-generated for you as well.\n\n```\n$ my-cli deploy --help\nUsage: my-cli deploy [options]\n\nOptions:\n  --name         Server name\n  --os           Operating system (choices: \"Ubuntu\", \"Debian\")\n  --memory       Memory size\n  --region       Region\n  -v, --version  Show version number\n  -h, --help     Show help\n```\n\n### Types\n\nPastel only supports [string](https://zod.dev/?id=strings), [number](https://zod.dev/?id=numbers), [boolean](https://zod.dev/?id=booleans), [enum](https://zod.dev/?id=zod-enums), [array](https://zod.dev/?id=arrays) and [set](https://zod.dev/?id=sets) types for defining options.\n\n#### String\n\nExample that defines a `--name` string option:\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\n\nexport const options = zod.object({\n\tname: zod.string().describe('Your name'),\n});\n\ntype Props = {\n\toptions: zod.infer\u003ctypeof options\u003e;\n};\n\nexport default function Example({options}: Props) {\n\treturn \u003cText\u003eName = {options.name}\u003c/Text\u003e;\n}\n```\n\n```\n$ my-cli --name=Jane\nName = Jane\n```\n\n#### Number\n\nExample that defines a `--size` number option:\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\n\nexport const options = zod.object({\n\tage: zod.number().describe('Your age'),\n});\n\ntype Props = {\n\toptions: zod.infer\u003ctypeof options\u003e;\n};\n\nexport default function Example({options}: Props) {\n\treturn \u003cText\u003eAge = {options.age}\u003c/Text\u003e;\n}\n```\n\n```\n$ my-cli --age=28\nAge = 28\n```\n\n#### Boolean\n\nExample that defines a `--compress` number option:\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\n\nexport const options = zod.object({\n\tcompress: zod.boolean().describe('Compress output'),\n});\n\ntype Props = {\n\toptions: zod.infer\u003ctypeof options\u003e;\n};\n\nexport default function Example({options}: Props) {\n\treturn \u003cText\u003eCompress = {String(options.compress)}\u003c/Text\u003e;\n}\n```\n\n```\n$ my-cli --compress\nCompress = true\n```\n\nBoolean options are special, because they can't be required and default to `false`, even if Zod schema doesn't use a `default(false)` function.\n\nWhen boolean option defaults to `true`, it's treated as a negated option, which adds a `no-` prefix to its name.\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\n\nexport const options = zod.object({\n\tcompress: zod.boolean().default(true).describe(\"Don't compress output\"),\n});\n\ntype Props = {\n\toptions: zod.infer\u003ctypeof options\u003e;\n};\n\nexport default function Example({options}: Props) {\n\treturn \u003cText\u003eCompress = {String(options.compress)}\u003c/Text\u003e;\n}\n```\n\n```\n$ my-cli --no-compress\nCompress = false\n```\n\n#### Enum\n\nExample that defines an `--os` enum option with a set of allowed values.\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\n\nexport const options = zod.object({\n\tos: zod.enum(['Ubuntu', 'Debian']).describe('Operating system'),\n});\n\ntype Props = {\n\toptions: zod.infer\u003ctypeof options\u003e;\n};\n\nexport default function Example({options}: Props) {\n\treturn \u003cText\u003eOperating system = {options.os}\u003c/Text\u003e;\n}\n```\n\n```\n$ my-cli --os=Ubuntu\nOperating system = Ubuntu\n\n$ my-cli --os=Debian\nOperating system = Debian\n\n$ my-cli --os=Windows\nerror: option '--os \u003cos\u003e' argument 'Windows' is invalid. Allowed choices are Ubuntu, Debian.\n```\n\n#### Array\n\nExample that defines a `--tag` array option, which can be specified multiple times.\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\n\nexport const options = zod.object({\n\ttag: zod.array(zod.string()).describe('Tags'),\n});\n\ntype Props = {\n\toptions: zod.infer\u003ctypeof options\u003e;\n};\n\nexport default function Example({options}: Props) {\n\treturn \u003cText\u003eTags = {options.tags.join(', ')}\u003c/Text\u003e;\n}\n```\n\n```\n$ my-cli --tag=App --tag=Production\nTags = App, Production\n```\n\nArray options can only include strings (`zod.string`), numbers (`zod.number`) or enums (`zod.enum`).\n\n#### Set\n\nExample that defines a `--tag` set option, which can be specified multiple times. It's similar to an array option, except duplicate values are removed, since the option's value is a [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) instance.\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\n\nexport const options = zod.object({\n\ttag: zod.set(zod.string()).describe('Tags'),\n});\n\ntype Props = {\n\toptions: zod.infer\u003ctypeof options\u003e;\n};\n\nexport default function Example({options}: Props) {\n\treturn \u003cText\u003eTags = {[...options.tags].join(', ')}\u003c/Text\u003e;\n}\n```\n\n```\n$ my-cli --tag=App --tag=Production --tag=Production\nTags = App, Production\n```\n\nSet options can only include strings (`zod.string`), numbers (`zod.number`) or enums (`zod.enum`).\n\n### Optional or required options\n\nPastel determines whether option is optional or required by parsing its Zod schema. Since Zod schemas are required by default, so are options in Pastel.\n\nIf an option isn't be required for a command to function properly, mark it as optional.\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\n\nexport const options = zod.object({\n\tos: zod.enum(['Ubuntu', 'Debian']).optional().describe('Operating system'),\n});\n\ntype Props = {\n\toptions: zod.infer\u003ctypeof options\u003e;\n};\n\nexport default function Example({options}: Props) {\n\treturn \u003cText\u003eOperating system = {options.os ?? 'unspecified'}\u003c/Text\u003e;\n}\n```\n\n```\n$ my-cli --os=Ubuntu\nOperating system = Ubuntu\n\n$ my-cli\nOperating system = unspecified\n```\n\n### Default value\n\nDefault value for an option can be set via a `default` function in Zod schema.\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\n\nexport const options = zod.object({\n\tsize: zod.number().default(1024).describe('Memory size'),\n});\n\ntype Props = {\n\toptions: zod.infer\u003ctypeof options\u003e;\n};\n\nexport default function Example({options}: Props) {\n\treturn \u003cText\u003eMemory = {options.size} MB\u003c/Text\u003e;\n}\n```\n\n```\n$ my-cli\nMemory size = 1024 MB\n```\n\nJSON representation of default value will be displayed in the help message.\n\n```\n$ my-cli --help\nUsage: my-cli [options]\n\nOptions:\n  --size  Memory size (default: 1024)\n```\n\nYou can also customize it via `defaultValueDescription` option in `option` helper function.\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\nimport {option} from 'pastel';\n\nexport const options = zod.object({\n\tsize: zod\n\t\t.number()\n\t\t.default(1024)\n\t\t.describe(\n\t\t\toption({\n\t\t\t\tdescription: 'Memory size',\n\t\t\t\tdefaultValueDescription: '1 GB',\n\t\t\t}),\n\t\t),\n});\n\ntype Props = {\n\toptions: zod.infer\u003ctypeof options\u003e;\n};\n\nexport default function Example({options}: Props) {\n\treturn \u003cText\u003eMemory = {options.size} MB\u003c/Text\u003e;\n}\n```\n\n```\n$ my-cli --help\nUsage: my-cli [options]\n\nOptions:\n  --size  Memory size (default: 1 GB)\n```\n\n### Alias\n\nOptions can specify an alias, which is usually the first letter of an original option name.\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\nimport {option} from 'pastel';\n\nexport const options = zod.object({\n\tforce: zod.boolean().describe(\n\t\toption({\n\t\t\tdescription: 'Force',\n\t\t\talias: 'f',\n\t\t}),\n\t),\n});\n\ntype Props = {\n\toptions: zod.infer\u003ctypeof options\u003e;\n};\n\nexport default function Example({options}: Props) {\n\treturn \u003cText\u003eForce = {String(options.force)}\u003c/Text\u003e;\n}\n```\n\n```\n$ my-cli --force\nForce = true\n\n$ my-cli -f\nForce = true\n```\n\n## Arguments\n\nArguments are similar to options, except they don't require a flag to specify them (e.g. `--name`) and they're always specified after command name and options. For example, [`mv`](https://linux.die.net/man/1/mv) requires 2 arguments, where first argument is a source path and second argument is a target path.\n\n```\n$ mv source.txt target.txt\n```\n\nA theoretical `mv` command in Pastel can define similar arguments like so:\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\n\nexport const args = zod.tuple([zod.string(), zod.string()]);\n\ntype Props = {\n\targs: zod.infer\u003ctypeof args\u003e;\n};\n\nexport default function Move({args}: Props) {\n\treturn (\n\t\t\u003cText\u003e\n\t\t\tMoving from {args[0]} to {args[1]}\n\t\t\u003c/Text\u003e\n\t);\n}\n```\n\n```\n$ my-cli source.txt target.txt\nMoving from source.txt to target.txt\n```\n\nThis command defines two positional arguments, which means that argument's position matters for command's execution. This is why positional arguments are defined via [zod.tuple](https://zod.dev/?id=tuples) in Zod, where a specific number of values is expected.\n\nHowever, there are commands like [`rm`](https://linux.die.net/man/1/rm), which can accept any number of arguments. To accomplish that in Pastel, use [`zod.array`](https://zod.dev/?id=arrays) instead.\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\n\nexport const args = zod.array(zod.string());\n\ntype Props = {\n\targs: zod.infer\u003ctypeof args\u003e;\n};\n\nexport default function Remove({args}: Props) {\n\treturn \u003cText\u003eRemoving {args.join(', ')}\u003c/Text\u003e;\n}\n```\n\n```\n$ my-cli a.txt b.txt c.txt\nRemoving a.txt, b.txt, c.txt\n```\n\n### Types\n\nPastel only supports [string](https://zod.dev/?id=strings), [number](https://zod.dev/?id=numbers) and [enum](https://zod.dev/?id=zod-enums) types for defining arguments inside [tuple](https://zod.dev/?id=tuples) or [array](https://zod.dev/?id=arrays).\n\n#### String\n\nExample that defines a string argument.\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\nimport {argument} from 'pastel';\n\nexport const args = zod.tuple([\n\tzod.string().describe(\n\t\targument({\n\t\t\tname: 'name',\n\t\t\tdescription: 'Your name',\n\t\t}),\n\t),\n]);\n\ntype Props = {\n\targs: zod.infer\u003ctypeof args\u003e;\n};\n\nexport default function Hello({args}: Props) {\n\treturn \u003cText\u003eHello, {args[0]}\u003c/Text\u003e;\n}\n```\n\n```\n$ my-cli Jane\nHello, Jane\n```\n\n#### Number\n\nExample that defines a number argument.\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\nimport {argument} from 'pastel';\n\nexport const args = zod.tuple([\n\tzod.number().describe(\n\t\targument({\n\t\t\tname: 'age',\n\t\t\tdescription: 'Age',\n\t\t}),\n\t),\n]);\n\ntype Props = {\n\targs: zod.infer\u003ctypeof args\u003e;\n};\n\nexport default function Hello({args}: Props) {\n\treturn \u003cText\u003eYour age is {args[0]}\u003c/Text\u003e;\n}\n```\n\n```\n$ my-cli 28\nYour age is 28\n```\n\n#### Enum\n\nExample that defines an enum argument.\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\nimport {argument} from 'pastel';\n\nexport const args = zod.tuple([\n\tzod.enum(['Ubuntu', 'Debian']).describe(\n\t\targument({\n\t\t\tname: 'os',\n\t\t\tdescription: 'Operating system',\n\t\t}),\n\t),\n]);\n\ntype Props = {\n\targs: zod.infer\u003ctypeof args\u003e;\n};\n\nexport default function Example({args}: Props) {\n\treturn \u003cText\u003eOperating system = {args[0]}\u003c/Text\u003e;\n}\n```\n\n```\n$ my-cli Ubuntu\nOperating system = Ubuntu\n```\n\n### Default value\n\nDefault value for an argument can be via a `default` function in Zod schema.\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\nimport {argument} from 'pastel';\n\nexport const args = zod.tuple([\n\tzod\n\t\t.number()\n\t\t.default(1024)\n\t\t.describe(\n\t\t\targument({\n\t\t\t\tname: 'number',\n\t\t\t\tdescription: 'Some number',\n\t\t\t}),\n\t\t),\n]);\n\ntype Props = {\n\targs: zod.infer\u003ctypeof args\u003e;\n};\n\nexport default function Example({args}: Props) {\n\treturn \u003cText\u003eSome number = {args[0]}\u003c/Text\u003e;\n}\n```\n\n```\n$ my-cli\nSome number = 1024\n```\n\nJSON representation of default value will be displayed in the help message.\n\n```\n$ my-cli --help\nUsage: my-cli [options] [number]\n\nArguments:\n  number  Some number (default: 1024)\n```\n\nYou can also customize it via `defaultValueDescription` option in `option` helper function.\n\n```tsx\nimport React from 'react';\nimport {Text} from 'ink';\nimport zod from 'zod';\nimport {argument} from 'pastel';\n\nexport const args = zod.tuple([\n\tzod\n\t\t.number()\n\t\t.default(1024)\n\t\t.describe(\n\t\t\targument({\n\t\t\t\tname: 'number',\n\t\t\t\tdescription: 'Some number',\n\t\t\t\tdefaultValueDescription: '1,204',\n\t\t\t}),\n\t\t),\n]);\n\ntype Props = {\n\targs: zod.infer\u003ctypeof args\u003e;\n};\n\nexport default function Example({args}: Props) {\n\treturn \u003cText\u003eSome number = {args[0]}\u003c/Text\u003e;\n}\n```\n\n```\n$ my-cli --help\nUsage: my-cli [options] [number]\n\nArguments:\n  number  Some number (default: 1,024)\n```\n\n## Custom app\n\nSimilar to Next.js, Pastel wraps every command component with a component exported from `commands/_app.tsx`. If this file doesn't exist, Pastel uses a default app component, which does nothing but render your component with `options` and `args` props.\n\n```tsx\nimport React from 'react';\nimport type {AppProps} from 'pastel';\n\nexport default function App({Component, commandProps}: AppProps) {\n\treturn \u003cComponent {...commandProps} /\u003e;\n}\n```\n\nYou can copy paste that code into `commands/_app.tsx` and add some logic that will be shared across all commands.\n\n## Custom program name\n\nPastel extracts a program name from the `name` field in the nearest `package.json` file. If it doesn't exist, a first argument in `process.argv` is used.\n\nWhen the name of an executable doesn't match the `name` in `package.json`, it can be customized via a `name` option during app initialization.\n\n```tsx\nimport Pastel from 'pastel';\n\nconst app = new Pastel({\n\tname: 'custom-cli-name',\n});\n\nawait app.run();\n```\n\n## Custom description\n\nSimilar to program name, Pastel looks for a description in `description` field in the nearest `package.json` file. To customize it, use a `description` option when initializating Pastel.\n\n```tsx\nimport Pastel from 'pastel';\n\nconst app = new Pastel({\n\tdescription: 'Custom description',\n});\n\nawait app.run();\n```\n\n## Custom version\n\nSimilar to program name and description, Pastel looks for a version in `version` field in the nearest `package.json` file. If Pastel can't find it, version will be hidden in the help message and `-v, --version` options won't be available.\n\nTo customize it, use a `version` option during app initialization.\n\n```tsx\nimport Pastel from 'pastel';\n\nconst app = new Pastel({\n\tversion: '1.0.0\n});\n\nawait app.run()\n```\n\n## API\n\n### Pastel(options)\n\nInitializes a Pastel app.\n\n#### options\n\nType: `object`\n\n##### name\n\nType: `string`\n\nProgram name. Defaults to `name` in the nearest package.json or the name of the executable.\n\n##### version\n\nType: `string`\n\nVersion. Defaults to `version` in the nearest package.json.\n\n##### description\n\nType: `string`\n\nDescription. Defaults to `description` in the nearest package.json.\n\n##### importMeta\n\nType: [`ImportMeta`](https://nodejs.org/dist/latest/docs/api/esm.html#esm_import_meta)\n\nPass in [`import.meta`](https://nodejs.org/dist/latest/docs/api/esm.html#esm_import_meta). This is used to find the `commands` directory.\n\n#### run(argv)\n\nParses the arguments and runs the app.\n\n##### argv\n\nType: `Array`\\\nDefault: `process.argv`\n\nProgram arguments.\n\n### option(config)\n\nSet additional metadata for an option. Must be used as an argument to `describe` function from Zod.\n\n#### config\n\nType: `object`\n\n##### description\n\nType: `string`\n\nDescription. If description is missing, option won't appear in the \"Options\" section of the help message.\n\n##### defaultValueDescription\n\nType: `string`\n\nDescription of a default value.\n\n##### valueDescription\n\nType: `string`\n\nDescription of a value. Replaces \"value\" in `--flag \u003cvalue\u003e` in the help message.\n\n##### alias\n\nType: `string`\n\nAlias. Usually a first letter of the full option name.\n\n### argument(config)\n\nSet additional metadata for an argument. Must be used as an argument to `describe` function from Zod.\n\n#### config\n\nType: `object`\n\n##### name\n\nType: `string`\\\nDefault: `'arg'`\n\nArgument's name. Displayed in \"Usage\" part of the help message. Doesn't affect how argument is parsed.\n\n##### description\n\nType: `string`\n\nDescription of an argument. If description is missing, argument won't appear in the \"Arguments\" section of the help message.\n\n##### defaultValueDescription\n\nType: `string`\n\nDescription of a default value.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvadimdemedes%2Fpastel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvadimdemedes%2Fpastel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvadimdemedes%2Fpastel/lists"}