[
  {
    "path": ".changeset/README.md",
    "content": "# Changesets\n\nHello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works\nwith multi-package repos, or single-package repos to help you version and publish your code. You can\nfind the full documentation for it [in our repository](https://github.com/changesets/changesets)\n\nWe have a quick list of common questions to get you started engaging with this project in\n[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)\n"
  },
  {
    "path": ".changeset/config.json",
    "content": "{\n\t\"$schema\": \"https://unpkg.com/@changesets/config@2.0.0/schema.json\",\n\t\"changelog\": \"@changesets/cli/changelog\",\n\t\"commit\": false,\n\t\"fixed\": [],\n\t\"linked\": [],\n\t\"access\": \"public\",\n\t\"baseBranch\": \"main\",\n\t\"updateInternalDependencies\": \"patch\",\n\t\"ignore\": []\n}\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: \"build\"\n\non:\n  push:\n    branches:\n      - \"main\"\n  pull_request:\n    branches:\n      - \"main\"\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: [\"18.x\", \"20.x\", \"22.x\", \"24.x\"]\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node-version }}\n      - uses: pnpm/action-setup@v4\n      - name: Cache pnpm modules\n        uses: actions/cache@v4\n        with:\n          path: ~/.pnpm-store\n          key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}\n          restore-keys: |\n            ${{ runner.os }}-\n      - run: pnpm install\n      - run: pnpm run build\n      - run: pnpm run test\n      - run: pnpm run lint\n      - run: pnpm dlx pkg-pr-new publish\n        if: matrix.node-version == '22.x'\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "on:\n  push:\n    branches:\n      - main\n\nname: \"release\"\nconcurrency: ${{ github.workflow }}-${{ github.ref }}\n\njobs:\n  release:\n    permissions:\n      id-token: write # Required for OIDC\n      contents: write\n      pull-requests: write\n    name: Release\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 24.x\n      - uses: pnpm/action-setup@v4\n      - name: Cache pnpm modules\n        uses: actions/cache@v4\n        with:\n          path: ~/.pnpm-store\n          key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}\n          restore-keys: |\n            ${{ runner.os }}-\n      - run: pnpm install\n      - name: Create Release Pull Request\n        uses: changesets/action@v1\n        with:\n          version: \"pnpm changeset:version\"\n          publish: \"pnpm changeset:publish\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "*.log\n.DS_Store\nnode_modules\ndist\n/coverage\n/public\n\n# book\n/book\n"
  },
  {
    "path": ".kodiak.toml",
    "content": "version = 1\n"
  },
  {
    "path": ".node-version",
    "content": "v24.13.0\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# cmd-ts\n\n## 0.15.0\n\n### Minor Changes\n\n- 0366d4f: Add pluggable help formatters with `HelpFormatter` interface and `setDefaultHelpFormatter()` API. This allows customizing how CLI help is rendered. Also adds:\n  - `examples` option to commands and subcommands for documenting usage examples\n  - `cmd-ts/batteries/vercelFormatter` - a Vercel-style help formatter with column-aligned output\n\n## 0.14.4\n\n### Patch Changes\n\n- bcd5d02: better multiline error formatting\n\n## 0.14.3\n\n### Patch Changes\n\n- e0afa2f: handle circuit breaker (--help and --version) before parsing arguments\n\n## 0.14.2\n\n### Patch Changes\n\n- 87565b2: Added onMissing callback support to flags, options, and custom types\n\n  That allows providing dynamic fallback values when command-line arguments are not provided, This enables:\n\n  - Hiding default values from help output\n  - Interactive prompts: Ask users for input when flags/options are missing\n  - Environment-based defaults: Check environment variables or config files dynamically\n  - Auto-discovery: Automatically find files or resources when not specified\n  - Async support: Handle both synchronous and asynchronous fallback logic\n\n  The onMissing callback is used as a fallback when defaultValue is not provided, following the precedence order: environment variables → defaultValue → onMissing → type defaults.\n\n  New APIs:\n\n  - flag({ onMissing: () => boolean | Promise<boolean> })\n  - option({ onMissing: () => T | Promise<T> })\n  - multioption({ onMissing: () => T[] | Promise<T[]> })\n  - Custom Type interface now supports onMissing property\n\n## 0.14.1\n\n### Patch Changes\n\n- 46bf4a7: fix: properly reconstruct original argument strings in rest combinator\n\n## 0.14.0\n\n### Minor Changes\n\n- a1afb05: --help exits with statuscode 0\n\n## 0.13.0\n\n### Minor Changes\n\n- dfeafc8: add `defaultValue` configuration @ `multioption`\n\n## 0.12.1\n\n### Patch Changes\n\n- 5867a13: Allow dangling forcePositionals\n\n## 0.12.0\n\n### Minor Changes\n\n- 2f651de: Display help when calling subcommands without any arguments\n\n### Patch Changes\n\n- e05e433: Allow readonly T[] in oneOf\n\n## 0.11.0\n\n### Minor Changes\n\n- 6cb8d08: upgrade all deps\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Gal Schlezinger\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# `cmd-ts`\n\n> 💻 A type-driven command line argument parser, with awesome error reporting 🤤\n\nNot all command line arguments are strings, but for some reason, our CLI parsers force us to use strings everywhere. 🤔 `cmd-ts` is a fully-fledged command line argument parser, influenced by Rust's [`clap`](https://github.com/clap-rs/clap) and [`structopt`](https://github.com/TeXitoi/structopt):\n\n🤩 Awesome autocomplete, awesome safeness\n\n🎭 Decode your own custom types from strings with logic and context-aware error handling\n\n🌲 Nested subcommands, composable API\n\n### Basic usage\n\n```ts\nimport { command, run, string, number, positional, option } from 'cmd-ts';\n\nconst cmd = command({\n  name: 'my-command',\n  description: 'print something to the screen',\n  version: '1.0.0',\n  args: {\n    number: positional({ type: number, displayName: 'num' }),\n    message: option({\n      long: 'greeting',\n      type: string,\n    }),\n  },\n  handler: (args) => {\n    args.message; // string\n    args.number; // number\n    console.log(args);\n  },\n});\n\nrun(cmd, process.argv.slice(2));\n```\n\n#### `command(arguments)`\n\nCreates a CLI command.\n\n### Decoding custom types from strings\n\nNot all command line arguments are strings. You sometimes want integers, UUIDs, file paths, directories, globs...\n\n> **Note:** this section describes the `ReadStream` type, implemented in `./src/example/test-types.ts`\n\nLet's say we're about to write a `cat` clone. We want to accept a file to read into stdout. A simple example would be something like:\n\n```ts\n// my-app.ts\n\nimport { command, run, positional, string } from 'cmd-ts';\n\nconst app = command({\n  /// name: ...,\n  args: {\n    file: positional({ type: string, displayName: 'file' }),\n  },\n  handler: ({ file }) => {\n    // read the file to the screen\n    fs.createReadStream(file).pipe(stdout);\n  },\n});\n\n// parse arguments\nrun(app, process.argv.slice(2));\n```\n\nThat works okay. But we can do better. In which ways?\n\n- Error handling is out of the command line argument parser context, and in userland, making things less consistent and pretty.\n- It shows we lack composability and encapsulation — and we miss a way to distribute shared \"command line\" behavior.\n\nWhat if we had a way to get a `Stream` out of the parser, instead of a plain string? This is where `cmd-ts` gets its power from, custom type decoding:\n\n```ts\n// ReadStream.ts\n\nimport { Type } from 'cmd-ts';\nimport fs from 'fs';\n\n// Type<string, Stream> reads as \"A type from `string` to `Stream`\"\nconst ReadStream: Type<string, Stream> = {\n  async from(str) {\n    if (!fs.existsSync(str)) {\n      // Here is our error handling!\n      throw new Error('File not found');\n    }\n\n    return fs.createReadStream(str);\n  },\n};\n```\n\nNow we can use (and share) this type and always get a `Stream`, instead of carrying the implementation detail around:\n\n```ts\n// my-app.ts\n\nimport { command, run, positional } from 'cmd-ts';\n\nconst app = command({\n  // name: ...,\n  args: {\n    stream: positional({ type: ReadStream, displayName: 'file' }),\n  },\n  handler: ({ stream }) => stream.pipe(process.stdout),\n});\n\n// parse arguments\nrun(app, process.argv.slice(2));\n```\n\nEncapsulating runtime behaviour and safe type conversions can help us with awesome user experience:\n\n- We can throw an error when the file is not found\n- We can try to parse the string as a URI and check if the protocol is HTTP, if so - make an HTTP request and return the body stream\n- We can see if the string is `-`, and when it happens, return `process.stdin` like many Unix applications\n\nAnd the best thing about it — everything is encapsulated to an easily tested type definition, which can be easily shared and reused. Take a look at [io-ts-types](https://github.com/gcanti/io-ts-types), for instance, which has types like DateFromISOString, NumberFromString and more, which is something we can totally do.\n\n## Inspiration\n\nThis project was previously called `clio-ts`, because it was based on `io-ts`. This is no longer the case, because I want to reduce the dependency count and mental overhead. I might have a function to migrate types between the two.\n"
  },
  {
    "path": "batteries/fs/package.json",
    "content": "{\n\t\"types\": \"../../dist/esm/batteries/fs.d.ts\",\n\t\"main\": \"../../dist/cjs/batteries/fs.js\",\n\t\"module\": \"../../dist/esm/batteries/fs.js\"\n}\n"
  },
  {
    "path": "batteries/url/package.json",
    "content": "{\n\t\"types\": \"../../dist/esm/batteries/url.d.ts\",\n\t\"main\": \"../../dist/cjs/batteries/url.js\",\n\t\"module\": \"../../dist/esm/batteries/url.js\"\n}\n"
  },
  {
    "path": "batteries/vercel-formatter/package.json",
    "content": "{\n\t\"types\": \"../../dist/esm/batteries/vercel-formatter.d.ts\",\n\t\"main\": \"../../dist/cjs/batteries/vercel-formatter.js\",\n\t\"module\": \"../../dist/esm/batteries/vercel-formatter.js\"\n}\n"
  },
  {
    "path": "biome.json",
    "content": "{\n\t\"$schema\": \"https://biomejs.dev/schemas/1.9.4/schema.json\",\n\t\"vcs\": {\n\t\t\"enabled\": false,\n\t\t\"clientKind\": \"git\",\n\t\t\"useIgnoreFile\": false\n\t},\n\t\"files\": {\n\t\t\"ignoreUnknown\": false,\n\t\t\"ignore\": []\n\t},\n\t\"formatter\": {\n\t\t\"enabled\": true,\n\t\t\"indentStyle\": \"tab\"\n\t},\n\t\"organizeImports\": {\n\t\t\"enabled\": true\n\t},\n\t\"linter\": {\n\t\t\"enabled\": true,\n\t\t\"rules\": {\n\t\t\t\"recommended\": true,\n\t\t\t\"suspicious\": {\n\t\t\t\t\"noExplicitAny\": \"off\"\n\t\t\t},\n\t\t\t\"complexity\": {\n\t\t\t\t\"noForEach\": \"off\",\n\t\t\t\t\"noBannedTypes\": \"off\"\n\t\t\t}\n\t\t}\n\t},\n\t\"javascript\": {\n\t\t\"formatter\": {\n\t\t\t\"quoteStyle\": \"double\"\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "book.toml",
    "content": "[book]\nauthors = [\"Gal Schlezinger\"]\nlanguage = \"en\"\nmultilingual = false\nsrc = \"docs\"\ntitle = \"cmd-ts\"\n"
  },
  {
    "path": "docs/SUMMARY.md",
    "content": "# Summary\n\n- [Introduction](./introduction.md)\n- [Getting Started](./getting_started.md)\n- [Included Types](./included_types.md)\n- [Custom Types](./custom_types.md)\n- [Battery Packs](./batteries.md)\n  - [File System](./batteries_file_system.md)\n  - [URL](./batteries_url.md)\n- [Parsers and Combinators](./parsers.md)\n  - [Positional Arguments](./parsers/positionals.md)\n  - [Options](./parsers/options.md)\n  - [Flags](./parsers/flags.md)\n  - [Command](./parsers/command.md)\n  - [Subcommands](./parsers/subcommands.md)\n  - [Binary](./parsers/binary.md)\n  - [Building a Custom Parser](./parsers/custom.md)\n"
  },
  {
    "path": "docs/batteries.md",
    "content": "# Battery Packs\n\nBatteries, from the term \"batteries included\", are optional imports you can use in your application but aren't needed in every application. They might have dependencies of their own peer dependencies or run only in a specific runtime (browser, Node.js).\n\nHere are some battery packs:\n\n- [File System](./batteries_file_system.md)\n- [URL](./batteries_url.md)\n"
  },
  {
    "path": "docs/batteries_file_system.md",
    "content": "# File System Battery Pack\n\nThe file system battery pack contains the following types:\n\n### `ExistingPath`\n\n```typescript\nimport { ExistingPath } from 'cmd-ts/batteries/fs';\n```\n\nResolves into a path that exists. Fails if the path does not exist.\nIf a relative path is provided (`../file`), it will expand by using the current working directory.\n\n### `Directory`\n\n```typescript\nimport { Directory } from 'cmd-ts/batteries/fs';\n```\n\nResolves into a path of an existing directory. If an existing file was given, it'll use its `dirname`.\n\n### `File`\n\n```typescript\nimport { File } from 'cmd-ts/batteries/fs';\n```\n\nResolves into an existing file. Fails if the provided path is not a file.\n"
  },
  {
    "path": "docs/batteries_url.md",
    "content": "# URL Battery Pack\n\nThe URL battery pack contains the following types:\n\n### `Url`\n\n```typescript\nimport { Url } from 'cmd-ts/batteries/url';\n```\n\nResolves into a `URL` class. Fails if there is no `host` or `protocol`.\n\n### `HttpUrl`\n\n```typescript\nimport { HttpUrl } from 'cmd-ts/batteries/url';\n```\n\nResolves into a `URL` class. Fails if the protocol is not `http` or `https`\n"
  },
  {
    "path": "docs/custom_types.md",
    "content": "# Custom Types\n\nNot all command line arguments are strings. You sometimes want integers, UUIDs, file paths, directories, globs...\n\n> **Note:** this section describes the `ReadStream` type, implemented in `./example/test-types.ts`\n\nLet's say we're about to write a `cat` clone. We want to accept a file to read into stdout. A simple example would be something like:\n\n```ts\n// my-app.ts\n\nimport { command, run, positional, string } from 'cmd-ts';\n\nconst app = command({\n  /// name: ...,\n  args: {\n    file: positional({ type: string, displayName: 'file' }),\n  },\n  handler: ({ file }) => {\n    // read the file to the screen\n    fs.createReadStream(file).pipe(stdout);\n  },\n});\n\n// parse arguments\nrun(app, process.argv.slice(2));\n```\n\nThat works well! We already get autocomplete from TypeScript and we're making progress towards developer experience. Still, we can do better. In which ways, you might think?\n\n- Error handling is non existent, and if we'd implement it in our handler it'll be out of the command line argument parser context, making things less consistent and pretty.\n- It shows we lack composability and encapsulation — we miss a way to share and distribute \"command line\" behavior.\n\n> 💡 What if we had a way to get a `Stream` out of the parser, instead of a plain string?\n\nThis is where `cmd-ts` gets its power from,\n\n### Custom Type Decoding\n\nExported from `cmd-ts`, the construct `Type<A, B>` is a way to declare a type that can be converted from `A` into `B`, in a safe manner. `cmd-ts` uses it to decode the arguments provided. You might've seen the `string` type, which is `Type<string, string>`, or, the identity: because every string is a string. Constructing our own types let us have all the implementation we need in an isolated and easily composable.\n\nSo in our app, we need to implement a `Type<string, Stream>`, or — a type that reads a `string` and outputs a `Stream`:\n\n```ts\n// ReadStream.ts\n\nimport { Type } from 'cmd-ts';\nimport fs from 'fs';\n\n// Type<string, Stream> reads as \"A type from `string` to `Stream`\"\nconst ReadStream: Type<string, Stream> = {\n  async from(str) {\n    if (!fs.existsSync(str)) {\n      // Here is our error handling!\n      throw new Error('File not found');\n    }\n\n    return fs.createReadStream(str);\n  },\n};\n```\n\n- `from` is the only required key in `Type<A, B>`. It's an async operation that gets `A` and returns a `B`, or throws an error with some message.\n- Other than `from`, we can provide more metadata about the type:\n  - `description` to provide a default description for this type\n  - `displayName` is a short way to describe the type in the help\n  - `defaultValue(): B` to allow the type to be optional and have a default value\n  - `onMissing(): B | Promise<B>` to provide a dynamic fallback when the argument is not provided (used as fallback if `defaultValue` is not provided)\n\nUsing the type we've just created is no different that using `string`:\n\n```ts\n// my-app.ts\n\nimport { command, run, positional } from 'cmd-ts';\n\nconst app = command({\n  // name: ...,\n  args: {\n    stream: positional({ type: ReadStream, displayName: 'file' }),\n  },\n  handler: ({ stream }) => stream.pipe(process.stdout),\n});\n\n// parse arguments\nrun(app, process.argv.slice(2));\n```\n\nOur `handler` function now takes a `stream` which has a type of `Stream`. This is amazing: we've pushed the logic of encoding a `string` into a `Stream` outside of our implementation, which free us from having lots of guards and checks inside our `handler` function, making it less readable and harder to test.\n\nNow, we can add more features to our `ReadStream` type and stop touching our code which expects a `Stream`:\n\n- We can throw a detailed error when the file is not found\n- We can try to parse the string as a URI and check if the protocol is HTTP, if so - make an HTTP request and return the body stream\n- We can see if the string is `-`, and when it happens, return `process.stdin` like many Unix applications\n\n### Custom Types with `onMissing`\n\nCustom types can also provide dynamic defaults using `onMissing`. This is useful when you want the type itself to determine what happens when no argument is provided:\n\n```ts\nconst ConfigFile: Type<string, Config> = {\n  async from(str) {\n    if (!fs.existsSync(str)) {\n      throw new Error(`Config file not found: ${str}`);\n    }\n    return JSON.parse(fs.readFileSync(str, 'utf8'));\n  },\n  \n  displayName: 'config-file',\n  \n  async onMissing() {\n    // Look for config in standard locations when not provided\n    const candidates = [\n      './config.json',\n      path.join(os.homedir(), '.myapp', 'config.json'),\n      '/etc/myapp/config.json'\n    ];\n    \n    for (const candidate of candidates) {\n      if (fs.existsSync(candidate)) {\n        console.log(`Using config from: ${candidate}`);\n        return JSON.parse(fs.readFileSync(candidate, 'utf8'));\n      }\n    }\n    \n    // Return default config if none found\n    return { debug: false, verbose: false };\n  },\n};\n```\n\nAnd the best thing about it — everything is encapsulated to an easily tested type definition, which can be easily shared and reused. Take a look at [io-ts-types](https://github.com/gcanti/io-ts-types), for instance, which has types like DateFromISOString, NumberFromString and more, which is something we can totally do.\n"
  },
  {
    "path": "docs/getting_started.md",
    "content": "# Getting Started\n\nInstall the package using npm:\n\n```\nnpm install --save cmd-ts\n```\n\nor if you use Yarn:\n\n```\nyarn add cmd-ts\n```\n\n## Using `cmd-ts`\n\nAll the interesting stuff is exported from the main module. Try writing the following app:\n\n```ts\nimport { command, run, string, positional } from 'cmd-ts';\n\nconst app = command({\n  name: 'my-first-app',\n  args: {\n    someArg: positional({ type: string, displayName: 'some arg' }),\n  },\n  handler: ({ someArg }) => {\n    console.log({ someArg });\n  },\n});\n\nrun(app, process.argv.slice(2));\n```\n\nThis app is taking one string positional argument and prints it to the screen. Read more about the different parsers and combinators in [Parsers and Combinators](./parsers.md).\n\n> **Note:** `string` is one type that comes included in `cmd-ts`. There are more of these bundled in the [included types guide](./included_types.md). You can define your own types using the [custom types guide](./custom_types.md)\n"
  },
  {
    "path": "docs/included_types.md",
    "content": "# Included Types\n\n### `string`\n\nA simple `string => string` type. Useful for [`option`](./parsers/options.md) and [`positional`](./parsers/positionals.md) arguments\n\n### `boolean`\n\nA simple `boolean => boolean` type. Useful for [`flag`](./parsers/flags.md)\n\n### `number`\n\nA `string => number` type. Checks that the input is indeed a number or fails with a descriptive error message.\n\n### `optional(type)`\n\nTakes a type and makes it nullable by providing a default value of `undefined`\n\n### `array(type)`\n\nTakes a type and turns it into an array of type, useful for [`multioption`](./parsers/options.md) and [`multiflag`](./parsers/flags.md).\n\n### `union([types])`\n\nTries to decode the types provided until it succeeds, or throws all the errors combined. There's an optional configuration to this function:\n\n- `combineErrors`: function that takes a list of strings (the error messages) and returns a string which is the combined error message. The default value for it is to join with a newline: `xs => xs.join(\"\\n\")`.\n\n### `oneOf([\"string1\", \"string2\", ...])`\n\nTakes a closed set of string values to decode from. An exact enum.\n"
  },
  {
    "path": "docs/introduction.md",
    "content": "# Introduction\n\n`cmd-ts` is a type-driven command line argument parser written in TypeScript. Let's break it down:\n\n### A command line argument parser written in TypeScript\n\nMuch like `commander` and similar Node.js tools, the goal of `cmd-ts` is to provide your users a superior experience while using your app from the terminal.\n\n`cmd-ts` is built with TypeScript and tries to bring soundness and ease of use to CLI apps. It is fully typed and allows custom types as CLI arguments. More on that on the next paragraph.\n\n`cmd-ts` API is built with small, composable \"parsers\" that are easily extensible\n\n`cmd-ts` has a wonderful error output, which preserves the parsing context, allowing the users to know what they've mistyped and where, instead of playing a guessing game\n\n### Type-driven command line argument parser\n\n`cmd-ts` is essentially an adapter between the user's shell and the code. For some reason, most command line argument parsers only accept strings as arguments, and provide no typechecking that the value makes sense in the context of your app:\n\n- Some arguments may be a number; so providing a string should result in an error\n- Some arguments may be an integer; so providing a float should result in an error\n- Some arguments may be readable files; so providing a missing path should result in an error\n\nThese types of concerns are mostly implemented in userland right now. `cmd-ts` has a different way of thinking about it using the `Type` construct, which provides both static (TypeScript) and runtime typechecking. The power of `Type` lets us have a strongly-typed commands that provide us autocomplete for our implementation and confidence in our codebase, while providing an awesome experience for the users, when they provide a wrong argument. More on that on the [Custom Types guide](./custom_types.md)\n"
  },
  {
    "path": "docs/parsers/binary.md",
    "content": "# `binary`\n\nA standard Node executable will receive two additional arguments that are often omitted:\n\n- the node executable path\n- the command path\n\n`cmd-ts` provides a small helper that ignores the first two positional arguments that a command receives:\n\n```ts\nimport { binary, command, run } from 'cmd-ts';\n\nconst myCommand = command({\n  /* ... */\n});\nconst binaryCommand = binary(myCommand);\nrun(binaryCommand, process.argv);\n```\n"
  },
  {
    "path": "docs/parsers/command.md",
    "content": "# `command`\n\nThis is what we call \"a combinator\": `command` takes multiple parsers and combine them into one parser that can also take raw user input using its `run` function.\n\n### Config\n\n- `name` (required): A name for the command\n- `version`: A version for the command\n- `handler` (required): A function that takes all the arguments and do something with it\n- `args` (required): An object where the keys are the argument names (how they'll be treated in code) and the values are [parsers](../parsers.md)\n- `aliases`: A list of other names this command can be called with. Useful with [`subcommands`](./subcommands.md)\n\n### Usage\n\n```ts\n{{#include ../../example/app2.ts}}\n```\n"
  },
  {
    "path": "docs/parsers/custom.md",
    "content": "# Building a Custom Parser\n\n... wip ...\n"
  },
  {
    "path": "docs/parsers/flags.md",
    "content": "# Flags\n\nA command line flag is an argument or arguments in the following formats:\n\n- `--long-key`\n- `--long-key=true` or `--long-key=false`\n- `-s`\n- `-s=true` or `--long-key=false`\n\nwhere `long-key` is \"the long form key\" and `s` is \"a short form key\".\n\nFlags can also be stacked using their short form. Let's assume we have flags with the short form keys of `a`, `b` and `c`: `-abc` will be parsed the same as `-a -b -c`.\n\nThere are two ways to parse flags:\n\n- [The `flag` parser](#flag) which parses one and only one flag\n- [The `multiflag` parser](#multiflag) which parser none or multiple flags\n\n## `flag`\n\nParses one and only one flag. Accepts a `Type` from `boolean` to any value to decode the users' intent.\n\nIn order to make this optional, either the type provided or a `defaultValue` function should be provided. In order to make a certain type optional, you can take a look at [`optional`](../included_types.md#optionaltype)\n\nThis parser will fail to parse if:\n\n- There are zero flags that match the long form key or the short form key\n- There are more than one flag that match the long form key or the short form key\n- A value other than `true` or `false` was provided (if it was treated like [an option](./options.md))\n- Decoding the user input fails\n\n### Usage\n\n```ts\nimport { command, boolean, flag } from 'cmd-ts';\n\nconst myFlag = flag({\n  type: boolean,\n  long: 'my-flag',\n  short: 'f',\n});\n\nconst cmd = command({\n  name: 'my flag',\n  args: { myFlag },\n});\n```\n\n### Dynamic Defaults with `onMissing`\n\nThe `onMissing` callback provides a way to dynamically generate values when a flag is not provided. This is useful for environment-based defaults, configuration file lookups, or user prompts.\n\n```ts\nimport { command, flag } from 'cmd-ts';\n\nconst verboseFlag = flag({\n  long: 'verbose',\n  short: 'v',\n  description: 'Enable verbose output',\n  onMissing: () => {\n    // Check environment variable as fallback\n    return process.env.NODE_ENV === 'development';\n  },\n});\n\nconst debugFlag = flag({\n  long: 'debug',\n  short: 'd',\n  description: 'Enable debug mode',\n  onMissing: async () => {\n    // Async example: check config file or make API call\n    const config = await loadConfig();\n    return config.debug || false;\n  },\n});\n\nconst cmd = command({\n  name: 'my app',\n  args: { \n    verbose: verboseFlag,\n    debug: debugFlag,\n  },\n  handler: ({ verbose, debug }) => {\n    console.log(`Verbose: ${verbose}, Debug: ${debug}`);\n  },\n});\n```\n\n### Config\n\n- `type` (required): A type from `boolean` to any value\n- `long` (required): The long form key\n- `short`: The short form key\n- `description`: A short description regarding the option\n- `displayName`: A short description regarding the option\n- `defaultValue`: A function that returns a default value for the option\n- `defaultValueIsSerializable`: Whether to print the defaultValue as a string in the help docs.\n- `onMissing`: A function (sync or async) that returns a value when the flag is not provided. Used as fallback if `defaultValue` is not provided.\n\n## `multiflag`\n\nParses multiple or zero flags. Accepts a `Type` from `boolean[]` to any value, letting you do the conversion yourself.\n\n> **Note:** using `multiflag` will drop all the contextual errors. Every error on the type conversion will show up as if all of the options were errored. This is a higher level with less granularity.\n\nThis parser will fail to parse if:\n\n- A value other than `true` or `false` was provided (if it was treated like [an option](./options.md))\n- Decoding the user input fails\n\n### Config\n\n- `type` (required): A type from `boolean[]` to any value\n- `long` (required): The long form key\n- `short`: The short form key\n- `description`: A short description regarding the flag\n- `displayName`: A short description regarding the flag\n"
  },
  {
    "path": "docs/parsers/options.md",
    "content": "# Options\n\nA command line option is an argument or arguments in the following formats:\n\n- `--long-key value`\n- `--long-key=value`\n- `-s value`\n- `-s=value`\n\nwhere `long-key` is \"the long form key\" and `s` is \"a short form key\".\n\nThere are two ways to parse options:\n\n- [The `option` parser](#option) which parses one and only one option\n- [The `multioption` parser](#multioption) which parser none or multiple options\n\n## `option`\n\nParses one and only one option. Accepts a `Type` from `string` to any value to decode the users' intent.\n\nIn order to make this optional, either the type provided or a `defaultValue` function should be provided. In order to make a certain type optional, you can take a look at [`optional`](../included_types.md#optionaltype)\n\nThis parser will fail to parse if:\n\n- There are zero options that match the long form key or the short form key\n- There are more than one option that match the long form key or the short form key\n- No value was provided (if it was treated like [a flag](./flags.md))\n- Decoding the user input fails\n\n### Usage\n\n```ts\nimport { command, number, option } from 'cmd-ts';\n\nconst myNumber = option({\n  type: number,\n  long: 'my-number',\n  short: 'n',\n});\n\nconst cmd = command({\n  name: 'my number',\n  args: { myNumber },\n});\n```\n\n### Dynamic Defaults with `onMissing`\n\nThe `onMissing` callback provides a way to dynamically generate values when an option is not provided. This is perfect for interactive prompts:\n\n```ts\nimport { command, option, string } from './src';\nimport { createInterface } from 'readline/promises';\n\nconst name = option({\n  type: string,\n  long: 'name',\n  short: 'n',\n  description: 'Your name for the greeting',\n  onMissing: async () => {\n    const rl = createInterface({\n      input: process.stdin,\n      output: process.stdout,\n    });\n\n    try {\n      const answer = await rl.question(\"What's your name? \");\n      return answer.trim() || 'Anonymous';\n    } finally {\n      rl.close();\n    }\n  },\n});\n\nconst cmd = command({\n  name: 'greeting',\n  args: { name },\n  handler: ({ name }) => {\n    console.log(`Hello, ${name}!`);\n  },\n});\n```\n\n### Config\n\n- `type` (required): A type from `string` to any value\n- `long` (required): The long form key\n- `short`: The short form key\n- `description`: A short description regarding the option\n- `displayName`: A short description regarding the option\n- `defaultValue`: A function that returns a default value for the option\n- `defaultValueIsSerializable`: Whether to print the defaultValue as a string in the help docs.\n- `onMissing`: A function (sync or async) that returns a value when the option is not provided. Used as fallback if `defaultValue` is not provided.\n\n## `multioption`\n\nParses multiple or zero options. Accepts a `Type` from `string[]` to any value, letting you do the conversion yourself.\n\n> **Note:** using `multioption` will drop all the contextual errors. Every error on the type conversion will show up as if all of the options were errored. This is a higher level with less granularity.\n\nThis parser will fail to parse if:\n\n- No value was provided (if it was treated like [a flag](./flags.md))\n- Decoding the user input fails\n\n### Dynamic Defaults for `multioption`\n\nLike single options, `multioption` supports `onMissing` callbacks for dynamic default arrays:\n\n```ts\nimport { command, multioption } from 'cmd-ts';\nimport type { Type } from 'cmd-ts';\n\nconst stringArray: Type<string[], string[]> = {\n  async from(strings) {\n    return strings;\n  },\n  displayName: 'string',\n};\n\nconst includes = multioption({\n  type: stringArray,\n  long: 'include',\n  short: 'i',\n  description: 'Files to include',\n  onMissing: async () => {\n    // Auto-discover files when none specified\n    const files = await glob('src/**/*.ts');\n    return files;\n  },\n});\n\nconst cmd = command({\n  name: 'build',\n  args: { includes },\n  handler: ({ includes }) => {\n    console.log(`Processing files: ${includes.join(', ')}`);\n  },\n});\n```\n\n### Config\n\n- `type` (required): A type from `string[]` to any value\n- `long` (required): The long form key\n- `short`: The short form key\n- `description`: A short description regarding the option\n- `displayName`: A short description regarding the option\n- `defaultValue`: A function that returns a default value for the option array in case no options were provided. If not provided, the default value will be an empty array.\n- `defaultValueIsSerializable`: Whether to print the defaultValue as a string in the help docs.\n- `onMissing`: A function (sync or async) that returns a value when the option is not provided. Used as fallback if `defaultValue` is not provided.\n"
  },
  {
    "path": "docs/parsers/positionals.md",
    "content": "# Positional Arguments\n\nRead positional arguments. Positional arguments are all the arguments that are not an [option](./options.md) or a [flag](./options.md). So in the following command line invocation for the `my-app` command:\n\n```\nmy-app greet --greeting Hello Joe\n       ^^^^^                  ^^^  - positional arguments\n```\n\n## `positional`\n\nFetch a single positional argument\n\nThis parser will fail to parse if:\n\n- Decoding the user input fails\n\n### Config:\n\n- `displayName` (required): a display name for the named argument. This is required so it'll be understandable what the argument is for\n- `type` (required): a [Type](../included_types.md) from `string` that will help decoding the value provided by the user\n- `description`: a short text describing what this argument is for\n\n## `restPositionals`\n\nFetch all the rest positionals\n\n> **Note:** this will swallaow all the other positionals, so you can't use [`positional`](#positional) to fetch a positional afterwards.\n\nThis parser will fail to parse if:\n\n- Decoding the user input fails\n\n### Config:\n\n- `displayName`: a display name for the named argument.\n- `type` (required): a [Type](../included_types.md) from `string` that will help decoding the value provided by the user. Each argument will go through this.\n- `description`: a short text describing what these arguments are for\n"
  },
  {
    "path": "docs/parsers/subcommands.md",
    "content": "# `subcommands`\n\nThis is yet another combinator, which takes a couple of [`command`](./command.md)s and produce a new command that the first argument will choose between them.\n\n### Config\n\n- `name` (required): A name for the container\n- `version`: The container version\n- `cmds`: An object where the keys are the names of the subcommands to use, and the values are [`command`](./command.md) instances. You can also provide `subcommands` instances to nest a nested subcommand!\n\n### Usage\n\n```ts\nimport { command, subcommands, run } from 'cmd-ts';\n\nconst cmd1 = command({\n  /* ... */\n});\nconst cmd2 = command({\n  /* ... */\n});\n\nconst subcmd1 = subcommands({\n  name: 'my subcmd1',\n  cmds: { cmd1, cmd2 },\n});\n\nconst nestingSubcommands = subcommands({\n  name: 'nesting subcommands',\n  cmds: { subcmd1 },\n});\n\nrun(nestingSubcommands, process.argv.slice(2));\n```\n"
  },
  {
    "path": "docs/parsers.md",
    "content": "# Parsers and Combinators\n\n`cmd-ts` can help you build a full command line application, with nested commands, options, arguments, and whatever you want. One of the secret sauces baked into `cmd-ts` is the ability to compose parsers.\n\n## Argument Parser\n\nAn argument parser is a simple struct with a `parse` function and an optional `register` function.\n\n`cmd-ts` is shipped with a couple of parsers and combinators to help you build your dream command-line app. [`subcommands`](./parsers/subcommands.md) are built using nested [`command`](./parsers/command.md)s. Every [`command`](./parsers/command.md) is built with [`flag`](./parsers/flags.md), [`option`](./parsers/options.md) and [`positional`](./parsers/positionals.md) arguments. Here is a short parser description:\n\n- [`positional` and `restPositionals`](./parsers/positionals.md) to read arguments by position\n- [`option` and `multioption`](./parsers/options.md) to read binary `--key value` arguments\n- [`flag` and `multiflag`](./parsers/flags.md) to read unary `--key` arguments\n- [`command`](./parsers/command.md) to compose multiple arguments into a command line app\n- [`subcommands`](./parsers/subcommands.md) to compose multiple command line apps into one command line app\n- [`binary`](./parsers/binary.md) to make a command line app a UNIX-executable-ready command\n"
  },
  {
    "path": "example/app.ts",
    "content": "#!/usr/bin/env YARN_SILENT=1 yarn ts-node\n\n/* istanbul ignore file */\n\nimport {\n\tbinary,\n\tboolean,\n\tcommand,\n\textendType,\n\tflag,\n\tnumber,\n\toption,\n\toptional,\n\tpositional,\n\trestPositionals,\n\trun,\n\tstring,\n\tsubcommands,\n\tunion,\n} from \"../src\";\nimport { Integer, ReadStream } from \"./test-types\";\n\nconst complex = command({\n\tversion: \"6.6.6-alpha\",\n\targs: {\n\t\tpos1: positional({\n\t\t\tdisplayName: \"pos1\",\n\t\t\ttype: Integer,\n\t\t}),\n\t\tpos2: positional(),\n\t\tnamed1: option({\n\t\t\ttype: Integer,\n\t\t\tshort: \"n\",\n\t\t\tlong: \"number\",\n\t\t}),\n\t\tintOrString: option({\n\t\t\ttype: union([Integer, string]),\n\t\t\tlong: \"int-or-string\",\n\t\t}),\n\t\tfloatOrString: option({\n\t\t\ttype: union([number, string]),\n\t\t\tlong: \"float-or-string\",\n\t\t}),\n\t\toptionalOption: option({\n\t\t\ttype: optional(string),\n\t\t\tlong: \"optional-option\",\n\t\t}),\n\t\toptionWithoutType: option({\n\t\t\tlong: \"no-type-option\",\n\t\t}),\n\t\toptionWithDefault: option({\n\t\t\tlong: \"optional-with-default\",\n\t\t\tenv: \"SOME_ENV_VAR\",\n\t\t\ttype: {\n\t\t\t\t...string,\n\t\t\t\tdefaultValue: () => \"Hello\",\n\t\t\t\tdefaultValueIsSerializable: true,\n\t\t\t},\n\t\t}),\n\t\tbool: flag({\n\t\t\ttype: boolean,\n\t\t\tlong: \"boolean\",\n\t\t}),\n\t\tboolWithoutType: flag({ long: \"bool-without-type\" }),\n\t\trest: restPositionals(),\n\t},\n\tname: \"printer\",\n\tdescription: \"Just prints the arguments\",\n\texamples: [\n\t\t{\n\t\t\tdescription: \"Print with required args\",\n\t\t\tcommand:\n\t\t\t\t\"complex 42 hello -n 10 --int-or-string 5 --float-or-string 3.14\",\n\t\t},\n\t\t{\n\t\t\tdescription: \"Using a string for int-or-string\",\n\t\t\tcommand: \"complex 1 world -n 1 --int-or-string foo --float-or-string bar\",\n\t\t},\n\t],\n\thandler: (args) => {\n\t\t/** @export complex -> intOrString */\n\t\tconst x = args.intOrString;\n\n\t\t/** @export complex -> floatOrString */\n\t\tconst y = args.floatOrString;\n\n\t\t/** @export complex -> pos2 */\n\t\tconst pos2 = args.pos2;\n\n\t\t/** @export complex -> boolWithoutType */\n\t\tconst boolWithoutType = args.boolWithoutType;\n\n\t\t/** @export complex -> optionWithoutType */\n\t\tconst optionWithoutType = args.optionWithoutType;\n\n\t\t/** @export complex -> rest */\n\t\tconst restPos = args.rest;\n\n\t\tconsole.log(\n\t\t\t\"I got\",\n\t\t\targs,\n\t\t\tx,\n\t\t\ty,\n\t\t\tpos2,\n\t\t\toptionWithoutType,\n\t\t\tboolWithoutType,\n\t\t\trestPos,\n\t\t);\n\t},\n});\n\nconst withStream = command({\n\targs: {\n\t\tstream: positional({\n\t\t\tdisplayName: \"stream\",\n\t\t\ttype: ReadStream,\n\t\t}),\n\t\trestStreams: restPositionals({\n\t\t\tdisplayName: \"stream\",\n\t\t\ttype: ReadStream,\n\t\t}),\n\t},\n\tdescription: \"A simple `cat` clone\",\n\tname: \"cat\",\n\taliases: [\"read\"],\n\thandler: (result) => {\n\t\t/** @export cat -> stream */\n\t\tconst stream = result.stream;\n\t\t/** @export cat -> restStreams */\n\t\tconst restStreams = result.restStreams;\n\t\t[stream, ...restStreams].forEach((s) => s.pipe(process.stdout));\n\t},\n});\n\nconst composed = subcommands({\n\tname: \"another-command\",\n\tcmds: {\n\t\tcat: withStream,\n\t},\n\tdescription: \"a nested subcommand\",\n});\n\nconst Name = extendType(string, {\n\tasync from(s) {\n\t\tif (s.length === 0) {\n\t\t\tthrow new Error(\"name cannot be empty\");\n\t\t}\n\t\tif (s === \"Bon Jovi\") {\n\t\t\tthrow new Error(`Woah, we're half way there\\nWoah! living on a prayer!`);\n\t\t}\n\t\tif (s.charAt(0).toUpperCase() !== s.charAt(0)) {\n\t\t\tthrow new Error(\"name must be capitalized\");\n\t\t}\n\t\treturn s;\n\t},\n\tdisplayName: \"name\",\n});\n\nconst withSubcommands = subcommands({\n\tcmds: {\n\t\tcomplex,\n\t\tcat: withStream,\n\t\tgreet: command({\n\t\t\tname: \"greet\",\n\t\t\tdescription: \"greet a person\",\n\t\t\targs: {\n\t\t\t\ttimes: option({\n\t\t\t\t\ttype: { ...Integer, defaultValue: () => 1 },\n\t\t\t\t\tlong: \"times\",\n\t\t\t\t}),\n\t\t\t\tname: positional({\n\t\t\t\t\tdisplayName: \"name\",\n\t\t\t\t\ttype: Name,\n\t\t\t\t}),\n\t\t\t\tnoExclaim: flag({\n\t\t\t\t\ttype: boolean,\n\t\t\t\t\tlong: \"no-exclaim\",\n\t\t\t\t}),\n\t\t\t\tgreeting: option({\n\t\t\t\t\tlong: \"greeting\",\n\t\t\t\t\ttype: string,\n\t\t\t\t\tdescription: \"the greeting to say\",\n\t\t\t\t\tenv: \"GREETING_NAME\",\n\t\t\t\t}),\n\t\t\t},\n\t\t\texamples: [\n\t\t\t\t{\n\t\t\t\t\tdescription: \"Greet someone\",\n\t\t\t\t\tcommand: \"greet --greeting Hello World\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"Greet multiple times\",\n\t\t\t\t\tcommand: \"greet --greeting Hi --times 3 Gal\",\n\t\t\t\t},\n\t\t\t],\n\t\t\thandler: (result) => {\n\t\t\t\tconst args = result;\n\t\t\t\t/** @export greet -> greeting */\n\t\t\t\tconst greeting = args.greeting;\n\t\t\t\t/** @export greet -> noExclaim */\n\t\t\t\tconst noExclaim = args.noExclaim;\n\t\t\t\t/** @export greet -> name */\n\t\t\t\tconst name = args.name;\n\t\t\t\tconst exclaim = noExclaim ? \"\" : \"!\";\n\t\t\t\tconsole.log(`${greeting}, ${name}${exclaim}`);\n\t\t\t},\n\t\t}),\n\t\tcomposed,\n\t},\n\tname: \"subcmds\",\n\tdescription: \"An awesome subcommand app!\",\n\tversion: \"1.0.0\",\n\texamples: [\n\t\t{ description: \"Show help for a command\", command: \"subcmds greet --help\" },\n\t\t{ description: \"Run the cat command\", command: \"subcmds cat ./file.txt\" },\n\t],\n});\n\nconst cli = binary(withSubcommands);\n\nasync function main() {\n\tawait run(cli, process.argv);\n}\n\nmain();\n"
  },
  {
    "path": "example/app2.ts",
    "content": "#!/usr/bin/env YARN_SILENT=1 yarn ts-node\n\nimport {\n\ttype Type,\n\tboolean,\n\tcommand,\n\textendType,\n\tflag,\n\toption,\n\trun,\n\tstring,\n} from \"../src\";\n\nconst PrNumber = extendType(string, {\n\tasync from(branchName) {\n\t\tconst prNumber = branchName === \"master\" ? \"10\" : undefined;\n\n\t\tif (!prNumber) {\n\t\t\tthrow new Error(`There is no PR associated with branch '${branchName}'`);\n\t\t}\n\n\t\treturn prNumber;\n\t},\n\tdefaultValue: () => \"Hello\",\n});\n\nconst Repo: Type<string, string> = {\n\t...string,\n\tdefaultValue: () => {\n\t\tthrow new Error(\"Can't infer repo from git\");\n\t},\n\tdescription: \"repository uri\",\n\tdisplayName: \"uri\",\n};\n\nconst app = command({\n\tname: \"build\",\n\targs: {\n\t\tuser: option({\n\t\t\ttype: string,\n\t\t\tenv: \"APP_USER\",\n\t\t\tlong: \"user\",\n\t\t\tshort: \"u\",\n\t\t}),\n\t\tpassword: option({\n\t\t\ttype: string,\n\t\t\tenv: \"APP_PASS\",\n\t\t\tlong: \"password\",\n\t\t\tshort: \"p\",\n\t\t}),\n\t\trepo: option({\n\t\t\ttype: Repo,\n\t\t\tlong: \"repo\",\n\t\t\tshort: \"r\",\n\t\t}),\n\t\tprNumber: option({\n\t\t\ttype: PrNumber,\n\t\t\tshort: \"b\",\n\t\t\tlong: \"pr-number\",\n\t\t\tenv: \"APP_BRANCH\",\n\t\t}),\n\t\tdev: flag({\n\t\t\ttype: boolean,\n\t\t\tlong: \"dev\",\n\t\t\tshort: \"D\",\n\t\t}),\n\t},\n\thandler: ({ repo, user, password, prNumber, dev }) => {\n\t\tconsole.log({ repo, user, password, prNumber, dev });\n\t},\n});\n\nrun(app, process.argv.slice(2));\n"
  },
  {
    "path": "example/app3.ts",
    "content": "import { inspect } from \"node:util\";\nimport {\n\tcommand,\n\tnumber,\n\toption,\n\toptional,\n\tpositional,\n\trun,\n\tstring,\n\tsubcommands,\n} from \"../src\";\n\nconst sub1 = command({\n\tname: \"sub1\",\n\targs: {\n\t\tname: option({ type: string, long: \"name\" }),\n\t},\n\thandler: ({ name }) => {\n\t\tconsole.log({ name });\n\t},\n});\n\nconst sub2 = command({\n\tname: \"sub2\",\n\targs: {\n\t\tage: positional({ type: optional(number) }),\n\t\tname: positional({\n\t\t\ttype: {\n\t\t\t\t...string,\n\t\t\t\tdefaultValue: () => \"anonymous\",\n\t\t\t\tdefaultValueIsSerializable: true,\n\t\t\t},\n\t\t}),\n\t},\n\thandler({ name, age }) {\n\t\tconsole.log(inspect({ name, age }));\n\t},\n});\n\nconst nested = subcommands({\n\tname: \"subcmds\",\n\tcmds: {\n\t\tsub1,\n\t\tsub2,\n\t},\n});\n\nrun(nested, process.argv.slice(2));\n"
  },
  {
    "path": "example/app4.ts",
    "content": "#!/usr/bin/env YARN_SILENT=1 yarn ts-node\n\nimport { command, extendType, option, run, string } from \"../src\";\n\nconst AsyncType = extendType(string, {\n\tasync from(str) {\n\t\treturn str;\n\t},\n\tonMissing: () => Promise.resolve(\"default value\"),\n\tdescription: \"A type with onMissing callback\",\n});\n\nconst app = command({\n\tname: \"async-test\",\n\targs: {\n\t\tasyncArg: option({\n\t\t\ttype: AsyncType,\n\t\t\tlong: \"async-arg\",\n\t\t\tshort: \"a\",\n\t\t}),\n\t\tasyncArg2: option({\n\t\t\tlong: \"async-arg-2\",\n\t\t\ttype: AsyncType,\n\t\t\tdefaultValue: () => \"Hi\",\n\t\t\tdefaultValueIsSerializable: true,\n\t\t}),\n\t\targ3: option({\n\t\t\tlong: \"async-arg-3\",\n\t\t\ttype: string,\n\t\t\tonMissing: () => \"Hello from opt\",\n\t\t}),\n\t},\n\thandler: ({ asyncArg, asyncArg2, arg3 }) => {\n\t\tconsole.log(`Result: ${asyncArg}, ${asyncArg2}, ${arg3}`);\n\t},\n});\n\nrun(app, process.argv.slice(2));\n"
  },
  {
    "path": "example/app5.ts",
    "content": "#!/usr/bin/env YARN_SILENT=1 yarn ts-node\n\nimport { command, extendType, option, run, string } from \"../src\";\n\nconst AsyncFailureType = extendType(string, {\n\tasync from(str) {\n\t\treturn str;\n\t},\n\tonMissing: () => Promise.reject(new Error(\"Async onMissing failed\")),\n\tdescription: \"A type with onMissing callback that fails\",\n});\n\nconst app = command({\n\tname: \"async-test-failure\",\n\targs: {\n\t\tfailArg: option({\n\t\t\ttype: AsyncFailureType,\n\t\t\tlong: \"fail-arg\",\n\t\t\tshort: \"f\",\n\t\t}),\n\t},\n\thandler: ({ failArg }) => {\n\t\tconsole.log(`Result: ${failArg}`);\n\t},\n});\n\nrun(app, process.argv.slice(2));\n"
  },
  {
    "path": "example/app6.ts",
    "content": "#!/usr/bin/env YARN_SILENT=1 yarn ts-node\n\nimport { command, flag, run } from \"../src\";\n\nconst app = command({\n\tname: \"flag-onmissing-demo\",\n\targs: {\n\t\tverbose: flag({\n\t\t\tlong: \"verbose\",\n\t\t\tshort: \"v\",\n\t\t\tdescription: \"Enable verbose output\",\n\t\t\tonMissing: () => {\n\t\t\t\tconsole.log(\"🤔 Verbose flag not provided, checking environment...\");\n\t\t\t\treturn process.env.NODE_ENV === \"development\";\n\t\t\t},\n\t\t}),\n\t\tdebug: flag({\n\t\t\tlong: \"debug\",\n\t\t\tshort: \"d\",\n\t\t\tdescription: \"Enable debug mode\",\n\t\t\tonMissing: async () => {\n\t\t\t\tconsole.log(\"🔍 Debug flag missing, simulating config check...\");\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, 100));\n\t\t\t\treturn Math.random() > 0.5; // Simulate config-based decision\n\t\t\t},\n\t\t}),\n\t\tforce: flag({\n\t\t\tlong: \"force\",\n\t\t\tshort: \"f\",\n\t\t\tdescription: \"Force operation without confirmation\",\n\t\t\tonMissing: () => {\n\t\t\t\tconsole.log(\"⚠️  Force flag not set, would normally prompt user...\");\n\t\t\t\t// In real scenario: return prompt(\"Force operation? (y/n)\") === \"y\"\n\t\t\t\treturn false; // Safe default for demo\n\t\t\t},\n\t\t}),\n\t},\n\thandler: ({ verbose, debug, force }) => {\n\t\tconsole.log(\"\\n📋 Results:\");\n\t\tconsole.log(`  Verbose: ${verbose ? \"✅\" : \"❌\"}`);\n\t\tconsole.log(`  Debug: ${debug ? \"✅\" : \"❌\"}`);\n\t\tconsole.log(`  Force: ${force ? \"✅\" : \"❌\"}`);\n\t},\n});\n\nrun(app, process.argv.slice(2));\n"
  },
  {
    "path": "example/app7.ts",
    "content": "#!/usr/bin/env YARN_SILENT=1 yarn ts-node\n\nimport { command, multioption, run } from \"../src\";\nimport type { Type } from \"../src/type\";\n\n// Create a simple string array type for multioption\nconst stringArray: Type<string[], string[]> = {\n\tasync from(strings) {\n\t\treturn strings;\n\t},\n\tdisplayName: \"string\",\n};\n\nconst app = command({\n\tname: \"multioption-onmissing-demo\",\n\targs: {\n\t\tincludes: multioption({\n\t\t\tlong: \"include\",\n\t\t\tshort: \"i\",\n\t\t\ttype: stringArray,\n\t\t\tdescription: \"Files to include in processing\",\n\t\t\tonMissing: async () => {\n\t\t\t\tconsole.log(\"📁 No includes specified, discovering files...\");\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, 100));\n\t\t\t\t// Simulate filesystem discovery\n\t\t\t\treturn [\"src/**/*.ts\", \"lib/**/*.js\"];\n\t\t\t},\n\t\t}),\n\t\ttargets: multioption({\n\t\t\tlong: \"target\",\n\t\t\tshort: \"t\",\n\t\t\ttype: stringArray,\n\t\t\tdescription: \"Build targets\",\n\t\t\tonMissing: () => {\n\t\t\t\tconsole.log(\"🎯 No targets specified, using environment defaults...\");\n\t\t\t\treturn process.env.NODE_ENV === \"production\"\n\t\t\t\t\t? [\"es2020\", \"node16\"]\n\t\t\t\t\t: [\"esnext\", \"node18\"];\n\t\t\t},\n\t\t}),\n\t\tfeatures: multioption({\n\t\t\tlong: \"feature\",\n\t\t\tshort: \"f\",\n\t\t\ttype: stringArray,\n\t\t\tdescription: \"Features to enable\",\n\t\t\tonMissing: async () => {\n\t\t\t\tconsole.log(\"🚀 No features specified, checking available features...\");\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, 150));\n\t\t\t\tconst available = [\"auth\", \"db\", \"api\", \"ui\"];\n\t\t\t\t// Simulate interactive selection or config-based defaults\n\t\t\t\treturn available.slice(0, 2); // Return first 2 as default\n\t\t\t},\n\t\t}),\n\t},\n\thandler: ({ includes, targets, features }) => {\n\t\tconsole.log(\"\\n📋 Configuration:\");\n\t\tconsole.log(`  Includes: [${includes.join(\", \")}]`);\n\t\tconsole.log(`  Targets: [${targets.join(\", \")}]`);\n\t\tconsole.log(`  Features: [${features.join(\", \")}]`);\n\t},\n});\n\nrun(app, process.argv.slice(2));\n"
  },
  {
    "path": "example/negative-numbers.ts",
    "content": "import { binary, command, number, option, run } from \"../src\";\n\nexport function createCmd() {\n\tconst cmd = command({\n\t\tname: \"test\",\n\t\targs: {\n\t\t\tnumber: option({\n\t\t\t\tlong: \"number\",\n\t\t\t\ttype: number,\n\t\t\t}),\n\t\t},\n\t\tasync handler(args) {\n\t\t\tconsole.log({ args });\n\t\t},\n\t});\n\n\treturn cmd;\n}\n\nif (require.main === module) {\n\tconst cmd = createCmd();\n\trun(binary(cmd), process.argv);\n}\n"
  },
  {
    "path": "example/rest-example.ts",
    "content": "import { binary, command, positional, rest, run } from \"../src\";\n\nconst cmd = command({\n\targs: {\n\t\tscriptName: positional(),\n\t\teverythingElse: rest(),\n\t},\n\tname: \"hi\",\n\thandler({ scriptName, everythingElse }) {\n\t\tconsole.log(JSON.stringify({ scriptName, everythingElse }));\n\t},\n});\n\nrun(binary(cmd), process.argv);\n"
  },
  {
    "path": "example/test-types.ts",
    "content": "/* istanbul ignore file */\n\nimport type { Stream } from \"node:stream\";\nimport { URL } from \"node:url\";\nimport { createReadStream, pathExists, stat } from \"fs-extra\";\nimport fetch from \"node-fetch\";\nimport { type Type, extendType, number } from \"../src\";\n\nexport const Integer: Type<string, number> = extendType(number, {\n\tasync from(n) {\n\t\tif (Math.round(n) !== n) {\n\t\t\tthrow new Error(\"This is a floating-point number\");\n\t\t}\n\t\treturn n;\n\t},\n});\n\nfunction stdin() {\n\treturn (global as any).mockStdin || process.stdin;\n}\n\nexport const ReadStream: Type<string, Stream> = {\n\tdescription: \"A file path or a URL to make a GET request to\",\n\tdisplayName: \"file\",\n\tasync from(str) {\n\t\tif (str.startsWith(\"http://\") || str.startsWith(\"https://\")) {\n\t\t\tconst parsedUrl = new URL(str);\n\n\t\t\tif (parsedUrl.protocol?.startsWith(\"http\")) {\n\t\t\t\tconst response = await fetch(str);\n\t\t\t\tconst statusGroup = Math.floor(response.status / 100);\n\t\t\t\tif (statusGroup !== 2) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Got status ${response.statusText} ${response.status} reading URL`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn response.body;\n\t\t\t}\n\t\t}\n\n\t\tif (str === \"-\") {\n\t\t\treturn stdin();\n\t\t}\n\n\t\tif (!(await pathExists(str))) {\n\t\t\tthrow new Error(`Can't find file in path ${str}`);\n\t\t}\n\n\t\tconst fileStat = await stat(str);\n\t\tif (!fileStat.isFile()) {\n\t\t\tthrow new Error(\"Path is not a file.\");\n\t\t}\n\n\t\treturn createReadStream(str);\n\t},\n};\n\nexport function readStreamToString(s: Stream): Promise<string> {\n\treturn new Promise((resolve, reject) => {\n\t\tlet str = \"\";\n\t\ts.on(\"data\", (x) => {\n\t\t\tstr += x.toString();\n\t\t});\n\t\ts.on(\"error\", (e) => reject(e));\n\t\ts.on(\"end\", () => resolve(str));\n\t});\n}\n\nexport const CommaSeparatedString: Type<string, string[]> = {\n\tdescription: \"comma seperated string\",\n\tasync from(s) {\n\t\treturn s.split(/, ?/);\n\t},\n};\n"
  },
  {
    "path": "example/tsconfig.json",
    "content": "{\n\t\"extends\": \"../tsconfig.noEmit.json\",\n\t\"include\": [\".\"]\n}\n"
  },
  {
    "path": "example/vercel-formatter.ts",
    "content": "import { vercelFormatter } from \"../batteries/vercel-formatter\";\nimport { setDefaultHelpFormatter } from \"../src\";\n\nsetDefaultHelpFormatter(vercelFormatter);\n\nimport(\"./app\");\n"
  },
  {
    "path": "jest.config.js",
    "content": "module.exports = {\n\ttestEnvironment: \"node\",\n\ttransform: {\n\t\t\"^.+\\\\.(t|j)sx?$\": [\"@swc-node/jest\"],\n\t},\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n\t\"name\": \"cmd-ts\",\n\t\"version\": \"0.15.0\",\n\t\"homepage\": \"https://cmd-ts.now.sh\",\n\t\"license\": \"MIT\",\n\t\"author\": \"Gal Schlezinger\",\n\t\"main\": \"dist/cjs/index.js\",\n\t\"typings\": \"dist/cjs/index.d.ts\",\n\t\"module\": \"dist/esm/index.js\",\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"https://github.com/Schniz/cmd-ts\"\n\t},\n\t\"files\": [\"dist\", \"batteries\"],\n\t\"scripts\": {\n\t\t\"build\": \"tsc && tsc --project ./tsconfig.esm.json\",\n\t\t\"lint\": \"biome check src test example\",\n\t\t\"now-build\": \"mdbook build --dest-dir=public\",\n\t\t\"start\": \"pnpm build --watch\",\n\t\t\"prepublishOnly\": \"rm -rf dist && pnpm build && pnpm test\",\n\t\t\"test\": \"vitest\",\n\t\t\"ts-node\": \"tsx\",\n\t\t\"changeset:version\": \"changeset version && pnpm install --no-frozen-lockfile\",\n\t\t\"changeset:publish\": \"pnpm run build && changeset publish\"\n\t},\n\t\"husky\": {\n\t\t\"hooks\": {\n\t\t\t\"pre-commit\": \"pnpm lint\"\n\t\t}\n\t},\n\t\"sideEffects\": false,\n\t\"prettier\": {\n\t\t\"printWidth\": 80,\n\t\t\"semi\": true,\n\t\t\"singleQuote\": true,\n\t\t\"trailingComma\": \"es5\"\n\t},\n\t\"dependencies\": {\n\t\t\"chalk\": \"^5.4.1\",\n\t\t\"debug\": \"^4.4.1\",\n\t\t\"didyoumean\": \"^1.2.2\",\n\t\t\"strip-ansi\": \"^7.1.0\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@biomejs/biome\": \"^1.9.4\",\n\t\t\"@changesets/cli\": \"2.29.4\",\n\t\t\"@types/debug\": \"4.1.12\",\n\t\t\"@types/didyoumean\": \"1.2.3\",\n\t\t\"@types/fs-extra\": \"11.0.4\",\n\t\t\"@types/node-fetch\": \"2.6.12\",\n\t\t\"@types/request\": \"2.48.12\",\n\t\t\"cargo-mdbook\": \"0.4.4\",\n\t\t\"docs-ts\": \"0.8.0\",\n\t\t\"execa\": \"9.6.0\",\n\t\t\"fs-extra\": \"11.3.0\",\n\t\t\"husky\": \"9.1.7\",\n\t\t\"infer-types\": \"0.0.2\",\n\t\t\"node-fetch\": \"3.3.2\",\n\t\t\"request\": \"2.88.2\",\n\t\t\"tempy\": \"3.1.0\",\n\t\t\"tsx\": \"^4.19.4\",\n\t\t\"typedoc\": \"0.28.5\",\n\t\t\"typescript\": \"5.8.3\",\n\t\t\"vitest\": \"3.2.4\"\n\t},\n\t\"packageManager\": \"pnpm@10.11.1\"\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n\t\"labels\": [\"PR: Dependency Update\"],\n\t\"packageRules\": [\n\t\t{\n\t\t\t\"packagePatterns\": [\"*\"],\n\t\t\t\"minor\": {\n\t\t\t\t\"groupName\": \"all non-major dependencies\",\n\t\t\t\t\"groupSlug\": \"all-minor-patch\"\n\t\t\t}\n\t\t}\n\t],\n\t\"extends\": [\"config:base\"]\n}\n"
  },
  {
    "path": "scripts/print-all-states.js",
    "content": "#!/usr/bin/env node\n\nconst chalk = require(\"chalk\");\nconst allSnapshots = require(\"../test/__snapshots__/ui.test.ts.snap\");\n\nfor (const [snapName, snapshot] of Object.entries(allSnapshots)) {\n\tconst snapNameWithoutNumber = snapName.match(/^(.+) \\d+$/)[1];\n\tconsole.log(chalk.bold.underline(snapNameWithoutNumber));\n\tconsole.log(\n\t\tsnapshot\n\t\t\t.trim()\n\t\t\t.slice(1, -1)\n\t\t\t.split(\"\\n\")\n\t\t\t.map((x) => `  ${x}`)\n\t\t\t.join(\"\\n\"),\n\t);\n\tconsole.log();\n\tconsole.log();\n}\n"
  },
  {
    "path": "scripts/ts-node",
    "content": "#!/bin/bash\n\nDIRECTORY=\"$(dirname \"$0\")\"\n\nSWC_NODE_PROJECT=$DIRECTORY/tsconfig.json node -r esm -r @swc-node/register \"$@\"\n"
  },
  {
    "path": "scripts/tsconfig.json",
    "content": "{\n\t\"extends\": \"../tsconfig.noEmit.json\",\n\t\"include\": [\"..\"],\n\t\"exclude\": [\"../dist\"]\n}\n"
  },
  {
    "path": "src/Result.ts",
    "content": "/**\n * A successful value\n */\nexport type Ok<R> = { _tag: \"ok\"; value: R };\n\n/**\n * A failed value\n */\nexport type Err<L> = { _tag: \"error\"; error: L };\n\n/**\n * A safe result type: imagine a language with no exceptions — the way to handle\n * errors would be to use something like a tagged union type.\n *\n * Why would we want that? I want to explicitly handle exceptions in this library\n * and having this construct really helps. It's also pretty easy to implement.\n */\nexport type Result<L, R> = Err<L> | Ok<R>;\n\nexport function ok<O>(value: O): Ok<O> {\n\treturn { _tag: \"ok\", value };\n}\n\nexport function err<E>(error: E): Err<E> {\n\treturn { _tag: \"error\", error };\n}\n\n/**\n * Checks whether a value is an `Ok`.\n * Handy with TypeScript guards\n */\nexport function isOk<R>(result: Result<any, R>): result is Ok<R> {\n\treturn result._tag === \"ok\";\n}\n\n/**\n * Checks whether a value is an `Err`.\n * Handy with TypeScript guards\n */\nexport function isErr<L>(either: Result<L, any>): either is Err<L> {\n\treturn either._tag === \"error\";\n}\n\n/**\n * Convert a `Promise<T>` into a `Promise<Result<Error, T>>`,\n * therefore catching the errors and being able to handle them explicitly\n */\nexport async function safeAsync<O>(\n\tpromise: Promise<O>,\n): Promise<Result<Error, O>> {\n\ttry {\n\t\tconst value = await promise;\n\t\treturn ok(value);\n\t} catch (e: any) {\n\t\treturn err(e);\n\t}\n}\n"
  },
  {
    "path": "src/argparser.ts",
    "content": "import type { Result } from \"./Result\";\nimport type { AstNode } from \"./newparser/parser\";\n\nexport type Nodes = AstNode[];\n\nexport type ParsingError = {\n\t/** Relevant nodes that should be shown with the message provided */\n\tnodes: AstNode[];\n\t/** Why did the parsing failed? */\n\tmessage: string;\n};\n\nexport type FailedParse = {\n\terrors: ParsingError[];\n\t/** The content that was parsed so far */\n\tpartialValue?: unknown;\n};\n\nexport type ParseContext = {\n\t/** The nodes we parsed */\n\tnodes: Nodes;\n\t/**\n\t * A set of nodes that were already visited. Helpful when writing a parser,\n\t * and wanting to skip all the nodes we've already used\n\t */\n\tvisitedNodes: Set<AstNode>;\n\t/** The command path breadcrumbs, to print when asking for help */\n\thotPath?: string[];\n};\n\nexport type ParsingResult<Into> = Result<FailedParse, Into>;\n\n/**\n * A weird thing about command line interfaces is that they are not consistent without some context.\n * Consider the following argument list: `my-app --server start`\n *\n * Should we parse it as `[positional my-app] [option --server start]`\n * or should we parse it as `[positional my-app] [flag --server] [positional start]`?\n *\n * The answer is — it depends. A good command line utility has the context to know which key is a flag\n * and which is an option that can take a value. We aim to be a good command line utility library, so\n * we need to have the ability to provide this context.\n *\n * This is the small object that has this context.\n */\nexport type RegisterOptions = {\n\tforceFlagLongNames: Set<string>;\n\tforceFlagShortNames: Set<string>;\n\tforceOptionLongNames: Set<string>;\n\tforceOptionShortNames: Set<string>;\n};\n\nexport type Register = {\n\t/**\n\t * Inform the parser with context before parsing.\n\t * Right now, only used to force flags in the parser.\n\t */\n\tregister(opts: RegisterOptions): void;\n};\n\nexport type ArgParser<Into> = Partial<Register> & {\n\t/**\n\t * Parse from AST nodes into the value provided in [[Into]].\n\t *\n\t * @param context The parsing context\n\t */\n\tparse(context: ParseContext): Promise<ParsingResult<Into>>;\n};\n\nexport type ParsingInto<AP extends ArgParser<any>> = AP extends ArgParser<\n\tinfer R\n>\n\t? R\n\t: never;\n"
  },
  {
    "path": "src/batteries/fs.ts",
    "content": "import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { extendType, string } from \"..\";\n\n/**\n * Resolves an existing path. Produces an error when path does not exist.\n * When provided a relative path, extends it using the current working directory.\n */\nexport const ExistingPath = extendType(string, {\n\tdisplayName: \"path\",\n\tdescription: \"An existing path\",\n\tasync from(str) {\n\t\tconst resolved = path.resolve(str);\n\n\t\tif (!fs.existsSync(resolved)) {\n\t\t\tthrow new Error(\"Path doesn't exist\");\n\t\t}\n\n\t\treturn resolved;\n\t},\n});\n\n/**\n * Resolves to a directory if given one, and to a file's directory if file was given.\n * Fails when the directory or the given file does not exist.\n */\nexport const Directory = extendType(ExistingPath, {\n\tasync from(resolved) {\n\t\tconst stat = fs.statSync(resolved);\n\n\t\tif (stat.isDirectory()) {\n\t\t\treturn resolved;\n\t\t}\n\n\t\treturn path.dirname(resolved);\n\t},\n\tdisplayName: \"dir\",\n\tdescription: \"A path to a directory or a file within a directory\",\n});\n\n/**\n * Resolves to a path to an existing file\n */\nexport const File = extendType(ExistingPath, {\n\tasync from(resolved) {\n\t\tconst stat = fs.statSync(resolved);\n\n\t\tif (stat.isFile()) {\n\t\t\treturn resolved;\n\t\t}\n\n\t\tthrow new Error(\"Provided path is not a file\");\n\t},\n\tdisplayName: \"file\",\n\tdescription: \"A file in the file system\",\n});\n"
  },
  {
    "path": "src/batteries/url.ts",
    "content": "import { URL } from \"node:url\";\nimport { extendType, string } from \"..\";\n\n/**\n * Decodes a string into the `URL` type\n */\nexport const Url = extendType(string, {\n\tdisplayName: \"url\",\n\tdescription: \"A valid URL\",\n\tasync from(str): Promise<URL> {\n\t\tconst url = new URL(str);\n\t\tif (!url.protocol || !url.host) {\n\t\t\tthrow new Error(\"Malformed URL\");\n\t\t}\n\n\t\tif (![\"http:\", \"https:\"].includes(url.protocol as string)) {\n\t\t\tthrow new Error(\"Only allowed http and https URLs\");\n\t\t}\n\n\t\treturn url;\n\t},\n});\n\n/**\n * Decodes an http/https only URL\n */\nexport const HttpUrl = extendType(Url, {\n\tasync from(url): Promise<URL> {\n\t\tif (![\"http:\", \"https:\"].includes(url.protocol as string)) {\n\t\t\tthrow new Error(\"Only allowed http and https URLs\");\n\t\t}\n\n\t\treturn url;\n\t},\n});\n"
  },
  {
    "path": "src/batteries/vercel-formatter.ts",
    "content": "import chalk from \"chalk\";\nimport type { ParseContext } from \"../argparser\";\nimport type {\n\tCommandHelpData,\n\tHelpFormatter,\n\tSubcommandsHelpData,\n} from \"../helpFormatter\";\n\n/**\n * Configuration for the Vercel-style help formatter\n */\nexport type VercelFormatterConfig = {\n\t/**\n\t * The CLI name to display in the header (e.g., \"Vercel Sandbox CLI\")\n\t */\n\tcliName?: string;\n\t/**\n\t * Logo/symbol to display before the command name (e.g., \"▲\")\n\t */\n\tlogo?: string;\n};\n\n/**\n * Extract argument hints from help topics.\n * Returns a string like \"[cmd]\" or \"[src] [dst]\" based on positional arguments.\n */\nfunction getArgHint(helpTopics: { category: string; usage: string }[]): string {\n\treturn helpTopics\n\t\t.filter((t) => t.category === \"arguments\")\n\t\t.map((t) => t.usage)\n\t\t.join(\" \");\n}\n\n/**\n * Format command name with aliases in \"alias | name\" style.\n * Prefers shorter alias first for display.\n */\nfunction formatCommandName(name: string, aliases?: string[]): string {\n\tif (!aliases?.length) {\n\t\treturn name;\n\t}\n\t// Sort aliases by length to prefer shorter ones first\n\tconst sortedAliases = [...aliases].sort((a, b) => a.length - b.length);\n\treturn `${sortedAliases[0]} | ${name}`;\n}\n\n/**\n * Create a Vercel-style help formatter.\n *\n * This formatter produces output similar to the Vercel CLI with:\n * - A header with CLI name and version\n * - Logo symbol before the command name\n * - Column-aligned commands with aliases shown as \"short | long\"\n * - Argument hints derived from positional arguments\n * - Examples section with highlighted commands\n *\n * @example\n * ```ts\n * import { setDefaultHelpFormatter } from \"cmd-ts\";\n * import { createVercelFormatter } from \"cmd-ts/batteries/vercelFormatter\";\n *\n * setDefaultHelpFormatter(createVercelFormatter({\n *   cliName: \"Vercel Sandbox CLI\",\n *   logo: \"▲\",\n * }));\n * ```\n */\nexport function createVercelFormatter(\n\tconfig: VercelFormatterConfig = {},\n): HelpFormatter {\n\tconst { cliName, logo = \"▲\" } = config;\n\n\treturn {\n\t\tformatCommand(data: CommandHelpData, _context: ParseContext): string {\n\t\t\tconst lines: string[] = [];\n\t\t\tconst displayName = cliName ?? data.name;\n\n\t\t\tlet header = displayName;\n\t\t\tif (data.version) {\n\t\t\t\theader += ` ${data.version}`;\n\t\t\t}\n\t\t\tlines.push(chalk.grey(header));\n\n\t\t\t// Command usage line\n\t\t\tlines.push(\"\");\n\t\t\tconst path = data.path.length > 0 ? data.path.join(\" \") : data.name;\n\t\t\tlines.push(`${logo} ${chalk.bold(path)} [options]`);\n\n\t\t\t// Description\n\t\t\tif (data.description) {\n\t\t\t\tlines.push(\"\");\n\t\t\t\tlines.push(chalk.dim(data.description));\n\t\t\t}\n\n\t\t\t// Group help topics by category\n\t\t\tconst byCategory = new Map<string, typeof data.helpTopics>();\n\t\t\tfor (const topic of data.helpTopics) {\n\t\t\t\tconst existing = byCategory.get(topic.category) ?? [];\n\t\t\t\texisting.push(topic);\n\t\t\t\tbyCategory.set(topic.category, existing);\n\t\t\t}\n\n\t\t\t// Render each category\n\t\t\tfor (const [category, topics] of byCategory) {\n\t\t\t\tlines.push(\"\");\n\t\t\t\tlines.push(chalk.dim(`${capitalize(category)}:`));\n\t\t\t\tlines.push(\"\");\n\n\t\t\t\t// Calculate max width for alignment\n\t\t\t\tconst maxUsageWidth = Math.max(...topics.map((t) => t.usage.length));\n\n\t\t\t\tfor (const topic of topics) {\n\t\t\t\t\tconst defaults =\n\t\t\t\t\t\ttopic.defaults.length > 0\n\t\t\t\t\t\t\t? chalk.dim(` [${topic.defaults.join(\", \")}]`)\n\t\t\t\t\t\t\t: \"\";\n\t\t\t\t\tlines.push(\n\t\t\t\t\t\t`    ${topic.usage.padEnd(maxUsageWidth + 2)}${chalk.dim(topic.description)}${defaults}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Examples\n\t\t\tif (data.examples && data.examples.length > 0) {\n\t\t\t\tlines.push(\"\");\n\t\t\t\tlines.push(chalk.dim(\"Examples:\"));\n\n\t\t\t\tfor (const example of data.examples) {\n\t\t\t\t\tlines.push(\"\");\n\t\t\t\t\tlines.push(`${chalk.gray(\"–\")} ${example.description}`);\n\t\t\t\t\tlines.push(\"\");\n\t\t\t\t\tlines.push(chalk.cyan(`  $ ${example.command}`));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlines.push(\"\");\n\t\t\treturn lines.join(\"\\n\");\n\t\t},\n\n\t\tformatSubcommands(\n\t\t\tdata: SubcommandsHelpData,\n\t\t\t_context: ParseContext,\n\t\t): string {\n\t\t\tconst lines: string[] = [];\n\t\t\tconst path = data.path.length > 0 ? data.path.join(\" \") : data.name;\n\t\t\tconst displayName = cliName ?? path;\n\n\t\t\t// Header\n\t\t\tif (data.version) {\n\t\t\t\tlines.push(chalk.grey(`${displayName} ${data.version}`));\n\t\t\t} else {\n\t\t\t\tlines.push(chalk.grey(displayName));\n\t\t\t}\n\n\t\t\t// Command usage line\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(`${logo} ${chalk.bold(path)} [options] <command>`);\n\n\t\t\t// Help hint\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\n\t\t\t\tchalk.dim(`For command help, run \\`${path} <command> --help\\``),\n\t\t\t);\n\n\t\t\t// Commands section\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(chalk.dim(\"Commands:\"));\n\t\t\tlines.push(\"\");\n\n\t\t\t// Calculate column widths\n\t\t\tconst commandNames = data.commands.map((cmd) =>\n\t\t\t\tformatCommandName(cmd.name, cmd.aliases),\n\t\t\t);\n\t\t\tconst maxNameWidth = Math.max(...commandNames.map((n) => n.length));\n\t\t\tconst argHints = data.commands.map((cmd) => getArgHint(cmd.helpTopics));\n\t\t\tconst maxArgWidth = Math.max(...argHints.map((a) => a.length), 0);\n\n\t\t\t// Render commands\n\t\t\tfor (let i = 0; i < data.commands.length; i++) {\n\t\t\t\tconst cmd = data.commands[i];\n\t\t\t\tconst displayCommandName = commandNames[i];\n\t\t\t\tconst argHint = argHints[i];\n\n\t\t\t\tlines.push(\n\t\t\t\t\t`    ${displayCommandName.padEnd(maxNameWidth + 2)}${chalk.dim(argHint.padEnd(maxArgWidth + 2))}${chalk.dim(cmd.description ?? \"\")}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Examples\n\t\t\tif (data.examples && data.examples.length > 0) {\n\t\t\t\tlines.push(\"\");\n\t\t\t\tlines.push(chalk.dim(\"Examples:\"));\n\n\t\t\t\tfor (const example of data.examples) {\n\t\t\t\t\tlines.push(\"\");\n\t\t\t\t\tlines.push(`${chalk.gray(\"–\")} ${example.description}`);\n\t\t\t\t\tlines.push(\"\");\n\t\t\t\t\tlines.push(chalk.cyan(`  $ ${example.command}`));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlines.push(\"\");\n\t\t\treturn lines.join(\"\\n\");\n\t\t},\n\t};\n}\n\nfunction capitalize(str: string): string {\n\treturn str.charAt(0).toUpperCase() + str.slice(1);\n}\n\n/**\n * A pre-configured Vercel-style formatter with default settings.\n * Uses \"▲\" as the logo and derives the CLI name from the command name.\n */\nexport const vercelFormatter = createVercelFormatter();\n"
  },
  {
    "path": "src/binary.ts",
    "content": "import type { ParseContext } from \"./argparser\";\nimport type { Named } from \"./helpdoc\";\nimport type { Runner } from \"./runner\";\n\n/**\n * A small helper to easily use `process.argv` without dropping context\n *\n * @param cmd a command line parser\n */\nexport function binary<Command extends Runner<any, any> & Named>(\n\tcmd: Command,\n): Command {\n\treturn {\n\t\t...cmd,\n\t\trun(context: ParseContext) {\n\t\t\tconst name = cmd.name || context.nodes[1].raw;\n\t\t\tcontext.hotPath?.push(name);\n\t\t\tcontext.nodes.splice(0, 1);\n\t\t\tcontext.nodes[0].raw = name;\n\t\t\tcontext.visitedNodes.add(context.nodes[0]);\n\t\t\treturn cmd.run(context);\n\t\t},\n\t};\n}\n"
  },
  {
    "path": "src/chalk.ts",
    "content": "import chalk, { type ChalkInstance } from \"chalk\";\n\nlet mode: \"chalk\" | \"tags\" | \"disabled\" = \"chalk\";\n\nexport function setMode(newMode: typeof mode) {\n\tmode = newMode;\n}\n\ntype ColorizerFunction = (...strings: string[]) => string;\n\ntype AllColorOptions = Extract<\n\t(typeof allColors)[keyof typeof allColors],\n\tstring\n>;\n\ntype Colored = {\n\t[key in AllColorOptions]: ColoredFunction;\n};\ntype ColoredFunction = ColorizerFunction & Colored;\n\ntype Strategy = (\n\tlevels: AllColorOptions[],\n\tstr: string,\n) => ReturnType<ColorizerFunction>;\n\nfunction withLevels(\n\tlevels: AllColorOptions[],\n\tstrategy: Strategy,\n): ColoredFunction {\n\tconst fn: ColorizerFunction = (str) => strategy(levels, str);\n\tObject.assign(fn, generateColoredBody(fn, levels, strategy));\n\treturn fn as any;\n}\n\nconst allColors = [\n\t\"black\",\n\t\"red\",\n\t\"green\",\n\t\"yellow\",\n\t\"blue\",\n\t\"magenta\",\n\t\"cyan\",\n\t\"white\",\n\t\"gray\",\n\t\"grey\",\n\t\"blackBright\",\n\t\"redBright\",\n\t\"greenBright\",\n\t\"yellowBright\",\n\t\"blueBright\",\n\t\"magentaBright\",\n\t\"cyanBright\",\n\t\"whiteBright\",\n\t\"italic\",\n\t\"bold\",\n\t\"underline\",\n] as const;\n\nfunction generateColoredBody<T>(\n\twrapper: T,\n\tlevels: AllColorOptions[],\n\tstrategy: Strategy,\n): T & Colored {\n\tconst c = allColors.reduce(\n\t\t(acc, curr) => {\n\t\t\tObject.defineProperty(acc, curr, {\n\t\t\t\tget() {\n\t\t\t\t\tconst x = withLevels([...levels, curr], strategy);\n\t\t\t\t\treturn x;\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn acc;\n\t\t},\n\t\twrapper as T & Colored,\n\t);\n\treturn c;\n}\n\nconst chalked = generateColoredBody({}, [], (levels, str) => {\n\tconst color = levels.reduce<ChalkInstance>((c, curr) => c[curr], chalk);\n\treturn color(str);\n});\n\nconst tagged = generateColoredBody({}, [], (levels, str) => {\n\tconst [start, end] = levels.reduce(\n\t\t(acc, curr) => {\n\t\t\tacc[0] += `<${curr}>`;\n\t\t\tacc[1] = `</${curr}>${acc[1]}`;\n\t\t\treturn acc;\n\t\t},\n\t\t[\"\", \"\"],\n\t);\n\treturn `${start}${str}${end}`;\n});\n\nconst disabled = generateColoredBody({}, [], (_levels, str) => str);\n\nexport function colored(): Colored {\n\tif (mode === \"chalk\") return chalked;\n\tif (mode === \"disabled\") return disabled;\n\treturn tagged;\n}\n\nsetMode(\"tags\");\n\nconsole.log(colored().red.bold(\"hello\"));\n\nsetMode(\"chalk\");\n\nconsole.log(colored().red.bold(\"hello\"));\n"
  },
  {
    "path": "src/circuitbreaker.ts",
    "content": "import * as Result from \"./Result\";\nimport type { ArgParser, ParseContext, Register } from \"./argparser\";\nimport { Exit } from \"./effects\";\nimport { flag } from \"./flag\";\nimport type { PrintHelp, ProvidesHelp, Versioned } from \"./helpdoc\";\nimport { boolean } from \"./types\";\n\ntype CircuitBreaker = \"help\" | \"version\";\n\nexport const helpFlag = flag({\n\tlong: \"help\",\n\tshort: \"h\",\n\ttype: boolean,\n\tdescription: \"show help\",\n});\n\nexport const versionFlag = flag({\n\tlong: \"version\",\n\tshort: \"v\",\n\ttype: boolean,\n\tdescription: \"print the version\",\n});\n\nexport function handleCircuitBreaker(\n\tcontext: ParseContext,\n\tvalue: PrintHelp & Partial<Versioned>,\n\tbreaker: Result.Result<any, CircuitBreaker>,\n): void {\n\tif (Result.isErr(breaker)) {\n\t\treturn;\n\t}\n\n\tif (breaker.value === \"help\") {\n\t\tconst message = value.printHelp(context);\n\t\tthrow new Exit({ exitCode: 0, message, into: \"stdout\" });\n\t}\n\tif (breaker.value === \"version\") {\n\t\tconst message = value.version || \"0.0.0\";\n\t\tthrow new Exit({ exitCode: 0, message, into: \"stdout\" });\n\t}\n}\n\n/**\n * Helper flags that are being used in `command` and `subcommands`:\n * `--help, -h` to show help\n * `--version, -v` to show the current version\n *\n * It is called circuitbreaker because if you have `--help` or `--version`\n * anywhere in your argument list, you'll see the version and the help for the closest command\n */\nexport function createCircuitBreaker(\n\twithVersion: boolean,\n): ArgParser<CircuitBreaker> & ProvidesHelp & Register {\n\treturn {\n\t\tregister(opts) {\n\t\t\thelpFlag.register(opts);\n\t\t\tif (withVersion) {\n\t\t\t\tversionFlag.register(opts);\n\t\t\t}\n\t\t},\n\t\thelpTopics() {\n\t\t\tconst helpTopics = helpFlag.helpTopics();\n\t\t\tif (withVersion) {\n\t\t\t\thelpTopics.push(...versionFlag.helpTopics());\n\t\t\t}\n\t\t\treturn helpTopics;\n\t\t},\n\t\tasync parse(context) {\n\t\t\tconst help = await helpFlag.parse(context);\n\t\t\tconst version = withVersion\n\t\t\t\t? await versionFlag.parse(context)\n\t\t\t\t: undefined;\n\n\t\t\tif (Result.isErr(help) || (version && Result.isErr(version))) {\n\t\t\t\tconst helpErrors = Result.isErr(help) ? help.error.errors : [];\n\t\t\t\tconst versionErrors =\n\t\t\t\t\tversion && Result.isErr(version) ? version.error.errors : [];\n\t\t\t\treturn Result.err({ errors: [...helpErrors, ...versionErrors] });\n\t\t\t}\n\n\t\t\tif (help.value) {\n\t\t\t\treturn Result.ok(\"help\");\n\t\t\t}\n\t\t\tif (version?.value) {\n\t\t\t\treturn Result.ok(\"version\");\n\t\t\t}\n\t\t\treturn Result.err({\n\t\t\t\terrors: [\n\t\t\t\t\t{\n\t\t\t\t\t\tnodes: [],\n\t\t\t\t\t\tmessage: \"Neither help nor version\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\t\t},\n\t};\n}\n"
  },
  {
    "path": "src/command.ts",
    "content": "import * as Result from \"./Result\";\nimport type {\n\tArgParser,\n\tParseContext,\n\tParsingError,\n\tParsingInto,\n\tParsingResult,\n} from \"./argparser\";\nimport { createCircuitBreaker, handleCircuitBreaker } from \"./circuitbreaker\";\nimport {\n\ttype CommandHelpData,\n\ttype Example,\n\tgetHelpFormatter,\n} from \"./helpFormatter\";\nimport type {\n\tAliased,\n\tDescriptive,\n\tNamed,\n\tPrintHelp,\n\tProvidesHelp,\n\tVersioned,\n} from \"./helpdoc\";\nimport type { AstNode } from \"./newparser/parser\";\nimport type { Runner } from \"./runner\";\nimport { entries, flatMap } from \"./utils\";\n\ntype ArgTypes = Record<string, ArgParser<any> & Partial<ProvidesHelp>>;\ntype HandlerFunc<Args extends ArgTypes> = (args: Output<Args>) => any;\n\ntype CommandConfig<\n\tArguments extends ArgTypes,\n\tHandler extends HandlerFunc<Arguments>,\n> = {\n\targs: Arguments;\n\tversion?: string;\n\tname: string;\n\tdescription?: string;\n\thandler: Handler;\n\taliases?: string[];\n\t/** Examples to show in help output */\n\texamples?: Example[];\n};\n\ntype Output<Args extends ArgTypes> = {\n\t[key in keyof Args]: ParsingInto<Args[key]>;\n};\n\n/**\n * A command line utility.\n *\n * A combination of multiple flags, options and arguments\n * with a common name and a handler that expects them as input.\n */\nexport function command<\n\tArguments extends ArgTypes,\n\tHandler extends HandlerFunc<Arguments>,\n>(\n\tconfig: CommandConfig<Arguments, Handler>,\n): ArgParser<Output<Arguments>> &\n\tPrintHelp &\n\tProvidesHelp &\n\tNamed &\n\tRunner<Output<Arguments>, ReturnType<Handler>> &\n\tPartial<Versioned & Descriptive & Aliased> {\n\tconst argEntries = entries(config.args);\n\tconst circuitbreaker = createCircuitBreaker(!!config.version);\n\n\treturn {\n\t\tname: config.name,\n\t\taliases: config.aliases,\n\t\thandler: config.handler,\n\t\tdescription: config.description,\n\t\tversion: config.version,\n\t\thelpTopics() {\n\t\t\treturn flatMap(\n\t\t\t\tObject.values(config.args).concat([circuitbreaker]),\n\t\t\t\t(x) => x.helpTopics?.() ?? [],\n\t\t\t);\n\t\t},\n\t\tprintHelp(context) {\n\t\t\tconst data: CommandHelpData = {\n\t\t\t\tname: config.name,\n\t\t\t\tpath: context.hotPath ?? [config.name],\n\t\t\t\tversion: config.version,\n\t\t\t\tdescription: config.description,\n\t\t\t\taliases: config.aliases,\n\t\t\t\thelpTopics: this.helpTopics(),\n\t\t\t\texamples: config.examples,\n\t\t\t};\n\t\t\treturn getHelpFormatter().formatCommand(data, context);\n\t\t},\n\t\tregister(opts) {\n\t\t\tfor (const [, arg] of argEntries) {\n\t\t\t\targ.register?.(opts);\n\t\t\t}\n\t\t},\n\t\tasync parse(\n\t\t\tcontext: ParseContext,\n\t\t): Promise<ParsingResult<Output<Arguments>>> {\n\t\t\tif (context.hotPath?.length === 0) {\n\t\t\t\tcontext.hotPath.push(config.name);\n\t\t\t}\n\n\t\t\tconst resultObject = {} as Output<Arguments>;\n\t\t\tconst errors: ParsingError[] = [];\n\n\t\t\tfor (const [argName, arg] of argEntries) {\n\t\t\t\tconst result = await arg.parse(context);\n\t\t\t\tif (Result.isErr(result)) {\n\t\t\t\t\terrors.push(...result.error.errors);\n\t\t\t\t} else {\n\t\t\t\t\tresultObject[argName] = result.value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst unknownArguments: AstNode[] = [];\n\t\t\tfor (const node of context.nodes) {\n\t\t\t\tif (context.visitedNodes.has(node)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (node.type === \"forcePositional\") {\n\t\t\t\t} else if (node.type === \"shortOptions\") {\n\t\t\t\t\tfor (const option of node.options) {\n\t\t\t\t\t\tif (context.visitedNodes.has(option)) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tunknownArguments.push(option);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tunknownArguments.push(node);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (unknownArguments.length > 0) {\n\t\t\t\terrors.push({\n\t\t\t\t\tmessage: \"Unknown arguments\",\n\t\t\t\t\tnodes: unknownArguments,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (errors.length > 0) {\n\t\t\t\treturn Result.err({\n\t\t\t\t\terrors: errors,\n\t\t\t\t\tpartialValue: resultObject,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn Result.ok(resultObject);\n\t\t},\n\t\tasync run(context) {\n\t\t\tconst breaker = await circuitbreaker.parse(context);\n\t\t\thandleCircuitBreaker(context, this, breaker);\n\t\t\tconst parsed = await this.parse(context);\n\n\t\t\tif (Result.isErr(parsed)) {\n\t\t\t\treturn Result.err(parsed.error);\n\t\t\t}\n\n\t\t\treturn Result.ok(await this.handler(parsed.value));\n\t\t},\n\t};\n}\n"
  },
  {
    "path": "src/default.ts",
    "content": "export type Default<T> = {\n\t/**\n\t * A default value to be provided when a value is missing.\n\t * i.e., `string | null` should probably return `null`.\n\t * This should be synchronous for fast help generation.\n\t */\n\tdefaultValue(): T;\n\tdefaultValueIsSerializable?: boolean;\n};\n\nexport type OnMissing<T> = {\n\t/**\n\t * A callback that will be executed when the value is missing from user input.\n\t * This could be used for interactive prompts, reading from config files,\n\t * API calls, or any dynamic fallback. Only executed during actual parsing,\n\t * never during help generation.\n\t */\n\tonMissing(): T | Promise<T>;\n};\n"
  },
  {
    "path": "src/effects.ts",
    "content": "/**\n * \"Effects\" are custom exceptions that can do stuff.\n * The concept comes from React, where they throw a `Promise` to provide the ability to write\n * async code with synchronous syntax.\n *\n * These effects _should stay an implementation detail_ and not leak out of the library.\n *\n * @packageDocumentation\n */\n\nimport chalk from \"chalk\";\n\n/**\n * An effect to exit the program with a message\n *\n * **Why this is an effect?**\n *\n * Using `process.exit` in a program is both a problem:\n * * in tests, because it needs to be mocked somehow\n * * in browser usage, because it does not have `process` at all\n *\n * Also, using `console.log` is something we'd rather avoid and return strings, and if returning strings\n * would be harmful for performance we might ask for a stream to write to:\n * Printing to stdout and stderr means that we don't control the values and it ties us to only use `cmd-ts`\n * with a command line, and again, to mock `stdout` and `stderr` it if we want to test it.\n */\nexport class Exit {\n\tconstructor(\n\t\tpublic readonly config: {\n\t\t\texitCode: number;\n\t\t\tmessage: string;\n\t\t\tinto: \"stdout\" | \"stderr\";\n\t\t},\n\t) {}\n\n\trun(): never {\n\t\tconst output = this.output();\n\t\toutput(this.config.message);\n\t\tprocess.exit(this.config.exitCode);\n\t}\n\n\tdryRun(): string {\n\t\tconst { into, message, exitCode } = this.config;\n\t\tconst coloredExit = chalk.dim(\n\t\t\t`process exited with status ${exitCode} (${into})`,\n\t\t);\n\t\treturn `${message}\\n\\n${coloredExit}`;\n\t}\n\n\tprivate output() {\n\t\tif (this.config.into === \"stderr\") {\n\t\t\treturn console.error;\n\t\t}\n\t\treturn console.log;\n\t}\n}\n"
  },
  {
    "path": "src/errorBox.ts",
    "content": "import chalk from \"chalk\";\nimport stripAnsi from \"strip-ansi\";\nimport type { ParsingError } from \"./argparser\";\nimport type { AstNode } from \"./newparser/parser\";\nimport { enumerate, padNoAnsi } from \"./utils\";\n\ntype HighlightResult = { colorized: string; errorIndex: number };\n\n/**\n * Get the input as highlighted keywords to show to the user\n * with the error that was generated from parsing the input.\n *\n * @param nodes AST nodes\n * @param error A parsing error\n */\nfunction highlight(\n\tnodes: AstNode[],\n\terror: ParsingError,\n): HighlightResult | undefined {\n\tconst strings: string[] = [];\n\tlet errorIndex: undefined | number = undefined;\n\n\tfunction foundError() {\n\t\tif (errorIndex !== undefined) return;\n\t\terrorIndex = stripAnsi(strings.join(\" \")).length;\n\t}\n\n\tif (error.nodes.length === 0) return;\n\n\tnodes.forEach((node) => {\n\t\tif (error.nodes.includes(node)) {\n\t\t\tfoundError();\n\t\t\treturn strings.push(chalk.red(node.raw));\n\t\t}\n\t\tif (node.type === \"shortOptions\") {\n\t\t\tlet failed = false;\n\t\t\tlet s = \"\";\n\t\t\tfor (const option of node.options) {\n\t\t\t\tif (error.nodes.includes(option)) {\n\t\t\t\t\ts += chalk.red(option.raw);\n\t\t\t\t\tfailed = true;\n\t\t\t\t} else {\n\t\t\t\t\ts += chalk.dim(option.raw);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst prefix = failed ? chalk.red(\"-\") : chalk.dim(\"-\");\n\t\t\tif (failed) {\n\t\t\t\tfoundError();\n\t\t\t}\n\t\t\treturn strings.push(prefix + s);\n\t\t}\n\n\t\treturn strings.push(chalk.dim(node.raw));\n\t});\n\n\treturn { colorized: strings.join(\" \"), errorIndex: errorIndex ?? 0 };\n}\n\n/**\n * An error UI\n *\n * @param breadcrumbs The command breadcrumbs to print with the error\n */\nexport function errorBox(\n\tnodes: AstNode[],\n\terrors: ParsingError[],\n\tbreadcrumbs: string[],\n): string {\n\tconst withHighlight: { message: string; highlighted?: HighlightResult }[] =\n\t\t[];\n\n\tconst errorMessages: string[] = [];\n\n\tfor (const error of errors) {\n\t\tconst highlighted = highlight(nodes, error);\n\t\twithHighlight.push({ message: error.message, highlighted });\n\t}\n\n\tlet number = 1;\n\tconst maxNumberWidth = String(withHighlight.length).length;\n\n\terrorMessages.push(\n\t\t`${chalk.red.bold(\"error: \")}found ${chalk.yellow(withHighlight.length)} error${withHighlight.length > 1 ? \"s\" : \"\"}`,\n\t);\n\terrorMessages.push(\"\");\n\n\twithHighlight\n\t\t.filter((x) => x.highlighted)\n\t\t.forEach((x) => {\n\t\t\tif (!x.highlighted) {\n\t\t\t\tthrow new Error(\"WELP\");\n\t\t\t}\n\n\t\t\tconst pad = \"\".padStart(x.highlighted.errorIndex);\n\n\t\t\terrorMessages.push(`  ${x.highlighted.colorized}`);\n\t\t\tfor (const [index, line] of enumerate(x.message.split(\"\\n\"))) {\n\t\t\t\tconst prefix = index === 0 ? chalk.bold(\"^\") : \" \";\n\t\t\t\tconst msg = chalk.red(`  ${pad} ${prefix} ${line}`);\n\t\t\t\terrorMessages.push(msg);\n\t\t\t}\n\t\t\terrorMessages.push(\"\");\n\t\t\tnumber++;\n\t\t});\n\n\tconst withNoHighlight = withHighlight.filter((x) => !x.highlighted);\n\n\tif (number > 1) {\n\t\tif (withNoHighlight.length === 1) {\n\t\t\terrorMessages.push(\"Along with the following error:\");\n\t\t} else if (withNoHighlight.length > 1) {\n\t\t\terrorMessages.push(\"Along with the following errors:\");\n\t\t}\n\t}\n\n\twithNoHighlight.forEach(({ message }) => {\n\t\tconst num = chalk.red.bold(\n\t\t\t`${padNoAnsi(number.toString(), maxNumberWidth, \"start\")}.`,\n\t\t);\n\t\tconst lines = message.split(\"\\n\");\n\t\terrorMessages.push(`  ${num} ${chalk.red(lines[0] ?? \"\")}`);\n\t\tfor (const line of lines.slice(1)) {\n\t\t\terrorMessages.push(\n\t\t\t\t` ${\"\".padStart(maxNumberWidth + 1)}  ${chalk.red(line)}`,\n\t\t\t);\n\t\t}\n\t\tnumber++;\n\t});\n\n\tconst helpCmd = chalk.yellow(`${breadcrumbs.join(\" \")} --help`);\n\n\terrorMessages.push(\"\");\n\terrorMessages.push(\n\t\t`${chalk.red.bold(\"hint: \")}for more information, try '${helpCmd}'`,\n\t);\n\n\treturn errorMessages.join(\"\\n\");\n}\n"
  },
  {
    "path": "src/flag.ts",
    "content": "import chalk from \"chalk\";\nimport * as Result from \"./Result\";\nimport type {\n\tArgParser,\n\tParseContext,\n\tParsingResult,\n\tRegister,\n} from \"./argparser\";\nimport type { Default, OnMissing } from \"./default\";\nimport type {\n\tDescriptive,\n\tEnvDoc,\n\tLongDoc,\n\tProvidesHelp,\n\tShortDoc,\n} from \"./helpdoc\";\nimport { findOption } from \"./newparser/findOption\";\nimport { type HasType, type OutputOf, type Type, extendType } from \"./type\";\nimport { boolean as booleanIdentity } from \"./types\";\nimport type { AllOrNothing } from \"./utils\";\n\ntype FlagConfig<Decoder extends Type<boolean, any>> = LongDoc &\n\tHasType<Decoder> &\n\tPartial<ShortDoc & Descriptive & EnvDoc & OnMissing<OutputOf<Decoder>>> &\n\tAllOrNothing<Default<OutputOf<Decoder>>>;\n\n/**\n * A decoder from `string` to `boolean`\n * works for `true` and `false` only.\n */\nexport const boolean: Type<string, boolean> = {\n\tasync from(str) {\n\t\tif (str === \"true\") return true;\n\t\tif (str === \"false\") return false;\n\t\tthrow new Error(\n\t\t\t`expected value to be either \"true\" or \"false\". got: \"${str}\"`,\n\t\t);\n\t},\n\tdisplayName: \"true/false\",\n\tdefaultValue: () => false,\n};\n\nexport function fullFlag<Decoder extends Type<boolean, any>>(\n\tconfig: FlagConfig<Decoder>,\n): ArgParser<OutputOf<Decoder>> &\n\tProvidesHelp &\n\tRegister &\n\tPartial<Descriptive> {\n\tconst decoder = extendType(boolean, config.type);\n\n\treturn {\n\t\tdescription: config.description ?? config.type.description,\n\t\thelpTopics() {\n\t\t\tlet usage = `--${config.long}`;\n\t\t\tif (config.short) {\n\t\t\t\tusage += `, -${config.short}`;\n\t\t\t}\n\t\t\tconst defaults: string[] = [];\n\n\t\t\tif (config.env) {\n\t\t\t\tconst env =\n\t\t\t\t\tprocess.env[config.env] === undefined\n\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t: `=${chalk.italic(process.env[config.env])}`;\n\t\t\t\tdefaults.push(`env: ${config.env}${env}`);\n\t\t\t}\n\n\t\t\tif (config.defaultValue) {\n\t\t\t\ttry {\n\t\t\t\t\tconst defaultValue = config.defaultValue();\n\t\t\t\t\tif (config.defaultValueIsSerializable) {\n\t\t\t\t\t\tdefaults.push(`default: ${chalk.italic(defaultValue)}`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdefaults.push(\"optional\");\n\t\t\t\t\t}\n\t\t\t\t} catch (e) {}\n\t\t\t} else if (config.type.defaultValue) {\n\t\t\t\ttry {\n\t\t\t\t\tconst defaultValue = config.type.defaultValue();\n\t\t\t\t\tif (config.type.defaultValueIsSerializable) {\n\t\t\t\t\t\tdefaults.push(`default: ${chalk.italic(defaultValue)}`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdefaults.push(\"optional\");\n\t\t\t\t\t}\n\t\t\t\t} catch (e) {}\n\t\t\t} else if (config.onMissing || config.type.onMissing) {\n\t\t\t\tdefaults.push(\"optional\");\n\t\t\t}\n\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tcategory: \"flags\",\n\t\t\t\t\tusage,\n\t\t\t\t\tdefaults,\n\t\t\t\t\tdescription:\n\t\t\t\t\t\tconfig.description ?? config.type.description ?? \"self explanatory\",\n\t\t\t\t},\n\t\t\t];\n\t\t},\n\t\tregister(opts) {\n\t\t\topts.forceFlagLongNames.add(config.long);\n\t\t\tif (config.short) {\n\t\t\t\topts.forceFlagShortNames.add(config.short);\n\t\t\t}\n\t\t},\n\t\tasync parse({\n\t\t\tnodes,\n\t\t\tvisitedNodes,\n\t\t}: ParseContext): Promise<ParsingResult<OutputOf<Decoder>>> {\n\t\t\tconst options = findOption(nodes, {\n\t\t\t\tlongNames: [config.long],\n\t\t\t\tshortNames: config.short ? [config.short] : [],\n\t\t\t}).filter((x) => !visitedNodes.has(x));\n\t\t\toptions.forEach((opt) => visitedNodes.add(opt));\n\n\t\t\tif (options.length > 1) {\n\t\t\t\treturn Result.err({\n\t\t\t\t\terrors: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnodes: options,\n\t\t\t\t\t\t\tmessage: `Expected 1 occurence, got ${options.length}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst valueFromEnv = config.env ? process.env[config.env] : undefined;\n\t\t\tconst onMissingFn = config.onMissing || config.type.onMissing;\n\n\t\t\tlet rawValue: string;\n\t\t\tlet envPrefix = \"\";\n\n\t\t\tif (options.length === 0 && valueFromEnv !== undefined) {\n\t\t\t\trawValue = valueFromEnv;\n\t\t\t\tenvPrefix = `env[${chalk.italic(config.env)}]: `;\n\t\t\t} else if (options.length === 0 && config.defaultValue) {\n\t\t\t\ttry {\n\t\t\t\t\tconst defaultValue = config.defaultValue();\n\t\t\t\t\treturn Result.ok(defaultValue);\n\t\t\t\t} catch (e: any) {\n\t\t\t\t\tconst message = `Default value not found for '--${config.long}': ${e.message}`;\n\t\t\t\t\treturn Result.err({\n\t\t\t\t\t\terrors: [{ message, nodes: [] }],\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else if (options.length === 0 && onMissingFn) {\n\t\t\t\ttry {\n\t\t\t\t\tconst missingValue = await onMissingFn();\n\t\t\t\t\treturn Result.ok(missingValue);\n\t\t\t\t} catch (e: any) {\n\t\t\t\t\tconst message = `Failed to get missing value for '--${config.long}': ${e.message}`;\n\t\t\t\t\treturn Result.err({\n\t\t\t\t\t\terrors: [{ message, nodes: [] }],\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else if (options.length === 0 && config.type.defaultValue) {\n\t\t\t\ttry {\n\t\t\t\t\tconst defaultValue = config.type.defaultValue();\n\t\t\t\t\treturn Result.ok(defaultValue);\n\t\t\t\t} catch (e: any) {\n\t\t\t\t\tconst message = `Default value not found for '--${config.long}': ${e.message}`;\n\t\t\t\t\treturn Result.err({\n\t\t\t\t\t\terrors: [{ message, nodes: [] }],\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else if (options.length === 1) {\n\t\t\t\trawValue = options[0].value?.node.raw ?? \"true\";\n\t\t\t} else {\n\t\t\t\treturn Result.err({\n\t\t\t\t\terrors: [\n\t\t\t\t\t\t{ nodes: [], message: `No value provided for --${config.long}` },\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst decoded = await Result.safeAsync(decoder.from(rawValue));\n\n\t\t\tif (Result.isErr(decoded)) {\n\t\t\t\treturn Result.err({\n\t\t\t\t\terrors: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnodes: options,\n\t\t\t\t\t\t\tmessage: envPrefix + decoded.error.message,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn decoded;\n\t\t},\n\t};\n}\n\ntype BooleanType = Type<boolean, boolean>;\n\n/**\n * Decodes an argument which is in the form of a key and a boolean value, and allows parsing the following ways:\n *\n * - `--long` where `long` is the provided `long`\n * - `-s=value` where `s` is the provided `short`\n * Shorthand forms can be combined:\n * - `-abcd` will call all flags for the short forms of `a`, `b`, `c` and `d`.\n * @param config flag configurations\n */\nexport function flag<Decoder extends Type<boolean, any>>(\n\tconfig: FlagConfig<Decoder>,\n): ArgParser<OutputOf<Decoder>> &\n\tProvidesHelp &\n\tRegister &\n\tPartial<Descriptive>;\nexport function flag(\n\tconfig: LongDoc &\n\t\tPartial<\n\t\t\tHasType<never> &\n\t\t\t\tShortDoc &\n\t\t\t\tDescriptive &\n\t\t\t\tEnvDoc &\n\t\t\t\tOnMissing<OutputOf<BooleanType>>\n\t\t> &\n\t\tAllOrNothing<Default<OutputOf<BooleanType>>>,\n): ArgParser<OutputOf<BooleanType>> &\n\tProvidesHelp &\n\tRegister &\n\tPartial<Descriptive>;\nexport function flag(\n\tconfig: LongDoc &\n\t\tPartial<HasType<any> & ShortDoc & Descriptive & EnvDoc> &\n\t\tAllOrNothing<Default<OutputOf<any>>>,\n): ArgParser<OutputOf<any>> & ProvidesHelp & Register & Partial<Descriptive> {\n\treturn fullFlag({\n\t\ttype: booleanIdentity,\n\t\t...config,\n\t});\n}\n"
  },
  {
    "path": "src/from.ts",
    "content": "export type FromFn<A, B> = (input: A) => Promise<B>;\n\n/** A safe conversion from type A to type B */\nexport type From<A, B> = {\n\t/**\n\t * Convert `input` safely and asynchronously into an output.\n\t */\n\tfrom: FromFn<A, B>;\n};\n\n/** The output of a conversion type or function */\nexport type OutputOf<F extends From<any, any> | FromFn<any, any>> =\n\tF extends From<any, infer Output>\n\t\t? Output\n\t\t: F extends FromFn<any, infer Output>\n\t\t\t? Output\n\t\t\t: never;\n\n/** The input of a conversion type or function */\nexport type InputOf<F extends From<any, any> | FromFn<any, any>> =\n\tF extends From<infer Input, any>\n\t\t? Input\n\t\t: F extends FromFn<infer Input, any>\n\t\t\t? Input\n\t\t\t: never;\n\n/**\n * A type \"conversion\" from any type to itself\n */\nexport function identity<T>(): From<T, T> {\n\treturn {\n\t\tasync from(a) {\n\t\t\treturn a;\n\t\t},\n\t};\n}\n"
  },
  {
    "path": "src/helpFormatter.ts",
    "content": "import chalk from \"chalk\";\nimport type { ParseContext } from \"./argparser\";\nimport type { HelpTopic } from \"./helpdoc\";\nimport { entries, groupBy, padNoAnsi } from \"./utils\";\n\n/**\n * An example to show in help output\n */\nexport type Example = {\n\t/** Description of what this example does */\n\tdescription: string;\n\t/** The command to run */\n\tcommand: string;\n};\n\n/**\n * Structured data for command help\n */\nexport type CommandHelpData = {\n\t/** The command name */\n\tname: string;\n\t/** Breadcrumb path to this command (e.g., [\"sandbox\", \"create\"]) */\n\tpath: string[];\n\t/** Version string */\n\tversion?: string;\n\t/** Description of the command */\n\tdescription?: string;\n\t/** Aliases for the command */\n\taliases?: string[];\n\t/** Help topics for flags, options, arguments */\n\thelpTopics: HelpTopic[];\n\t/** Examples to show in help output */\n\texamples?: Example[];\n};\n\n/**\n * Structured data for subcommands help\n */\nexport type SubcommandsHelpData = {\n\t/** The name of the subcommands group */\n\tname: string;\n\t/** Breadcrumb path (e.g., [\"sandbox\"]) */\n\tpath: string[];\n\t/** Version string */\n\tversion?: string;\n\t/** Description of the subcommands group */\n\tdescription?: string;\n\t/** Available subcommands */\n\tcommands: Array<{\n\t\tname: string;\n\t\tdescription?: string;\n\t\taliases?: string[];\n\t\t/** Help topics from the command (to derive argument hints) */\n\t\thelpTopics: HelpTopic[];\n\t}>;\n\t/** Examples to show in help output */\n\texamples?: Example[];\n};\n\n/**\n * Interface for custom help formatters.\n * Implement this to create your own help output style.\n */\nexport interface HelpFormatter {\n\t/** Format help output for a single command */\n\tformatCommand(data: CommandHelpData, context: ParseContext): string;\n\t/** Format help output for a subcommands group */\n\tformatSubcommands(data: SubcommandsHelpData, context: ParseContext): string;\n}\n\n// Global formatter storage\nlet currentFormatter: HelpFormatter | null = null;\n\n/**\n * Set a custom help formatter to be used for all commands.\n *\n * @example\n * ```ts\n * import { setDefaultHelpFormatter } from \"cmd-ts\";\n *\n * setDefaultHelpFormatter({\n *   formatCommand(data, context) {\n *     return `My CLI ${data.version}\\n${data.description}`;\n *   },\n *   formatSubcommands(data, context) {\n *     return `Commands: ${data.commands.map(c => c.name).join(\", \")}`;\n *   },\n * });\n * ```\n */\nexport function setDefaultHelpFormatter(formatter: HelpFormatter): void {\n\tcurrentFormatter = formatter;\n}\n\n/**\n * Reset the help formatter to the default.\n * Useful for testing.\n */\nexport function resetHelpFormatter(): void {\n\tcurrentFormatter = null;\n}\n\n/**\n * Get the current help formatter (custom or default).\n * @internal\n */\nexport function getHelpFormatter(): HelpFormatter {\n\treturn currentFormatter ?? defaultHelpFormatter;\n}\n\n/**\n * The default help formatter that matches cmd-ts's original output style.\n */\nexport const defaultHelpFormatter: HelpFormatter = {\n\tformatCommand(data: CommandHelpData, _context: ParseContext): string {\n\t\tconst lines: string[] = [];\n\n\t\tlet name = data.path.length > 0 ? data.path.join(\" \") : data.name;\n\t\tname = chalk.bold(name);\n\n\t\tif (data.version) {\n\t\t\tname += ` ${chalk.dim(data.version)}`;\n\t\t}\n\n\t\tlines.push(name);\n\n\t\tif (data.description) {\n\t\t\tlines.push(chalk.dim(\"> \") + data.description);\n\t\t}\n\n\t\tconst usageBreakdown = groupBy(data.helpTopics, (x) => x.category);\n\n\t\tfor (const [category, helpTopics] of entries(usageBreakdown)) {\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(`${category.toUpperCase()}:`);\n\t\t\tconst widestUsage = helpTopics.reduce((len, curr) => {\n\t\t\t\treturn Math.max(len, curr.usage.length);\n\t\t\t}, 0);\n\t\t\tfor (const helpTopic of helpTopics) {\n\t\t\t\tlet line = \"\";\n\t\t\t\tline += `  ${padNoAnsi(helpTopic.usage, widestUsage, \"end\")}`;\n\t\t\t\tline += \" - \";\n\t\t\t\tline += helpTopic.description;\n\t\t\t\tfor (const defaultValue of helpTopic.defaults) {\n\t\t\t\t\tline += chalk.dim(` [${defaultValue}]`);\n\t\t\t\t}\n\t\t\t\tlines.push(line);\n\t\t\t}\n\t\t}\n\n\t\tif (data.examples && data.examples.length > 0) {\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"EXAMPLES:\");\n\t\t\tfor (const example of data.examples) {\n\t\t\t\tlines.push(\"\");\n\t\t\t\tlines.push(`  ${example.description}`);\n\t\t\t\tlines.push(chalk.dim(`  $ ${example.command}`));\n\t\t\t}\n\t\t}\n\n\t\treturn lines.join(\"\\n\");\n\t},\n\n\tformatSubcommands(data: SubcommandsHelpData, _context: ParseContext): string {\n\t\tconst lines: string[] = [];\n\t\tconst argsSoFar = data.path.length > 0 ? data.path.join(\" \") : data.name;\n\n\t\tlines.push(chalk.bold(argsSoFar + chalk.italic(\" <subcommand>\")));\n\n\t\tif (data.description) {\n\t\t\tlines.push(chalk.dim(\"> \") + data.description);\n\t\t}\n\n\t\tlines.push(\"\");\n\t\tlines.push(`where ${chalk.italic(\"<subcommand>\")} can be one of:`);\n\t\tlines.push(\"\");\n\n\t\tfor (const cmd of data.commands) {\n\t\t\tlet description = cmd.description ?? \"\";\n\t\t\tdescription = description && ` - ${description} `;\n\t\t\tif (cmd.aliases?.length) {\n\t\t\t\tconst aliasTxt = cmd.aliases.length === 1 ? \"alias\" : \"aliases\";\n\t\t\t\tconst aliases = cmd.aliases.join(\", \");\n\t\t\t\tdescription += chalk.dim(`[${aliasTxt}: ${aliases}]`);\n\t\t\t}\n\t\t\tconst row = chalk.dim(\"- \") + cmd.name + description;\n\t\t\tlines.push(row.trim());\n\t\t}\n\n\t\tconst helpCommand = chalk.yellow(`${argsSoFar} <subcommand> --help`);\n\n\t\tlines.push(\"\");\n\t\tlines.push(chalk.dim(`For more help, try running \\`${helpCommand}\\``));\n\n\t\tif (data.examples && data.examples.length > 0) {\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"EXAMPLES:\");\n\t\t\tfor (const example of data.examples) {\n\t\t\t\tlines.push(\"\");\n\t\t\t\tlines.push(`  ${example.description}`);\n\t\t\t\tlines.push(chalk.dim(`  $ ${example.command}`));\n\t\t\t}\n\t\t}\n\n\t\treturn lines.join(\"\\n\");\n\t},\n};\n"
  },
  {
    "path": "src/helpdoc.ts",
    "content": "import type { ParseContext } from \"./argparser\";\n\nexport type Descriptive = {\n\t/** A long description that will be shown on help */\n\tdescription: string;\n};\n\nexport type Versioned = {\n\t/** The item's version */\n\tversion: string;\n};\n\nexport type Named = {\n\t/** The item's name */\n\tname: string;\n};\n\nexport type Displayed = {\n\t/** A short display name that summarizes it */\n\tdisplayName: string;\n};\n\nexport type HelpTopic = {\n\t/**\n\t * A category to show in `PrintHelp`\n\t */\n\tcategory: string;\n\n\t/**\n\t * How to use this?\n\t */\n\tusage: string;\n\n\t/**\n\t * A short description of what it does\n\t */\n\tdescription: string;\n\n\t/**\n\t * Defaults to show the user\n\t */\n\tdefaults: string[];\n};\n\nexport type ProvidesHelp = {\n\thelpTopics(): HelpTopic[];\n};\n\nexport type PrintHelp = {\n\t/**\n\t * Print help for the current item and the current parsing context.\n\t */\n\tprintHelp(context: ParseContext): string;\n};\n\nexport type Aliased = {\n\t/** More ways to call this item */\n\taliases: string[];\n};\n\nexport type ShortDoc = {\n\t/**\n\t * One letter to support the shorthand format of `-s`\n\t * where `s` is the value provided\n\t */\n\tshort: string;\n};\n\nexport type LongDoc = {\n\t/**\n\t * A name to support the long format of `--long`\n\t * where `long` is the value provided\n\t */\n\tlong: string;\n};\n\nexport type EnvDoc = {\n\t/**\n\t * An environment variable name\n\t *\n\t * i.e. `env: 'MY_ARGUMENT'` would allow a default value coming from\n\t * the `MY_ARGUMENT` environment variable.\n\t */\n\tenv: string;\n};\n"
  },
  {
    "path": "src/index.ts",
    "content": "/**\n * The index module: the entrance to the world of cmd-ts 😎\n *\n * @packageDocumentation\n */\n\nexport { subcommands } from \"./subcommands\";\nexport { type Type, extendType } from \"./type\";\nexport * from \"./types\";\nexport { binary } from \"./binary\";\nexport { command } from \"./command\";\nexport { flag } from \"./flag\";\nexport {\n\tsetDefaultHelpFormatter,\n\tresetHelpFormatter,\n\tdefaultHelpFormatter,\n\ttype HelpFormatter,\n\ttype CommandHelpData,\n\ttype SubcommandsHelpData,\n\ttype Example,\n} from \"./helpFormatter\";\nexport { option } from \"./option\";\nexport { positional } from \"./positional\";\nexport { dryRun, runSafely, run, parse, type Runner } from \"./runner\";\nexport { restPositionals } from \"./restPositionals\";\nexport { multiflag } from \"./multiflag\";\nexport { multioption } from \"./multioption\";\nexport { union } from \"./union\";\nexport { oneOf } from \"./oneOf\";\nexport { rest } from \"./rest\";\n"
  },
  {
    "path": "src/multiflag.ts",
    "content": "import * as Result from \"./Result\";\nimport type {\n\tArgParser,\n\tParseContext,\n\tParsingError,\n\tParsingResult,\n} from \"./argparser\";\nimport { boolean } from \"./flag\";\nimport type { From, OutputOf } from \"./from\";\nimport type { Descriptive, LongDoc, ProvidesHelp, ShortDoc } from \"./helpdoc\";\nimport { findOption } from \"./newparser/findOption\";\nimport type { HasType } from \"./type\";\n\ntype MultiFlagConfig<Decoder extends From<boolean[], any>> = HasType<Decoder> &\n\tLongDoc &\n\tPartial<Descriptive & ShortDoc>;\n\n/**\n * Like `option`, but can accept multiple options, and expects a decoder from a list of strings.\n * An error will highlight all option occurences.\n */\nexport function multiflag<Decoder extends From<boolean[], any>>(\n\tconfig: MultiFlagConfig<Decoder>,\n): ArgParser<OutputOf<Decoder>> & ProvidesHelp {\n\treturn {\n\t\thelpTopics() {\n\t\t\tlet usage = `--${config.long}`;\n\t\t\tif (config.short) {\n\t\t\t\tusage += `, -${config.short}`;\n\t\t\t}\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tcategory: \"flags\",\n\t\t\t\t\tusage,\n\t\t\t\t\tdefaults: [],\n\t\t\t\t\tdescription: config.description ?? \"self explanatory\",\n\t\t\t\t},\n\t\t\t];\n\t\t},\n\t\tregister(opts) {\n\t\t\topts.forceFlagLongNames.add(config.long);\n\t\t\tif (config.short) {\n\t\t\t\topts.forceFlagShortNames.add(config.short);\n\t\t\t}\n\t\t},\n\t\tasync parse({\n\t\t\tnodes,\n\t\t\tvisitedNodes,\n\t\t}: ParseContext): Promise<ParsingResult<OutputOf<Decoder>>> {\n\t\t\tconst options = findOption(nodes, {\n\t\t\t\tlongNames: [config.long],\n\t\t\t\tshortNames: config.short ? [config.short] : [],\n\t\t\t}).filter((x) => !visitedNodes.has(x));\n\n\t\t\tfor (const option of options) {\n\t\t\t\tvisitedNodes.add(option);\n\t\t\t}\n\n\t\t\tconst optionValues: boolean[] = [];\n\t\t\tconst errors: ParsingError[] = [];\n\n\t\t\tfor (const option of options) {\n\t\t\t\tconst decoded = await Result.safeAsync(\n\t\t\t\t\tboolean.from(option.value?.node.raw ?? \"true\"),\n\t\t\t\t);\n\t\t\t\tif (Result.isErr(decoded)) {\n\t\t\t\t\terrors.push({ nodes: [option], message: decoded.error.message });\n\t\t\t\t} else {\n\t\t\t\t\toptionValues.push(decoded.value);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (errors.length > 0) {\n\t\t\t\treturn Result.err({\n\t\t\t\t\terrors,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst multiDecoded = await Result.safeAsync(\n\t\t\t\tconfig.type.from(optionValues),\n\t\t\t);\n\n\t\t\tif (Result.isErr(multiDecoded)) {\n\t\t\t\treturn Result.err({\n\t\t\t\t\terrors: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnodes: options,\n\t\t\t\t\t\t\tmessage: multiDecoded.error.message,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn multiDecoded;\n\t\t},\n\t};\n}\n"
  },
  {
    "path": "src/multioption.ts",
    "content": "import chalk from \"chalk\";\nimport * as Result from \"./Result\";\nimport type {\n\tArgParser,\n\tParseContext,\n\tParsingError,\n\tParsingResult,\n\tRegister,\n} from \"./argparser\";\nimport type { Default, OnMissing } from \"./default\";\nimport type { OutputOf } from \"./from\";\nimport type { Descriptive, LongDoc, ProvidesHelp, ShortDoc } from \"./helpdoc\";\nimport { findOption } from \"./newparser/findOption\";\nimport type { AstNode } from \"./newparser/parser\";\nimport type { HasType, Type } from \"./type\";\n\ntype MultiOptionConfig<Decoder extends Type<string[], any>> = HasType<Decoder> &\n\tLongDoc &\n\tPartial<\n\t\tShortDoc &\n\t\t\tDescriptive &\n\t\t\tDefault<OutputOf<Decoder>> &\n\t\t\tOnMissing<OutputOf<Decoder>>\n\t>;\n\n/**\n * Like `option`, but can accept multiple options, and expects a decoder from a list of strings.\n * An error will highlight all option occurences.\n */\nexport function multioption<Decoder extends Type<string[], any>>(\n\tconfig: MultiOptionConfig<Decoder>,\n): ArgParser<OutputOf<Decoder>> & ProvidesHelp & Register {\n\treturn {\n\t\thelpTopics() {\n\t\t\tconst displayName = config.type.displayName ?? \"value\";\n\t\t\tlet usage = `--${config.long} <${displayName}>`;\n\t\t\tif (config.short) {\n\t\t\t\tusage += `, -${config.short}=<${displayName}>`;\n\t\t\t}\n\n\t\t\tconst defaults: string[] = [];\n\n\t\t\tif (config.defaultValue) {\n\t\t\t\ttry {\n\t\t\t\t\tconst defaultValue = config.defaultValue();\n\t\t\t\t\tif (config.defaultValueIsSerializable) {\n\t\t\t\t\t\tdefaults.push(`default: ${chalk.italic(defaultValue)}`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdefaults.push(\"[...optional]\");\n\t\t\t\t\t}\n\t\t\t\t} catch (e) {}\n\t\t\t} else if (config.type.defaultValue) {\n\t\t\t\ttry {\n\t\t\t\t\tconst defaultValue = config.type.defaultValue();\n\t\t\t\t\tif (config.type.defaultValueIsSerializable) {\n\t\t\t\t\t\tdefaults.push(`default: ${chalk.italic(defaultValue)}`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdefaults.push(\"[...optional]\");\n\t\t\t\t\t}\n\t\t\t\t} catch (e) {}\n\t\t\t} else if (config.onMissing || config.type.onMissing) {\n\t\t\t\tdefaults.push(\"[...optional]\");\n\t\t\t}\n\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tcategory: \"options\",\n\t\t\t\t\tusage,\n\t\t\t\t\tdefaults,\n\t\t\t\t\tdescription: config.description ?? \"self explanatory\",\n\t\t\t\t},\n\t\t\t];\n\t\t},\n\t\tregister(opts) {\n\t\t\topts.forceOptionLongNames.add(config.long);\n\t\t\tif (config.short) {\n\t\t\t\topts.forceOptionShortNames.add(config.short);\n\t\t\t}\n\t\t},\n\t\tasync parse({\n\t\t\tnodes,\n\t\t\tvisitedNodes,\n\t\t}: ParseContext): Promise<ParsingResult<OutputOf<Decoder>>> {\n\t\t\tconst options = findOption(nodes, {\n\t\t\t\tlongNames: [config.long],\n\t\t\t\tshortNames: config.short ? [config.short] : [],\n\t\t\t}).filter((x) => !visitedNodes.has(x));\n\n\t\t\tconst defaultValueFn = config.defaultValue || config.type.defaultValue;\n\t\t\tconst onMissingFn = config.onMissing || config.type.onMissing;\n\n\t\t\tif (options.length === 0 && defaultValueFn) {\n\t\t\t\ttry {\n\t\t\t\t\tconst defaultValue = defaultValueFn();\n\t\t\t\t\treturn Result.ok(defaultValue);\n\t\t\t\t} catch (e: any) {\n\t\t\t\t\tconst message = `Failed to resolve default value for '--${config.long}': ${e.message}`;\n\t\t\t\t\treturn Result.err({\n\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tnodes: [],\n\t\t\t\t\t\t\t\tmessage,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else if (options.length === 0 && onMissingFn) {\n\t\t\t\ttry {\n\t\t\t\t\tconst missingValue = await onMissingFn();\n\t\t\t\t\treturn Result.ok(missingValue);\n\t\t\t\t} catch (e: any) {\n\t\t\t\t\tconst message = `Failed to get missing value for '--${config.long}': ${e.message}`;\n\t\t\t\t\treturn Result.err({\n\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tnodes: [],\n\t\t\t\t\t\t\t\tmessage,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (const option of options) {\n\t\t\t\tvisitedNodes.add(option);\n\t\t\t}\n\n\t\t\tconst optionValues: string[] = [];\n\t\t\tconst errors: ParsingError[] = [];\n\t\t\tconst flagNodes: AstNode[] = [];\n\n\t\t\tfor (const option of options) {\n\t\t\t\tconst providedValue = option.value?.node.raw;\n\t\t\t\tif (providedValue === undefined) {\n\t\t\t\t\tflagNodes.push(option);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\toptionValues.push(providedValue);\n\t\t\t}\n\n\t\t\tif (flagNodes.length > 0) {\n\t\t\t\terrors.push({\n\t\t\t\t\tnodes: flagNodes,\n\t\t\t\t\tmessage: \"Expected to get a value, found a flag\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (errors.length > 0) {\n\t\t\t\treturn Result.err({ errors });\n\t\t\t}\n\n\t\t\tconst multiDecoded = await Result.safeAsync(\n\t\t\t\tconfig.type.from(optionValues),\n\t\t\t);\n\n\t\t\tif (Result.isErr(multiDecoded)) {\n\t\t\t\treturn Result.err({\n\t\t\t\t\terrors: [{ nodes: options, message: multiDecoded.error.message }],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn multiDecoded;\n\t\t},\n\t};\n}\n"
  },
  {
    "path": "src/newparser/findOption.ts",
    "content": "import type { AstNode, LongOption, ShortOption } from \"./parser\";\n\ntype Option = LongOption | ShortOption;\n\n/**\n * A utility function to find an option in the AST\n *\n * @param nodes AST node list\n * @param opts Long and short names to look up\n */\nexport function findOption(\n\tnodes: AstNode[],\n\topts: {\n\t\tlongNames: string[];\n\t\tshortNames: string[];\n\t},\n): Option[] {\n\tconst result: Option[] = [];\n\n\tfor (const node of nodes) {\n\t\tif (node.type === \"longOption\" && opts.longNames.includes(node.key)) {\n\t\t\tresult.push(node);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (node.type === \"shortOptions\" && opts.shortNames.length) {\n\t\t\tfor (const option of node.options) {\n\t\t\t\tif (opts.shortNames.includes(option.key)) {\n\t\t\t\t\tresult.push(option);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result;\n}\n"
  },
  {
    "path": "src/newparser/parser.ts",
    "content": "import createDebugger from \"debug\";\nimport type { RegisterOptions } from \"../argparser\";\nimport type { Token } from \"./tokenizer\";\n\nconst debug = createDebugger(\"cmd-ts:parser\");\n\nexport type AstNode =\n\t| Value\n\t| LongOption\n\t| ShortOption\n\t| ShortOptions\n\t| PositionalArgument\n\t| ForcePositional;\n\ntype BaseAstNode<Type extends string> = {\n\ttype: Type;\n\tindex: number;\n\traw: string;\n};\n\nexport interface LongOption extends BaseAstNode<\"longOption\"> {\n\tkey: string;\n\tvalue?: OptionValue;\n}\n\ninterface Delimiter extends BaseAstNode<\"delimiter\"> {}\n\ninterface Value extends BaseAstNode<\"value\"> {}\n\ninterface OptionValue extends BaseAstNode<\"optionValue\"> {\n\tdelimiter: Delimiter;\n\tnode: Value;\n}\n\nexport interface ShortOptions extends BaseAstNode<\"shortOptions\"> {\n\toptions: ShortOption[];\n}\n\nexport interface ShortOption extends BaseAstNode<\"shortOption\"> {\n\tkey: string;\n\tvalue?: OptionValue;\n}\n\nexport interface PositionalArgument extends BaseAstNode<\"positionalArgument\"> {}\n\ninterface ForcePositional extends BaseAstNode<\"forcePositional\"> {\n\ttype: \"forcePositional\";\n}\n\n/**\n * Create an AST from a token list\n *\n * @param tokens A token list, coming from `tokenizer.ts`\n * @param forceFlag Keys to force as flag. {@see ForceFlag} to read more about it.\n */\nexport function parse(tokens: Token[], forceFlag: RegisterOptions): AstNode[] {\n\tif (debug.enabled) {\n\t\tconst registered = {\n\t\t\tshortFlags: [...forceFlag.forceFlagShortNames],\n\t\t\tlongFlags: [...forceFlag.forceFlagLongNames],\n\t\t\tshortOptions: [...forceFlag.forceOptionShortNames],\n\t\t\tlongOptions: [...forceFlag.forceOptionLongNames],\n\t\t};\n\t\tdebug(\"Registered:\", JSON.stringify(registered));\n\t}\n\n\tconst nodes: AstNode[] = [];\n\tlet index = 0;\n\tlet forcedPositional = false;\n\n\tfunction getToken(): Token | undefined {\n\t\treturn tokens[index++];\n\t}\n\n\tfunction peekToken(): Token | undefined {\n\t\treturn tokens[index];\n\t}\n\n\twhile (index < tokens.length) {\n\t\tconst currentToken = getToken();\n\t\tif (!currentToken) break;\n\n\t\tif (currentToken.type === \"argumentDivider\") {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (forcedPositional) {\n\t\t\tlet str = currentToken.raw;\n\t\t\tlet nextToken = getToken();\n\t\t\twhile (nextToken && nextToken?.type !== \"argumentDivider\") {\n\t\t\t\tstr += nextToken.raw;\n\t\t\t\tnextToken = getToken();\n\t\t\t}\n\t\t\tnodes.push({\n\t\t\t\ttype: \"positionalArgument\",\n\t\t\t\tindex: currentToken.index,\n\t\t\t\traw: str,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (currentToken.type === \"char\") {\n\t\t\tlet str = currentToken.raw;\n\t\t\tlet nextToken = getToken();\n\t\t\twhile (nextToken && nextToken?.type !== \"argumentDivider\") {\n\t\t\t\tstr += nextToken.raw;\n\t\t\t\tnextToken = getToken();\n\t\t\t}\n\t\t\tnodes.push({\n\t\t\t\ttype: \"positionalArgument\",\n\t\t\t\tindex: currentToken.index,\n\t\t\t\traw: str,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (currentToken.type === \"longPrefix\") {\n\t\t\tlet nextToken = getToken();\n\n\t\t\tif (nextToken?.type === \"argumentDivider\" || !nextToken) {\n\t\t\t\tnodes.push({\n\t\t\t\t\ttype: \"forcePositional\",\n\t\t\t\t\tindex: currentToken.index,\n\t\t\t\t\traw: \"--\",\n\t\t\t\t});\n\t\t\t\tforcedPositional = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet key = \"\";\n\t\t\twhile (\n\t\t\t\tnextToken &&\n\t\t\t\tnextToken?.raw !== \"=\" &&\n\t\t\t\tnextToken?.type !== \"argumentDivider\"\n\t\t\t) {\n\t\t\t\tkey += nextToken.raw;\n\t\t\t\tnextToken = getToken();\n\t\t\t}\n\n\t\t\tconst parsedValue = parseOptionValue({\n\t\t\t\tkey,\n\t\t\t\tdelimiterToken: nextToken,\n\t\t\t\tforceFlag: forceFlag.forceFlagLongNames,\n\t\t\t\tgetToken,\n\t\t\t\tpeekToken,\n\t\t\t\tforceOption: forceFlag.forceOptionLongNames,\n\t\t\t});\n\t\t\tlet raw = `--${key}`;\n\n\t\t\tif (parsedValue) {\n\t\t\t\traw += parsedValue.raw;\n\t\t\t}\n\n\t\t\tnodes.push({\n\t\t\t\ttype: \"longOption\",\n\t\t\t\tkey,\n\t\t\t\tindex: currentToken.index,\n\t\t\t\traw,\n\t\t\t\tvalue: parsedValue,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (currentToken.type === \"shortPrefix\") {\n\t\t\tconst keys: Token[] = [];\n\t\t\tlet nextToken = getToken();\n\n\t\t\tif (nextToken?.type === \"argumentDivider\" || !nextToken) {\n\t\t\t\tnodes.push({\n\t\t\t\t\ttype: \"positionalArgument\",\n\t\t\t\t\tindex: currentToken.index,\n\t\t\t\t\traw: \"-\",\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\twhile (\n\t\t\t\tnextToken &&\n\t\t\t\tnextToken?.type !== \"argumentDivider\" &&\n\t\t\t\tnextToken?.raw !== \"=\"\n\t\t\t) {\n\t\t\t\tkeys.push(nextToken);\n\t\t\t\tnextToken = getToken();\n\t\t\t}\n\n\t\t\t// biome-ignore lint/style/noNonNullAssertion: migration\n\t\t\tconst lastKey = keys.pop()!;\n\t\t\tconst parsedValue = parseOptionValue({\n\t\t\t\tkey: lastKey.raw,\n\t\t\t\tdelimiterToken: nextToken,\n\t\t\t\tforceFlag: forceFlag.forceFlagShortNames,\n\t\t\t\tforceOption: forceFlag.forceOptionShortNames,\n\t\t\t\tgetToken,\n\t\t\t\tpeekToken,\n\t\t\t});\n\n\t\t\tconst options: ShortOption[] = [];\n\n\t\t\tfor (const key of keys) {\n\t\t\t\toptions.push({\n\t\t\t\t\ttype: \"shortOption\",\n\t\t\t\t\tindex: key.index,\n\t\t\t\t\traw: key.raw,\n\t\t\t\t\tkey: key.raw,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tlet lastKeyRaw = lastKey.raw;\n\n\t\t\tif (parsedValue) {\n\t\t\t\tlastKeyRaw += parsedValue.raw;\n\t\t\t}\n\n\t\t\toptions.push({\n\t\t\t\ttype: \"shortOption\",\n\t\t\t\tindex: lastKey.index,\n\t\t\t\traw: lastKeyRaw,\n\t\t\t\tvalue: parsedValue,\n\t\t\t\tkey: lastKey.raw,\n\t\t\t});\n\n\t\t\tlet optionsRaw = `-${keys.map((x) => x.raw).join(\"\")}${lastKey.raw}`;\n\t\t\tif (parsedValue) {\n\t\t\t\toptionsRaw += parsedValue.raw;\n\t\t\t}\n\n\t\t\tconst shortOptions: ShortOptions = {\n\t\t\t\ttype: \"shortOptions\",\n\t\t\t\tindex: currentToken.index,\n\t\t\t\traw: optionsRaw,\n\t\t\t\toptions,\n\t\t\t};\n\n\t\t\tnodes.push(shortOptions);\n\t\t\tcontinue;\n\t\t}\n\n\t\tindex++;\n\t}\n\n\tif (debug.enabled) {\n\t\tconst objectNodes = nodes.map((node) => ({ [node.type]: node.raw }));\n\t\tdebug(\"Parsed items:\", JSON.stringify(objectNodes));\n\t}\n\n\treturn nodes;\n}\n\nfunction parseOptionValue(opts: {\n\tdelimiterToken?: Token;\n\tgetToken(): Token | undefined;\n\tpeekToken(): Token | undefined;\n\tkey: string;\n\tforceFlag: Set<string>;\n\tforceOption: Set<string>;\n}): OptionValue | undefined {\n\tconst { getToken, delimiterToken, forceFlag, key, forceOption } = opts;\n\tconst shouldReadKeyAsOption = forceOption.has(key);\n\tconst shouldReadKeyAsFlag =\n\t\t!shouldReadKeyAsOption &&\n\t\t(forceFlag.has(key) || opts.peekToken()?.type !== \"char\");\n\n\tif (!delimiterToken || (delimiterToken.raw !== \"=\" && shouldReadKeyAsFlag)) {\n\t\treturn;\n\t}\n\n\tconst delimiter = delimiterToken.raw === \"=\" ? \"=\" : \" \";\n\tconst delimiterIndex = delimiterToken.index;\n\n\tlet nextToken = getToken();\n\tif (!nextToken) {\n\t\treturn;\n\t}\n\n\tlet value = \"\";\n\tconst valueIndex = nextToken.index;\n\twhile (nextToken && nextToken?.type !== \"argumentDivider\") {\n\t\tvalue += nextToken.raw;\n\t\tnextToken = getToken();\n\t}\n\n\treturn {\n\t\ttype: \"optionValue\",\n\t\tindex: delimiterToken.index,\n\t\tdelimiter: { type: \"delimiter\", raw: delimiter, index: delimiterIndex },\n\t\tnode: { type: \"value\", raw: value, index: valueIndex },\n\t\traw: `${delimiter}${value}`,\n\t};\n}\n"
  },
  {
    "path": "src/newparser/tokenizer.ts",
    "content": "import { enumerate } from \"../utils\";\n\nexport type Token =\n\t| { index: number; type: \"argumentDivider\"; raw: \" \" }\n\t| {\n\t\t\tindex: number;\n\t\t\ttype: \"shortPrefix\";\n\t\t\traw: \"-\";\n\t  }\n\t| {\n\t\t\tindex: number;\n\t\t\ttype: \"longPrefix\";\n\t\t\traw: \"--\";\n\t  }\n\t| {\n\t\t\tindex: number;\n\t\t\ttype: \"char\";\n\t\t\traw: string;\n\t  };\n\n/**\n * Tokenize a list of arguments\n *\n * @param strings arguments, based on `process.argv`\n */\nexport function tokenize(strings: string[]): Token[] {\n\tconst tokens: Token[] = [];\n\tlet overallIndex = 0;\n\n\tconst push = (token: Token) => {\n\t\ttokens.push(token);\n\t\toverallIndex += token.raw.length;\n\t};\n\n\tfor (const [stringIndex, string] of enumerate(strings)) {\n\t\tconst chars = [...string];\n\t\tfor (let i = 0; i < chars.length; i++) {\n\t\t\tif (chars[i] === \"-\" && chars[i + 1] === \"-\") {\n\t\t\t\tpush({ type: \"longPrefix\", raw: \"--\", index: overallIndex });\n\t\t\t\ti++;\n\t\t\t} else if (chars[i] === \"-\") {\n\t\t\t\tpush({ type: \"shortPrefix\", raw: \"-\", index: overallIndex });\n\t\t\t} else {\n\t\t\t\tpush({ type: \"char\", raw: chars[i], index: overallIndex });\n\t\t\t}\n\t\t}\n\n\t\tif (stringIndex + 1 !== strings.length) {\n\t\t\tpush({ type: \"argumentDivider\", raw: \" \", index: overallIndex });\n\t\t}\n\t}\n\n\treturn tokens;\n}\n"
  },
  {
    "path": "src/oneOf.ts",
    "content": "import { inspect } from \"node:util\";\nimport type { Type } from \"./type\";\n\n/**\n * A union of literals. When you want to take an exact enum value.\n */\nexport function oneOf<T extends string>(\n\tliterals: readonly T[],\n): Type<string, T> {\n\tconst examples = literals.map((x) => inspect(x)).join(\", \");\n\treturn {\n\t\tasync from(str) {\n\t\t\tconst value = literals.find((x) => x === str);\n\t\t\tif (!value) {\n\t\t\t\tthrow new Error(`Invalid value '${str}'. Expected one of: ${examples}`);\n\t\t\t}\n\t\t\treturn value;\n\t\t},\n\t\tdescription: `One of ${examples}`,\n\t};\n}\n"
  },
  {
    "path": "src/option.ts",
    "content": "import chalk from \"chalk\";\nimport * as Result from \"./Result\";\nimport type {\n\tArgParser,\n\tParseContext,\n\tParsingError,\n\tParsingResult,\n} from \"./argparser\";\nimport type { Default, OnMissing } from \"./default\";\nimport type { OutputOf } from \"./from\";\nimport type {\n\tDescriptive,\n\tEnvDoc,\n\tLongDoc,\n\tProvidesHelp,\n\tShortDoc,\n} from \"./helpdoc\";\nimport { findOption } from \"./newparser/findOption\";\nimport type { HasType, Type } from \"./type\";\nimport { string } from \"./types\";\nimport type { AllOrNothing } from \"./utils\";\n\ntype OptionConfig<Decoder extends Type<string, any>> = LongDoc &\n\tHasType<Decoder> &\n\tPartial<Descriptive & EnvDoc & ShortDoc & OnMissing<OutputOf<Decoder>>> &\n\tAllOrNothing<Default<OutputOf<Decoder>>>;\n\nfunction fullOption<Decoder extends Type<string, any>>(\n\tconfig: OptionConfig<Decoder>,\n): ArgParser<OutputOf<Decoder>> & ProvidesHelp & Partial<Descriptive> {\n\treturn {\n\t\tdescription: config.description ?? config.type.description,\n\t\thelpTopics() {\n\t\t\tconst displayName = config.type.displayName ?? \"value\";\n\t\t\tlet usage = `--${config.long}`;\n\t\t\tif (config.short) {\n\t\t\t\tusage += `, -${config.short}`;\n\t\t\t}\n\t\t\tusage += ` <${displayName}>`;\n\n\t\t\tconst defaults: string[] = [];\n\n\t\t\tif (config.env) {\n\t\t\t\tconst env =\n\t\t\t\t\tprocess.env[config.env] === undefined\n\t\t\t\t\t\t? \"\"\n\t\t\t\t\t\t: `=${chalk.italic(process.env[config.env])}`;\n\t\t\t\tdefaults.push(`env: ${config.env}${env}`);\n\t\t\t}\n\n\t\t\tif (config.defaultValue) {\n\t\t\t\ttry {\n\t\t\t\t\tconst defaultValue = config.defaultValue();\n\t\t\t\t\tif (config.defaultValueIsSerializable) {\n\t\t\t\t\t\tdefaults.push(`default: ${chalk.italic(defaultValue)}`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdefaults.push(\"optional\");\n\t\t\t\t\t}\n\t\t\t\t} catch (e) {}\n\t\t\t} else if (config.type.defaultValue) {\n\t\t\t\ttry {\n\t\t\t\t\tconst defaultValue = config.type.defaultValue();\n\t\t\t\t\tif (config.type.defaultValueIsSerializable) {\n\t\t\t\t\t\tdefaults.push(`default: ${chalk.italic(defaultValue)}`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdefaults.push(\"optional\");\n\t\t\t\t\t}\n\t\t\t\t} catch (e) {}\n\t\t\t} else if (config.onMissing || config.type.onMissing) {\n\t\t\t\tdefaults.push(\"optional\");\n\t\t\t}\n\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tcategory: \"options\",\n\t\t\t\t\tusage,\n\t\t\t\t\tdefaults,\n\t\t\t\t\tdescription:\n\t\t\t\t\t\tconfig.description ?? config.type.description ?? \"self explanatory\",\n\t\t\t\t},\n\t\t\t];\n\t\t},\n\t\tregister(opts) {\n\t\t\topts.forceOptionLongNames.add(config.long);\n\t\t\tif (config.short) {\n\t\t\t\topts.forceOptionShortNames.add(config.short);\n\t\t\t}\n\t\t},\n\t\tasync parse({\n\t\t\tnodes,\n\t\t\tvisitedNodes,\n\t\t}: ParseContext): Promise<ParsingResult<OutputOf<Decoder>>> {\n\t\t\tconst options = findOption(nodes, {\n\t\t\t\tlongNames: [config.long],\n\t\t\t\tshortNames: config.short ? [config.short] : [],\n\t\t\t}).filter((x) => !visitedNodes.has(x));\n\n\t\t\toptions.forEach((opt) => visitedNodes.add(opt));\n\n\t\t\tif (options.length > 1) {\n\t\t\t\tconst error: ParsingError = {\n\t\t\t\t\tmessage: `Too many times provided. Expected 1, got: ${options.length}`,\n\t\t\t\t\tnodes: options,\n\t\t\t\t};\n\t\t\t\treturn Result.err({ errors: [error] });\n\t\t\t}\n\n\t\t\tconst valueFromEnv = config.env ? process.env[config.env] : undefined;\n\t\t\tconst defaultValueFn = config.defaultValue || config.type.defaultValue;\n\t\t\tconst onMissingFn = config.onMissing || config.type.onMissing;\n\n\t\t\tconst option = options[0];\n\t\t\tlet rawValue: string;\n\t\t\tlet envPrefix = \"\";\n\t\t\tif (option?.value) {\n\t\t\t\trawValue = option.value.node.raw;\n\t\t\t} else if (valueFromEnv !== undefined) {\n\t\t\t\trawValue = valueFromEnv;\n\t\t\t\tenvPrefix = `env[${chalk.italic(config.env)}]: `;\n\t\t\t} else if (defaultValueFn) {\n\t\t\t\ttry {\n\t\t\t\t\tconst defaultValue = defaultValueFn();\n\t\t\t\t\treturn Result.ok(defaultValue);\n\t\t\t\t} catch (e: any) {\n\t\t\t\t\tconst message = `Default value not found for '--${config.long}': ${e.message}`;\n\t\t\t\t\treturn Result.err({\n\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tnodes: [],\n\t\t\t\t\t\t\t\tmessage,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else if (onMissingFn) {\n\t\t\t\ttry {\n\t\t\t\t\tconst missingValue = await onMissingFn();\n\t\t\t\t\treturn Result.ok(missingValue);\n\t\t\t\t} catch (e: any) {\n\t\t\t\t\tconst message = `Failed to get missing value for '--${config.long}': ${e.message}`;\n\t\t\t\t\treturn Result.err({\n\t\t\t\t\t\terrors: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tnodes: [],\n\t\t\t\t\t\t\t\tmessage,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// If we reach here, no default or prompt value was available\n\t\t\t\tconst raw =\n\t\t\t\t\toption?.type === \"shortOption\"\n\t\t\t\t\t\t? `-${option?.key}`\n\t\t\t\t\t\t: `--${config.long}`;\n\t\t\t\treturn Result.err({\n\t\t\t\t\terrors: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnodes: options,\n\t\t\t\t\t\t\tmessage: `No value provided for ${raw}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst decoded = await Result.safeAsync(config.type.from(rawValue));\n\t\t\tif (Result.isErr(decoded)) {\n\t\t\t\treturn Result.err({\n\t\t\t\t\terrors: [\n\t\t\t\t\t\t{ nodes: options, message: envPrefix + decoded.error.message },\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn Result.ok(decoded.value);\n\t\t},\n\t};\n}\n\ntype StringType = Type<string, string>;\n\n/**\n * Decodes an argument which is in the form of a key and a value, and allows parsing the following ways:\n *\n * - `--long=value` where `long` is the provided `long`\n * - `--long value` where `long` is the provided `long`\n * - `-s=value` where `s` is the provided `short`\n * - `-s value` where `s` is the provided `short`\n * @param config flag configurations\n */\nexport function option<Decoder extends Type<string, any>>(\n\tconfig: LongDoc &\n\t\tHasType<Decoder> &\n\t\tPartial<Descriptive & EnvDoc & ShortDoc & OnMissing<OutputOf<Decoder>>> &\n\t\tAllOrNothing<Default<OutputOf<Decoder>>>,\n): ArgParser<OutputOf<Decoder>> & ProvidesHelp & Partial<Descriptive>;\nexport function option(\n\tconfig: LongDoc &\n\t\tPartial<\n\t\t\tHasType<never> &\n\t\t\t\tDescriptive &\n\t\t\t\tEnvDoc &\n\t\t\t\tShortDoc &\n\t\t\t\tOnMissing<OutputOf<StringType>>\n\t\t> &\n\t\tAllOrNothing<Default<OutputOf<StringType>>>,\n): ArgParser<OutputOf<StringType>> & ProvidesHelp & Partial<Descriptive>;\nexport function option(\n\tconfig: LongDoc &\n\t\tPartial<HasType<any>> &\n\t\tPartial<Descriptive & EnvDoc & ShortDoc>,\n): ArgParser<OutputOf<any>> & ProvidesHelp & Partial<Descriptive> {\n\treturn fullOption({\n\t\ttype: string,\n\t\t...config,\n\t});\n}\n"
  },
  {
    "path": "src/positional.ts",
    "content": "import chalk from \"chalk\";\nimport * as Result from \"./Result\";\nimport type { ArgParser, ParseContext, ParsingResult } from \"./argparser\";\nimport type { Default } from \"./default\";\nimport type { OutputOf } from \"./from\";\nimport type { Descriptive, Displayed, ProvidesHelp } from \"./helpdoc\";\nimport type { PositionalArgument } from \"./newparser/parser\";\nimport type { HasType, Type } from \"./type\";\nimport { string } from \"./types\";\nimport type { AllOrNothing } from \"./utils\";\n\ntype PositionalConfig<Decoder extends Type<string, any>> = HasType<Decoder> &\n\tPartial<Displayed & Descriptive> &\n\tAllOrNothing<Default<OutputOf<Decoder>>>;\n\ntype PositionalParser<Decoder extends Type<string, any>> = ArgParser<\n\tOutputOf<Decoder>\n> &\n\tProvidesHelp &\n\tPartial<Descriptive>;\n\nfunction fullPositional<Decoder extends Type<string, any>>(\n\tconfig: PositionalConfig<Decoder>,\n): PositionalParser<Decoder> {\n\tconst displayName = config.displayName ?? config.type.displayName ?? \"arg\";\n\n\treturn {\n\t\tdescription: config.description ?? config.type.description,\n\t\thelpTopics() {\n\t\t\tconst defaults: string[] = [];\n\t\t\tconst defaultValueFn = config.defaultValue ?? config.type.defaultValue;\n\n\t\t\tif (defaultValueFn) {\n\t\t\t\ttry {\n\t\t\t\t\tconst defaultValue = defaultValueFn();\n\t\t\t\t\tif (\n\t\t\t\t\t\tconfig.defaultValueIsSerializable ??\n\t\t\t\t\t\tconfig.type.defaultValueIsSerializable\n\t\t\t\t\t) {\n\t\t\t\t\t\tdefaults.push(`default: ${chalk.italic(defaultValue)}`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdefaults.push(\"optional\");\n\t\t\t\t\t}\n\t\t\t\t} catch (e) {}\n\t\t\t}\n\n\t\t\tconst usage =\n\t\t\t\tdefaults.length > 0 ? `[${displayName}]` : `<${displayName}>`;\n\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tcategory: \"arguments\",\n\t\t\t\t\tusage,\n\t\t\t\t\tdescription:\n\t\t\t\t\t\tconfig.description ?? config.type.description ?? \"self explanatory\",\n\t\t\t\t\tdefaults,\n\t\t\t\t},\n\t\t\t];\n\t\t},\n\t\tregister(_opts) {},\n\t\tasync parse({\n\t\t\tnodes,\n\t\t\tvisitedNodes,\n\t\t}: ParseContext): Promise<ParsingResult<OutputOf<Decoder>>> {\n\t\t\tconst positionals = nodes.filter(\n\t\t\t\t(node): node is PositionalArgument =>\n\t\t\t\t\tnode.type === \"positionalArgument\" && !visitedNodes.has(node),\n\t\t\t);\n\n\t\t\tconst defaultValueFn = config.defaultValue ?? config.type.defaultValue;\n\n\t\t\tconst positional = positionals[0];\n\n\t\t\tif (!positional) {\n\t\t\t\tif (defaultValueFn) {\n\t\t\t\t\treturn Result.ok(defaultValueFn());\n\t\t\t\t}\n\t\t\t\treturn Result.err({\n\t\t\t\t\terrors: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnodes: [],\n\t\t\t\t\t\t\tmessage: `No value provided for ${displayName}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tvisitedNodes.add(positional);\n\t\t\tconst decoded = await Result.safeAsync(config.type.from(positional.raw));\n\n\t\t\tif (Result.isErr(decoded)) {\n\t\t\t\treturn Result.err({\n\t\t\t\t\terrors: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnodes: [positional],\n\t\t\t\t\t\t\tmessage: decoded.error.message,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn Result.ok(decoded.value);\n\t\t},\n\t};\n}\n\ntype StringType = Type<string, string>;\n\n/**\n * A positional command line argument.\n *\n * Decodes one argument that is not a flag or an option:\n * In `hello --key value world` we have 2 positional arguments — `hello` and `world`.\n *\n * @param config positional argument config\n */\nexport function positional<Decoder extends Type<string, any>>(\n\tconfig: HasType<Decoder> & Partial<Displayed & Descriptive>,\n): PositionalParser<Decoder>;\nexport function positional(\n\tconfig?: Partial<HasType<never> & Displayed & Descriptive>,\n): PositionalParser<StringType>;\nexport function positional(\n\tconfig?: Partial<HasType<any>> & Partial<Displayed & Descriptive>,\n): PositionalParser<any> {\n\treturn fullPositional({\n\t\ttype: string,\n\t\t...config,\n\t});\n}\n"
  },
  {
    "path": "src/rest.ts",
    "content": "import * as Result from \"./Result\";\nimport type { ArgParser } from \"./argparser\";\nimport type { Descriptive, Displayed, ProvidesHelp } from \"./helpdoc\";\nimport type { AstNode } from \"./newparser/parser\";\n\nexport function rest(\n\tconfig?: Partial<Displayed & Descriptive>,\n): ArgParser<string[]> & ProvidesHelp {\n\treturn {\n\t\thelpTopics() {\n\t\t\tconst displayName = config?.displayName ?? \"arg\";\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tusage: `[...${displayName}]`,\n\t\t\t\t\tcategory: \"arguments\",\n\t\t\t\t\tdefaults: [],\n\t\t\t\t\tdescription: config?.description ?? \"catches the rest of the values\",\n\t\t\t\t},\n\t\t\t];\n\t\t},\n\t\tregister() {},\n\t\tasync parse(context) {\n\t\t\tconst visitedNodeIndices = [...context.visitedNodes]\n\t\t\t\t.map((x) => context.nodes.indexOf(x))\n\t\t\t\t.filter((x) => x > -1);\n\n\t\t\tconst strings: string[] = [];\n\n\t\t\tconst maxIndex = Math.max(-1, ...visitedNodeIndices);\n\t\t\tconst restItems = context.nodes.slice(maxIndex + 1);\n\t\t\tfor (const node of restItems) {\n\t\t\t\tswitch (node.type) {\n\t\t\t\t\tcase \"positionalArgument\": {\n\t\t\t\t\t\tstrings.push(node.raw);\n\t\t\t\t\t\tcontext.visitedNodes.add(node);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"longOption\": {\n\t\t\t\t\t\tstrings.push(...getOriginal(node));\n\t\t\t\t\t\tcontext.visitedNodes.add(node);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"shortOption\": {\n\t\t\t\t\t\tstrings.push(...getOriginal(node));\n\t\t\t\t\t\tcontext.visitedNodes.add(node);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"forcePositional\": {\n\t\t\t\t\t\tstrings.push(node.raw);\n\t\t\t\t\t\tcontext.visitedNodes.add(node);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"shortOptions\": {\n\t\t\t\t\t\tconst last = node.options.at(-1);\n\t\t\t\t\t\tcontext.visitedNodes.add(node);\n\t\t\t\t\t\tstrings.push(...getOriginal({ ...node, value: last?.value }));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Result.ok(strings);\n\t\t},\n\t};\n}\n\nfunction getOriginal(node: {\n\tindex: number;\n\traw: string;\n\tvalue?: Extract<AstNode, { type: \"longOption\" }>[\"value\"];\n}): string[] {\n\tif (!node.value) {\n\t\treturn [node.raw];\n\t}\n\n\tif (node.value.delimiter.raw === \" \") {\n\t\treturn [\n\t\t\tnode.raw.slice(0, node.value.index - node.index),\n\t\t\tnode.value.node.raw,\n\t\t];\n\t}\n\n\treturn [node.raw];\n}\n"
  },
  {
    "path": "src/restPositionals.ts",
    "content": "import * as Result from \"./Result\";\nimport type {\n\tArgParser,\n\tParseContext,\n\tParsingError,\n\tParsingResult,\n} from \"./argparser\";\nimport type { OutputOf } from \"./from\";\nimport type { Descriptive, Displayed, ProvidesHelp } from \"./helpdoc\";\nimport type { PositionalArgument } from \"./newparser/parser\";\nimport type { HasType, Type } from \"./type\";\nimport { string } from \"./types\";\n\ntype RestPositionalsConfig<Decoder extends Type<string, any>> =\n\tHasType<Decoder> & Partial<Displayed & Descriptive>;\n\n/**\n * Read all the positionals and decode them using the type provided.\n * Works best when it is the last item on the `command` construct, to be\n * used like the `...rest` operator in JS and TypeScript.\n */\nfunction fullRestPositionals<Decoder extends Type<string, any>>(\n\tconfig: RestPositionalsConfig<Decoder>,\n): ArgParser<OutputOf<Decoder>[]> & ProvidesHelp {\n\treturn {\n\t\thelpTopics() {\n\t\t\tconst displayName =\n\t\t\t\tconfig.displayName ?? config.type.displayName ?? \"arg\";\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tusage: `[...${displayName}]`,\n\t\t\t\t\tcategory: \"arguments\",\n\t\t\t\t\tdefaults: [],\n\t\t\t\t\tdescription: config.description ?? config.type.description ?? \"\",\n\t\t\t\t},\n\t\t\t];\n\t\t},\n\t\tregister(_opts) {},\n\t\tasync parse({\n\t\t\tnodes,\n\t\t\tvisitedNodes,\n\t\t}: ParseContext): Promise<ParsingResult<OutputOf<Decoder>[]>> {\n\t\t\tconst positionals = nodes.filter(\n\t\t\t\t(node): node is PositionalArgument =>\n\t\t\t\t\tnode.type === \"positionalArgument\" && !visitedNodes.has(node),\n\t\t\t);\n\n\t\t\tconst results: OutputOf<Decoder>[] = [];\n\t\t\tconst errors: ParsingError[] = [];\n\n\t\t\tfor (const positional of positionals) {\n\t\t\t\tvisitedNodes.add(positional);\n\t\t\t\tconst decoded = await Result.safeAsync(\n\t\t\t\t\tconfig.type.from(positional.raw),\n\t\t\t\t);\n\t\t\t\tif (Result.isOk(decoded)) {\n\t\t\t\t\tresults.push(decoded.value);\n\t\t\t\t} else {\n\t\t\t\t\terrors.push({\n\t\t\t\t\t\tnodes: [positional],\n\t\t\t\t\t\tmessage: decoded.error.message,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (errors.length > 0) {\n\t\t\t\treturn Result.err({\n\t\t\t\t\terrors,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn Result.ok(results);\n\t\t},\n\t};\n}\n\ntype StringType = Type<string, string>;\n\ntype RestPositionalsParser<Decoder extends Type<string, any>> = ArgParser<\n\tOutputOf<Decoder>[]\n> &\n\tProvidesHelp;\n\n/**\n * Read all the positionals and decode them using the type provided.\n * Works best when it is the last item on the `command` construct, to be\n * used like the `...rest` operator in JS and TypeScript.\n *\n * @param config rest positionals argument config\n */\nexport function restPositionals<Decoder extends Type<string, any>>(\n\tconfig: HasType<Decoder> & Partial<Displayed & Descriptive>,\n): RestPositionalsParser<Decoder>;\nexport function restPositionals(\n\tconfig?: Partial<HasType<never> & Displayed & Descriptive>,\n): RestPositionalsParser<StringType>;\nexport function restPositionals(\n\tconfig?: Partial<HasType<any>> & Partial<Displayed & Descriptive>,\n): RestPositionalsParser<any> {\n\treturn fullRestPositionals({\n\t\ttype: string,\n\t\t...config,\n\t});\n}\n"
  },
  {
    "path": "src/runner.ts",
    "content": "import { type Result, err, isErr, ok } from \"./Result\";\nimport type {\n\tArgParser,\n\tParseContext,\n\tParsingResult,\n\tRegister,\n} from \"./argparser\";\nimport { Exit } from \"./effects\";\nimport { errorBox } from \"./errorBox\";\nimport type { PrintHelp, Versioned } from \"./helpdoc\";\nimport { type AstNode, parse as doParse } from \"./newparser/parser\";\nimport { tokenize } from \"./newparser/tokenizer\";\n\nexport type Handling<Values, Result> = { handler: (values: Values) => Result };\n\nexport type Runner<HandlerArgs, HandlerResult> = PrintHelp &\n\tPartial<Versioned> &\n\tRegister &\n\tHandling<HandlerArgs, HandlerResult> &\n\tArgParser<HandlerArgs> & {\n\t\trun(context: ParseContext): Promise<ParsingResult<HandlerResult>>;\n\t};\n\nexport type Into<R extends Runner<any, any>> = R extends Runner<any, infer X>\n\t? X\n\t: never;\n\nexport async function run<R extends Runner<any, any>>(\n\tap: R,\n\tstrings: string[],\n): Promise<Into<R>> {\n\tconst result = await runSafely(ap, strings);\n\tif (isErr(result)) {\n\t\treturn result.error.run();\n\t}\n\treturn result.value;\n}\n\n/**\n * Runs a command but does not apply any effect\n */\nexport async function runSafely<R extends Runner<any, any>>(\n\tap: R,\n\tstrings: string[],\n): Promise<Result<Exit, Into<R>>> {\n\tconst hotPath: string[] = [];\n\tconst nodes = parseCommon(ap, strings);\n\n\ttry {\n\t\tconst result = await ap.run({ nodes, visitedNodes: new Set(), hotPath });\n\n\t\tif (isErr(result)) {\n\t\t\tthrow new Exit({\n\t\t\t\tmessage: errorBox(nodes, result.error.errors, hotPath),\n\t\t\t\texitCode: 1,\n\t\t\t\tinto: \"stderr\",\n\t\t\t});\n\t\t}\n\t\treturn ok(result.value);\n\t} catch (e) {\n\t\tif (e instanceof Exit) {\n\t\t\treturn err(e);\n\t\t}\n\t\tthrow e;\n\t}\n}\n\n/**\n * Run a command but don't quit. Returns an `Result` instead.\n */\nexport async function dryRun<R extends Runner<any, any>>(\n\tap: R,\n\tstrings: string[],\n): Promise<Result<string, Into<R>>> {\n\tconst result = await runSafely(ap, strings);\n\tif (isErr(result)) {\n\t\treturn err(result.error.dryRun());\n\t}\n\treturn result;\n}\n\n/**\n * Parse the command as if to run it, but only return the parse result and don't run the command.\n */\nexport function parse<R extends Runner<any, any>>(\n\tap: R,\n\tstrings: string[],\n): Promise<ParsingResult<any>> {\n\tconst hotPath: string[] = [];\n\tconst nodes = parseCommon(ap, strings);\n\treturn ap.parse({ nodes, visitedNodes: new Set(), hotPath });\n}\n\nfunction parseCommon<R extends Runner<any, any>>(\n\tap: R,\n\tstrings: string[],\n): AstNode[] {\n\tconst longFlagKeys = new Set<string>();\n\tconst shortFlagKeys = new Set<string>();\n\tconst longOptionKeys = new Set<string>();\n\tconst shortOptionKeys = new Set<string>();\n\tconst registerContext = {\n\t\tforceFlagShortNames: shortFlagKeys,\n\t\tforceFlagLongNames: longFlagKeys,\n\t\tforceOptionShortNames: shortOptionKeys,\n\t\tforceOptionLongNames: longOptionKeys,\n\t};\n\n\tap.register(registerContext);\n\n\tconst tokens = tokenize(strings);\n\treturn doParse(tokens, registerContext);\n}\n"
  },
  {
    "path": "src/subcommands.ts",
    "content": "import chalk from \"chalk\";\nimport didYouMean from \"didyoumean\";\nimport * as Result from \"./Result\";\n// import { Runner, Into } from './runner';\nimport type {\n\tArgParser,\n\tParseContext,\n\tParsingInto,\n\tParsingResult,\n} from \"./argparser\";\nimport { createCircuitBreaker, handleCircuitBreaker } from \"./circuitbreaker\";\nimport type { From } from \"./from\";\nimport {\n\ttype Example,\n\ttype SubcommandsHelpData,\n\tgetHelpFormatter,\n} from \"./helpFormatter\";\nimport type {\n\tAliased,\n\tDescriptive,\n\tNamed,\n\tProvidesHelp,\n\tVersioned,\n} from \"./helpdoc\";\nimport { positional } from \"./positional\";\nimport type { Runner } from \"./runner\";\n\ntype Output<\n\tCommands extends Record<string, ArgParser<any> & Runner<any, any>>,\n> = {\n\t[key in keyof Commands]: { command: key; args: ParsingInto<Commands[key]> };\n}[keyof Commands];\n\ntype RunnerOutput<\n\tCommands extends Record<string, Runner<any, any> & ArgParser<any>>,\n> = {\n\t[key in keyof Commands]: {\n\t\tcommand: key;\n\t\tvalue: Commands[key] extends Runner<any, infer X> ? X : never;\n\t};\n}[keyof Commands];\n\n/**\n * Combine multiple `command`s into one\n */\nexport function subcommands<\n\tCommands extends Record<\n\t\tstring,\n\t\tArgParser<any> &\n\t\t\tRunner<any, any> &\n\t\t\tPartial<Descriptive & Aliased & ProvidesHelp>\n\t>,\n>(config: {\n\tname: string;\n\tversion?: string;\n\tcmds: Commands;\n\tdescription?: string;\n\t/** Examples to show in help output */\n\texamples?: Example[];\n}): ArgParser<Output<Commands>> &\n\tNamed &\n\tPartial<Descriptive & Versioned> &\n\tRunner<Output<Commands>, RunnerOutput<Commands>> {\n\tconst circuitbreaker = createCircuitBreaker(!!config.version);\n\tconst type: From<string, keyof Commands> = {\n\t\tasync from(str) {\n\t\t\tconst commands = Object.entries(config.cmds).map(([name, cmd]) => {\n\t\t\t\treturn {\n\t\t\t\t\tcmdName: name as keyof Commands,\n\t\t\t\t\tnames: [name, ...(cmd.aliases ?? [])],\n\t\t\t\t};\n\t\t\t});\n\t\t\tconst cmd = commands.find((x) => x.names.includes(str));\n\t\t\tif (cmd) {\n\t\t\t\treturn cmd.cmdName;\n\t\t\t}\n\t\t\tlet errorMessage = \"Not a valid subcommand name\";\n\n\t\t\tconst closeOptions = didYouMean(\n\t\t\t\tstr,\n\t\t\t\tflatMap(commands, (x) => x.names),\n\t\t\t);\n\t\t\tif (closeOptions) {\n\t\t\t\tconst option = Array.isArray(closeOptions)\n\t\t\t\t\t? closeOptions[0]\n\t\t\t\t\t: closeOptions;\n\t\t\t\terrorMessage += `\\nDid you mean ${chalk.italic(option)}?`;\n\t\t\t}\n\n\t\t\tthrow new Error(errorMessage);\n\t\t},\n\t};\n\n\tconst subcommand = positional({\n\t\tdisplayName: \"subcommand\",\n\t\tdescription: `one of ${Object.keys(config.cmds).join(\", \")}`,\n\t\ttype,\n\t});\n\n\tfunction normalizeContext(context: ParseContext) {\n\t\tif (context.hotPath?.length === 0) {\n\t\t\tcontext.hotPath.push(config.name);\n\t\t}\n\n\t\t// Called without any arguments? We default to subcommand help.\n\t\tif (!context.nodes.some((n) => !context.visitedNodes.has(n))) {\n\t\t\tcontext.nodes.push({\n\t\t\t\ttype: \"longOption\",\n\t\t\t\tindex: 0,\n\t\t\t\tkey: \"help\",\n\t\t\t\traw: \"--help\",\n\t\t\t});\n\t\t}\n\t}\n\n\treturn {\n\t\tversion: config.version,\n\t\tdescription: config.description,\n\t\tname: config.name,\n\t\thandler: (value) => {\n\t\t\tconst cmd = config.cmds[value.command];\n\t\t\treturn cmd.handler(value.args);\n\t\t},\n\t\tregister(opts) {\n\t\t\tfor (const cmd of Object.values(config.cmds)) {\n\t\t\t\tcmd.register(opts);\n\t\t\t}\n\t\t\tcircuitbreaker.register(opts);\n\t\t},\n\t\tprintHelp(context) {\n\t\t\tconst data: SubcommandsHelpData = {\n\t\t\t\tname: config.name,\n\t\t\t\tpath: context.hotPath ?? [config.name],\n\t\t\t\tversion: config.version,\n\t\t\t\tdescription: config.description,\n\t\t\t\tcommands: Object.entries(config.cmds).map(([name, cmd]) => ({\n\t\t\t\t\tname,\n\t\t\t\t\tdescription: cmd.description,\n\t\t\t\t\taliases: cmd.aliases,\n\t\t\t\t\thelpTopics: cmd.helpTopics?.() ?? [],\n\t\t\t\t})),\n\t\t\t\texamples: config.examples,\n\t\t\t};\n\t\t\treturn getHelpFormatter().formatSubcommands(data, context);\n\t\t},\n\t\tasync parse(\n\t\t\tcontext: ParseContext,\n\t\t): Promise<ParsingResult<Output<Commands>>> {\n\t\t\tnormalizeContext(context);\n\t\t\tconst parsed = await subcommand.parse(context);\n\n\t\t\tif (Result.isErr(parsed)) {\n\t\t\t\treturn Result.err({\n\t\t\t\t\terrors: parsed.error.errors,\n\t\t\t\t\tpartialValue: {},\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tcontext.hotPath?.push(parsed.value as string);\n\n\t\t\tconst cmd = config.cmds[parsed.value];\n\t\t\tconst parsedCommand = await cmd.parse(context);\n\t\t\tif (Result.isErr(parsedCommand)) {\n\t\t\t\treturn Result.err({\n\t\t\t\t\terrors: parsedCommand.error.errors,\n\t\t\t\t\tpartialValue: {\n\t\t\t\t\t\tcommand: parsed.value,\n\t\t\t\t\t\targs: parsedCommand.error.partialValue,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn Result.ok({\n\t\t\t\targs: parsedCommand.value,\n\t\t\t\tcommand: parsed.value,\n\t\t\t});\n\t\t},\n\t\tasync run(context): Promise<ParsingResult<RunnerOutput<Commands>>> {\n\t\t\tnormalizeContext(context);\n\t\t\tconst parsedSubcommand = await subcommand.parse(context);\n\n\t\t\tif (Result.isErr(parsedSubcommand)) {\n\t\t\t\tconst breaker = await circuitbreaker.parse(context);\n\t\t\t\thandleCircuitBreaker(context, this, breaker);\n\n\t\t\t\treturn Result.err({ ...parsedSubcommand.error, partialValue: {} });\n\t\t\t}\n\n\t\t\tcontext.hotPath?.push(parsedSubcommand.value as string);\n\n\t\t\tconst cmd = config.cmds[parsedSubcommand.value];\n\t\t\tconst commandRun = await cmd.run(context);\n\n\t\t\tif (Result.isOk(commandRun)) {\n\t\t\t\treturn Result.ok({\n\t\t\t\t\tcommand: parsedSubcommand.value,\n\t\t\t\t\tvalue: commandRun.value,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn Result.err({\n\t\t\t\t...commandRun.error,\n\t\t\t\tpartialValue: {\n\t\t\t\t\tcommand: parsedSubcommand.value,\n\t\t\t\t\tvalue: commandRun.error.partialValue,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t};\n}\n\nfunction flatMap<T, R>(array: T[], f: (t: T) => R[]): R[] {\n\tconst rs: R[] = [];\n\tfor (const item of array) {\n\t\trs.push(...f(item));\n\t}\n\treturn rs;\n}\n"
  },
  {
    "path": "src/type.ts",
    "content": "import type { Default, OnMissing } from \"./default\";\nimport type { From, FromFn, InputOf, OutputOf } from \"./from\";\nimport type { Descriptive, Displayed } from \"./helpdoc\";\n\nexport { identity, type OutputOf, type InputOf } from \"./from\";\n\nexport type Type<From_, To> = From<From_, To> &\n\tPartial<Descriptive & Displayed & Default<To> & OnMissing<To>>;\n\n/**\n * Get the type definitions or an empty object from a type or a decoding function\n */\nexport function typeDef<T extends From<any, any> | FromFn<any, any>>(\n\tfrom: T,\n): T extends FromFn<any, any> ? {} : Omit<T, \"from\"> {\n\tif (typeof from === \"function\") {\n\t\treturn {} as any;\n\t}\n\treturn from as any;\n}\n\n/**\n * Get the decoding function from a type or a function\n */\nexport function fromFn<A, B>(t: FromFn<A, B> | From<A, B>): FromFn<A, B> {\n\tif (typeof t === \"function\") {\n\t\treturn t;\n\t}\n\treturn t.from;\n}\n\n/**\n * Extend a type: take a type and use it as a base for another type. Much like using the spread operator:\n * ```\n * const newType = { ...oldType }\n * ```\n * but composes the `from` arguments\n *\n * @param base A base type from `InputA` to `OutputA`\n * @param nextTypeOrDecodingFunction Either an entire `Type<OutputA, AnyOutput>` or just a decoding function from `OutputA` to any type\n */\nexport function extendType<\n\tBaseType extends Type<any, any>,\n\tNextType extends\n\t\t| Type<OutputOf<BaseType>, any>\n\t\t| FromFn<OutputOf<BaseType>, any>,\n>(\n\tbase: BaseType,\n\tnextTypeOrDecodingFunction: NextType,\n): Omit<BaseType, \"from\" | \"defaultValue\" | \"onMissing\"> &\n\t(NextType extends FromFn<any, any> ? unknown : Omit<NextType, \"from\">) &\n\tFrom<InputOf<BaseType>, OutputOf<NextType>> {\n\tconst {\n\t\tdefaultValue: _defaultValue,\n\t\tonMissing: _onMissing,\n\t\tfrom: _from,\n\t\t...t1WithoutDefault\n\t} = base;\n\tconst t2Object = typeDef(nextTypeOrDecodingFunction);\n\tconst t2From = fromFn(nextTypeOrDecodingFunction);\n\n\treturn {\n\t\t...t1WithoutDefault,\n\t\t...t2Object,\n\t\tasync from(a) {\n\t\t\tconst f1Result = await base.from(a);\n\t\t\treturn await t2From(f1Result);\n\t\t},\n\t};\n}\n\n/** Contains a type definition inside */\nexport type HasType<T extends Type<any, any>> = {\n\t/** The value decoding strategy for this item */\n\ttype: T;\n};\n"
  },
  {
    "path": "src/types.ts",
    "content": "import { type InputOf, type OutputOf, type Type, identity } from \"./type\";\n\n/**\n * A number type to be used with `option`\n *\n * Throws an error when the provided string is not a number\n */\nexport const number: Type<string, number> = {\n\tasync from(str) {\n\t\tconst decoded = Number.parseFloat(str);\n\n\t\tif (Number.isNaN(decoded)) {\n\t\t\tthrow new Error(\"Not a number\");\n\t\t}\n\t\treturn decoded;\n\t},\n\tdisplayName: \"number\",\n\tdescription: \"a number\",\n};\n\n/**\n * A string type to be used with `option`.\n */\nexport const string: Type<string, string> = {\n\t...identity(),\n\tdescription: \"a string\",\n\tdisplayName: \"str\",\n};\n\n/**\n * A boolean type to be used with `flag`.\n */\nexport const boolean: Type<boolean, boolean> = {\n\t...identity(),\n\tdescription: \"a boolean\",\n\tdisplayName: \"true/false\",\n\tdefaultValue() {\n\t\treturn false;\n\t},\n};\n\n/**\n * Makes any type optional, by defaulting to `undefined`.\n */\nexport function optional<T extends Type<any, any>>(\n\tt: T,\n): Type<InputOf<T>, OutputOf<T> | undefined> {\n\treturn {\n\t\t...t,\n\t\tdefaultValue(): OutputOf<T> | undefined {\n\t\t\treturn undefined;\n\t\t},\n\t};\n}\n\n/**\n * Transforms any type into an array, useful for `multioption` and `multiflag`.\n */\n\nexport function array<T extends Type<any, any>>(\n\tt: T,\n): Type<InputOf<T>[], OutputOf<T>[]> {\n\treturn {\n\t\t...t,\n\t\tasync from(inputs: InputOf<T>[]): Promise<OutputOf<T>[]> {\n\t\t\treturn Promise.all(inputs.map((input) => t.from(input)));\n\t\t},\n\t};\n}\n"
  },
  {
    "path": "src/union.ts",
    "content": "import * as Result from \"./Result\";\nimport type { From, FromFn, InputOf, OutputOf } from \"./from\";\nimport { type Type, fromFn, typeDef } from \"./type\";\n\ntype Any<A = any> = FromFn<A, any> | From<A, any>;\n\n/**\n * Take one of the types. Merge the metadata from left to right.\n * If nothing matches, prints all the errors.\n */\nexport function union<T1 extends Any, T2s extends Any<InputOf<T1>>>(\n\tts: [T1, ...T2s[]],\n\t{\n\t\tcombineErrors = (errors) => errors.join(\"\\n\"),\n\t}: {\n\t\t/**\n\t\t * Combine all the errors produced by the types.\n\t\t * Defaults to joining them with a newline.\n\t\t */\n\t\tcombineErrors?(errors: string[]): string;\n\t} = {},\n): Type<InputOf<T1>, OutputOf<T1 | T2s>> {\n\tconst merged = Object.assign({}, ...ts.map((x) => typeDef(x)));\n\treturn {\n\t\t...merged,\n\t\tasync from(input) {\n\t\t\tconst errors: string[] = [];\n\n\t\t\tfor (const t of ts) {\n\t\t\t\tconst decoded = await Result.safeAsync(fromFn(t)(input));\n\t\t\t\tif (Result.isOk(decoded)) {\n\t\t\t\t\treturn decoded.value;\n\t\t\t\t}\n\t\t\t\terrors.push(decoded.error.message);\n\t\t\t}\n\n\t\t\tthrow new Error(combineErrors(errors));\n\t\t},\n\t};\n}\n"
  },
  {
    "path": "src/utils.ts",
    "content": "import stripAnsi from \"strip-ansi\";\n\n/**\n * @ignore\n */\nexport function padNoAnsi(\n\tstr: string,\n\tlength: number,\n\tplace: \"end\" | \"start\",\n): string {\n\tconst noAnsiStr = stripAnsi(str);\n\tif (length < noAnsiStr.length) return str;\n\tconst pad = Array(length - noAnsiStr.length + 1).join(\" \");\n\tif (place === \"end\") {\n\t\treturn str + pad;\n\t}\n\treturn pad + str;\n}\n\n/**\n * Group an array by a function that returns the key\n *\n * @ignore\n */\nexport function groupBy<A, B extends string>(\n\tobjs: A[],\n\tf: (a: A) => B,\n): Record<B, A[]> {\n\tconst result = {} as Record<B, A[]>;\n\tfor (const obj of objs) {\n\t\tconst key = f(obj);\n\t\tresult[key] = result[key] ?? [];\n\t\tresult[key].push(obj);\n\t}\n\treturn result;\n}\n\n/**\n * A better typed version of `Object.entries`\n *\n * @ignore\n */\nexport function entries<Obj extends Record<string, any>>(\n\tobj: Obj,\n): { [key in keyof Obj]: [key, Obj[key]] }[keyof Obj][] {\n\treturn Object.entries(obj);\n}\n\n/**\n * Enumerate over a list, to get a pair of [index, value]\n *\n * @ignore\n */\nexport function* enumerate<T>(arr: T[]): Generator<[number, T]> {\n\tfor (let i = 0; i < arr.length; i++) {\n\t\tyield [i, arr[i]];\n\t}\n}\n\n/**\n * Array#flatMap polyfill\n *\n * @ignore\n */\nexport function flatMap<A, B>(xs: A[], fn: (a: A) => B[]): B[] {\n\tconst results: B[] = [];\n\tfor (const x of xs) {\n\t\tresults.push(...fn(x));\n\t}\n\treturn results;\n}\n\n/**\n * Flatten an array\n *\n * @ignore\n */\nexport function flatten<A>(xs: A[][]): A[] {\n\tconst results: A[] = [];\n\tfor (const x of xs) {\n\t\tresults.push(...x);\n\t}\n\treturn results;\n}\n\n/**\n * Either the provided `T` or an empty object\n */\nexport type AllOrNothing<T> = T | { [key in keyof T]?: never };\n"
  },
  {
    "path": "test/__snapshots__/ui.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`allows positional arguments > expects the correct order 1`] = `\n\"\u001b[31m\u001b[1merror: \u001b[22m\u001b[39mfound \u001b[33m1\u001b[39m error\n\n  \u001b[2msub2\u001b[22m \u001b[31mhello\u001b[39m\n\u001b[31m       \u001b[1m^\u001b[22m Not a number\u001b[39m\n\n\n\u001b[31m\u001b[1mhint: \u001b[22m\u001b[39mfor more information, try '\u001b[33msubcmds sub2 --help\u001b[39m'\"\n`;\n\nexports[`allows positional arguments > help shows them 1`] = `\n\"\u001b[1msubcmds sub2\u001b[22m\n\nARGUMENTS:\n  [number] - a number\u001b[2m [optional]\u001b[22m\n  [str]    - a string\u001b[2m [default: \u001b[3manonymous\u001b[23m]\u001b[22m\n\nFLAGS:\n  --help, -h - show help\u001b[2m [optional]\u001b[22m\"\n`;\n\nexports[`asynchronous type conversion works for failures 1`] = `\n\"\u001b[31m\u001b[1merror: \u001b[22m\u001b[39mfound \u001b[33m1\u001b[39m error\n\n  \u001b[2msubcmds\u001b[22m \u001b[2mcomposed\u001b[22m \u001b[2mcat\u001b[22m \u001b[31mhttps://mock.httpstatus.io/404\u001b[39m\n\u001b[31m                       \u001b[1m^\u001b[22m Got status Not Found 404 reading URL\u001b[39m\n\n\n\u001b[31m\u001b[1mhint: \u001b[22m\u001b[39mfor more information, try '\u001b[33msubcmds composed cat --help\u001b[39m'\"\n`;\n\nexports[`asynchronous type conversion works for success 1`] = `\"200 OK\"`;\n\nexports[`composes errors 1`] = `\n\"\u001b[31m\u001b[1merror: \u001b[22m\u001b[39mfound \u001b[33m3\u001b[39m errors\n\n  \u001b[2msubcmds\u001b[22m \u001b[2mgreet\u001b[22m \u001b[31m--times=not-a-number\u001b[39m \u001b[2mnot-capitalized\u001b[22m\n\u001b[31m                \u001b[1m^\u001b[22m Not a number\u001b[39m\n\n  \u001b[2msubcmds\u001b[22m \u001b[2mgreet\u001b[22m \u001b[2m--times=not-a-number\u001b[22m \u001b[31mnot-capitalized\u001b[39m\n\u001b[31m                                     \u001b[1m^\u001b[22m name must be capitalized\u001b[39m\n\nAlong with the following error:\n  \u001b[31m\u001b[1m3.\u001b[22m\u001b[39m \u001b[31mNo value provided for --greeting\u001b[39m\n\n\u001b[31m\u001b[1mhint: \u001b[22m\u001b[39mfor more information, try '\u001b[33msubcmds greet --help\u001b[39m'\"\n`;\n\nexports[`displays nested subcommand help if no arguments passed 1`] = `\n\"\u001b[1msubcmds composed\u001b[3m <subcommand>\u001b[23m\u001b[22m\n\u001b[2m> \u001b[22ma nested subcommand\n\nwhere \u001b[3m<subcommand>\u001b[23m can be one of:\n\n\u001b[2m- \u001b[22mcat - A simple \\`cat\\` clone \u001b[2m[alias: read]\u001b[22m\n\n\u001b[2mFor more help, try running \\`\u001b[33msubcmds composed <subcommand> --help\u001b[39m\\`\u001b[22m\"\n`;\n\nexports[`displays subcommand help if no arguments passed 1`] = `\n\"\u001b[1msubcmds\u001b[3m <subcommand>\u001b[23m\u001b[22m\n\u001b[2m> \u001b[22mAn awesome subcommand app!\n\nwhere \u001b[3m<subcommand>\u001b[23m can be one of:\n\n\u001b[2m- \u001b[22mcomplex - Just prints the arguments\n\u001b[2m- \u001b[22mcat - A simple \\`cat\\` clone \u001b[2m[alias: read]\u001b[22m\n\u001b[2m- \u001b[22mgreet - greet a person\n\u001b[2m- \u001b[22mcomposed - a nested subcommand\n\n\u001b[2mFor more help, try running \\`\u001b[33msubcmds <subcommand> --help\u001b[39m\\`\u001b[22m\n\nEXAMPLES:\n\n  Show help for a command\n\u001b[2m  $ subcmds greet --help\u001b[22m\n\n  Run the cat command\n\u001b[2m  $ subcmds cat ./file.txt\u001b[22m\"\n`;\n\nexports[`failures in defaultValue 1`] = `\n\"\u001b[31m\u001b[1merror: \u001b[22m\u001b[39mfound \u001b[33m3\u001b[39m errors\n\n  \u001b[31m\u001b[1m1.\u001b[22m\u001b[39m \u001b[31mNo value provided for --user\u001b[39m\n  \u001b[31m\u001b[1m2.\u001b[22m\u001b[39m \u001b[31mNo value provided for --password\u001b[39m\n  \u001b[31m\u001b[1m3.\u001b[22m\u001b[39m \u001b[31mDefault value not found for '--repo': Can't infer repo from git\u001b[39m\n\n\u001b[31m\u001b[1mhint: \u001b[22m\u001b[39mfor more information, try '\u001b[33mbuild --help\u001b[39m'\"\n`;\n\nexports[`help for complex command 1`] = `\n\"\u001b[1msubcmds complex\u001b[22m \u001b[2m6.6.6-alpha\u001b[22m\n\u001b[2m> \u001b[22mJust prints the arguments\n\nARGUMENTS:\n  <pos1>   - a number\n  <str>    - a string\n  [...str] - a string\n\nOPTIONS:\n  --number, -n <number>         - a number\n  --int-or-string <str>         - a string\n  --float-or-string <str>       - a string\n  --optional-option <str>       - a string\u001b[2m [optional]\u001b[22m\n  --no-type-option <str>        - a string\n  --optional-with-default <str> - a string\u001b[2m [env: SOME_ENV_VAR]\u001b[22m\u001b[2m [default: \u001b[3mHello\u001b[23m]\u001b[22m\n\nFLAGS:\n  --boolean           - a boolean\u001b[2m [optional]\u001b[22m\n  --bool-without-type - a boolean\u001b[2m [optional]\u001b[22m\n  --help, -h          - show help\u001b[2m [optional]\u001b[22m\n  --version, -v       - print the version\u001b[2m [optional]\u001b[22m\n\nEXAMPLES:\n\n  Print with required args\n\u001b[2m  $ complex 42 hello -n 10 --int-or-string 5 --float-or-string 3.14\u001b[22m\n\n  Using a string for int-or-string\n\u001b[2m  $ complex 1 world -n 1 --int-or-string foo --float-or-string bar\u001b[22m\"\n`;\n\nexports[`help for composed subcommand 1`] = `\n\"\u001b[1msubcmds composed cat\u001b[22m\n\u001b[2m> \u001b[22mA simple \\`cat\\` clone\n\nARGUMENTS:\n  <stream>    - A file path or a URL to make a GET request to\n  [...stream] - A file path or a URL to make a GET request to\n\nFLAGS:\n  --help, -h - show help\u001b[2m [optional]\u001b[22m\"\n`;\n\nexports[`help for composed subcommands 1`] = `\n\"\u001b[1msubcmds composed\u001b[3m <subcommand>\u001b[23m\u001b[22m\n\u001b[2m> \u001b[22ma nested subcommand\n\nwhere \u001b[3m<subcommand>\u001b[23m can be one of:\n\n\u001b[2m- \u001b[22mcat - A simple \\`cat\\` clone \u001b[2m[alias: read]\u001b[22m\n\n\u001b[2mFor more help, try running \\`\u001b[33msubcmds composed <subcommand> --help\u001b[39m\\`\u001b[22m\"\n`;\n\nexports[`help for subcommands 1`] = `\n\"\u001b[1msubcmds\u001b[3m <subcommand>\u001b[23m\u001b[22m\n\u001b[2m> \u001b[22mAn awesome subcommand app!\n\nwhere \u001b[3m<subcommand>\u001b[23m can be one of:\n\n\u001b[2m- \u001b[22mcomplex - Just prints the arguments\n\u001b[2m- \u001b[22mcat - A simple \\`cat\\` clone \u001b[2m[alias: read]\u001b[22m\n\u001b[2m- \u001b[22mgreet - greet a person\n\u001b[2m- \u001b[22mcomposed - a nested subcommand\n\n\u001b[2mFor more help, try running \\`\u001b[33msubcmds <subcommand> --help\u001b[39m\\`\u001b[22m\n\nEXAMPLES:\n\n  Show help for a command\n\u001b[2m  $ subcmds greet --help\u001b[22m\n\n  Run the cat command\n\u001b[2m  $ subcmds cat ./file.txt\u001b[22m\"\n`;\n\nexports[`help shows onMissing option 1`] = `\n\"\u001b[1masync-test\u001b[22m\n\nOPTIONS:\n  --async-arg, -a <str> - A type with onMissing callback\u001b[2m [optional]\u001b[22m\n  --async-arg-2 <str>   - A type with onMissing callback\u001b[2m [default: \u001b[3mHi\u001b[23m]\u001b[22m\n  --async-arg-3 <str>   - a string\u001b[2m [optional]\u001b[22m\n\nFLAGS:\n  --help, -h - show help\u001b[2m [optional]\u001b[22m\"\n`;\n\nexports[`invalid subcommand 1`] = `\n\"\u001b[31m\u001b[1merror: \u001b[22m\u001b[39mfound \u001b[33m1\u001b[39m error\n\n  \u001b[2msubcmds\u001b[22m \u001b[31msubcommand-that-doesnt-exist\u001b[39m\n\u001b[31m          \u001b[1m^\u001b[22m Not a valid subcommand name\u001b[39m\n\n\n\u001b[31m\u001b[1mhint: \u001b[22m\u001b[39mfor more information, try '\u001b[33msubcmds --help\u001b[39m'\"\n`;\n\nexports[`multiline error 1`] = `\n\"\u001b[31m\u001b[1merror: \u001b[22m\u001b[39mfound \u001b[33m2\u001b[39m errors\n\n  \u001b[2msubcmds\u001b[22m \u001b[2mgreet\u001b[22m \u001b[31mBon Jovi\u001b[39m\n\u001b[31m                \u001b[1m^\u001b[22m Woah, we're half way there\u001b[39m\n\u001b[31m                  Woah! living on a prayer!\u001b[39m\n\nAlong with the following error:\n  \u001b[31m\u001b[1m2.\u001b[22m\u001b[39m \u001b[31mNo value provided for --greeting\u001b[39m\n\n\u001b[31m\u001b[1mhint: \u001b[22m\u001b[39mfor more information, try '\u001b[33msubcmds greet --help\u001b[39m'\"\n`;\n\nexports[`onMissing failure 1`] = `\n\"\u001b[31m\u001b[1merror: \u001b[22m\u001b[39mfound \u001b[33m1\u001b[39m error\n\n  \u001b[31m\u001b[1m1.\u001b[22m\u001b[39m \u001b[31mFailed to get missing value for '--fail-arg': Async onMissing failed\u001b[39m\n\n\u001b[31m\u001b[1mhint: \u001b[22m\u001b[39mfor more information, try '\u001b[33masync-test-failure --help\u001b[39m'\"\n`;\n\nexports[`subcommands show their version 1`] = `\"1.0.0\"`;\n\nexports[`subcommands with process.argv.slice(2) 1`] = `\n\"\u001b[1msubcmds\u001b[3m <subcommand>\u001b[23m\u001b[22m\n\nwhere \u001b[3m<subcommand>\u001b[23m can be one of:\n\n\u001b[2m- \u001b[22msub1\n\u001b[2m- \u001b[22msub2\n\n\u001b[2mFor more help, try running \\`\u001b[33msubcmds <subcommand> --help\u001b[39m\\`\u001b[22m\"\n`;\n\nexports[`suggests a subcommand on typo 1`] = `\n\"\u001b[31m\u001b[1merror: \u001b[22m\u001b[39mfound \u001b[33m1\u001b[39m error\n\n  \u001b[2msubcmds\u001b[22m \u001b[31mgreek\u001b[39m\n\u001b[31m          \u001b[1m^\u001b[22m Not a valid subcommand name\u001b[39m\n\u001b[31m            Did you mean \u001b[3mgreet\u001b[23m?\u001b[39m\n\n\n\u001b[31m\u001b[1mhint: \u001b[22m\u001b[39mfor more information, try '\u001b[33msubcmds --help\u001b[39m'\"\n`;\n\nexports[`too many arguments 1`] = `\n\"\u001b[31m\u001b[1merror: \u001b[22m\u001b[39mfound \u001b[33m1\u001b[39m error\n\n  \u001b[2msubcmds\u001b[22m \u001b[31m--this=will-be-an-error\u001b[39m \u001b[2mcat\u001b[22m \u001b[2mpackage.json\u001b[22m \u001b[31m--and-also-this\u001b[39m\n\u001b[31m          \u001b[1m^\u001b[22m Unknown arguments\u001b[39m\n\n\n\u001b[31m\u001b[1mhint: \u001b[22m\u001b[39mfor more information, try '\u001b[33msubcmds cat --help\u001b[39m'\"\n`;\n"
  },
  {
    "path": "test/__snapshots__/vercel-formatter.test.ts.snap",
    "content": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`vercelFormatter > formats command help 1`] = `\n\"create 1.0.0\n\n▲ create [options]\n\nCreate a new sandbox\n\nFlags:\n\n    --connect, -c  Connect to the sandbox after creating [optional]\n    --help, -h     show help [optional]\n    --version, -v  print the version [optional]\n\nArguments:\n\n    <name>  Name for the sandbox\n\nExamples:\n\n– Create a sandbox named 'dev'\n\n  $ create dev\n\n– Create and connect\n\n  $ create dev --connect\n\"\n`;\n\nexports[`vercelFormatter > formats subcommands help with version and logo 1`] = `\n\"Vercel Sandbox CLI 2.3.0\n\n▲ sandbox [options] <command>\n\nFor command help, run \\`sandbox <command> --help\\`\n\nCommands:\n\n    create                  Create a new sandbox\n    ls | list               List sandboxes for the current project\n    run        <cmd>        Create and run a command in a sandbox\n    rm | stop  <id...>      Stop one or more running sandboxes\n    cp | copy  <src> <dst>  Copy files between local and remote\n\nExamples:\n\n– Create a sandbox and start a shell\n\n  $ sandbox create --connect\n\n– Run a command in a new sandbox\n\n  $ sandbox run -- node -e \"console.log('hello')\"\n\"\n`;\n"
  },
  {
    "path": "test/command.test.ts",
    "content": "import { expect, test } from \"vitest\";\nimport * as Result from \"../src/Result\";\nimport { command } from \"../src/command\";\nimport { flag } from \"../src/flag\";\nimport { parse } from \"../src/newparser/parser\";\nimport { tokenize } from \"../src/newparser/tokenizer\";\nimport { option } from \"../src/option\";\nimport { restPositionals } from \"../src/restPositionals\";\nimport { createRegisterOptions } from \"./createRegisterOptions\";\nimport { boolean, number, string } from \"./test-types\";\n\nconst cmd = command({\n\tname: \"My command\",\n\targs: {\n\t\tpositionals: restPositionals({ type: string }),\n\t\toption: option({ type: number, long: \"option\" }),\n\t\tsecondOption: option({\n\t\t\ttype: string,\n\t\t\tlong: \"second-option\",\n\t\t}),\n\t\tflag: flag({ type: boolean, long: \"flag\" }),\n\t},\n\thandler: (_) => {},\n});\n\ntest(\"merges options, positionals and flags\", async () => {\n\tconst argv =\n\t\t\"first --option=666 second --second-option works-too --flag third\".split(\n\t\t\t\" \",\n\t\t);\n\tconst tokens = tokenize(argv);\n\n\tconst registerOptions = createRegisterOptions();\n\tcmd.register(registerOptions);\n\n\tconst nodes = parse(tokens, registerOptions);\n\tconst result = await cmd.parse({ nodes, visitedNodes: new Set() });\n\tconst expected: typeof result = Result.ok({\n\t\tpositionals: [\"first\", \"second\", \"third\"],\n\t\toption: 666,\n\t\tsecondOption: \"works-too\",\n\t\tflag: true,\n\t});\n\n\texpect(result).toEqual(expected);\n});\n\ntest(\"fails if an argument fail to parse\", async () => {\n\tconst argv =\n\t\t\"first --option=hello second --second-option works-too --flag=fails-too third\".split(\n\t\t\t\" \",\n\t\t);\n\tconst tokens = tokenize(argv);\n\n\tconst registerOptions = createRegisterOptions();\n\n\tcmd.register(registerOptions);\n\n\tconst nodes = parse(tokens, registerOptions);\n\tconst result = cmd.parse({\n\t\tnodes,\n\t\tvisitedNodes: new Set(),\n\t});\n\n\tawait expect(result).resolves.toEqual(\n\t\tResult.err({\n\t\t\terrors: [\n\t\t\t\t{\n\t\t\t\t\tnodes: nodes.filter((x) => x.raw.startsWith(\"--option\")),\n\t\t\t\t\tmessage: \"Not a number\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tnodes: nodes.filter((x) => x.raw.startsWith(\"--flag\")),\n\t\t\t\t\tmessage: `expected value to be either \"true\" or \"false\". got: \"fails-too\"`,\n\t\t\t\t},\n\t\t\t],\n\t\t\tpartialValue: {\n\t\t\t\tpositionals: [\"first\", \"second\", \"third\"],\n\t\t\t\tsecondOption: \"works-too\",\n\t\t\t},\n\t\t}),\n\t);\n});\n\ntest(\"fails if providing unknown arguments\", async () => {\n\tconst cmd = command({\n\t\tname: \"my command\",\n\t\targs: {\n\t\t\tpositionals: restPositionals({ type: string }),\n\t\t},\n\t\thandler: (_) => {},\n\t});\n\tconst argv = \"okay --option=failing alright --another=fail\".split(\" \");\n\tconst tokens = tokenize(argv);\n\n\tconst registerOptions = createRegisterOptions();\n\tcmd.register(registerOptions);\n\n\tconst nodes = parse(tokens, registerOptions);\n\tconst result = await cmd.parse({\n\t\tnodes,\n\t\tvisitedNodes: new Set(),\n\t});\n\n\texpect(result).toEqual(\n\t\tResult.err({\n\t\t\terrors: [\n\t\t\t\t{\n\t\t\t\t\tmessage: \"Unknown arguments\",\n\t\t\t\t\tnodes: nodes.filter(\n\t\t\t\t\t\t(node) =>\n\t\t\t\t\t\t\tnode.raw.startsWith(\"--option\") ||\n\t\t\t\t\t\t\tnode.raw.startsWith(\"--another\"),\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t],\n\t\t\tpartialValue: {\n\t\t\t\tpositionals: [\"okay\", \"alright\"],\n\t\t\t},\n\t\t}),\n\t);\n});\n\ntest(\"should fail run when an async handler fails\", async () => {\n\tconst error = new Error(\"oops\");\n\tconst cmd = command({\n\t\tname: \"my command\",\n\t\targs: {},\n\t\thandler: async (_) => {\n\t\t\tthrow error;\n\t\t},\n\t});\n\n\tawait expect(\n\t\tcmd.run({\n\t\t\tnodes: [],\n\t\t\tvisitedNodes: new Set(),\n\t\t}),\n\t).rejects.toEqual(error);\n});\n\ntest(\"succeeds when rest is quoted\", async () => {\n\t// since spliting this by space doesn't give us the expected result, I just built the array myself\n\t// const argv = `--option=666 --second-option works-too positional -- \"--restPositionals --trailing-comma all {{scripts,src}/**/*.{js,ts},{scripts,src}/*.{js,ts},*.{js,ts}}\"`;\n\tconst tokens = tokenize([\n\t\t\"--option=666\",\n\t\t\"--second-option\",\n\t\t\"works-too\",\n\t\t\"positional\",\n\t\t\"--\",\n\t\t\"--restPositionals --trailing-comma all {{scripts,src}/**/*.{js,ts},{scripts,src}/*.{js,ts},*.{js,ts}}\",\n\t]);\n\tconst registerOptions = createRegisterOptions();\n\tcmd.register(registerOptions);\n\tconst nodes = parse(tokens, registerOptions);\n\tconst result = cmd.parse({\n\t\tnodes,\n\t\tvisitedNodes: new Set(),\n\t});\n\n\tawait expect(result).resolves.toEqual(\n\t\tResult.ok({\n\t\t\tpositionals: [\n\t\t\t\t\"positional\",\n\t\t\t\t\"--restPositionals --trailing-comma all {{scripts,src}/**/*.{js,ts},{scripts,src}/*.{js,ts},*.{js,ts}}\",\n\t\t\t],\n\t\t\toption: 666,\n\t\t\tsecondOption: \"works-too\",\n\t\t\tflag: false,\n\t\t}),\n\t);\n});\n"
  },
  {
    "path": "test/createRegisterOptions.ts",
    "content": "import type { RegisterOptions } from \"../src/argparser\";\n\nexport function createRegisterOptions(): RegisterOptions {\n\treturn {\n\t\tforceFlagLongNames: new Set(),\n\t\tforceFlagShortNames: new Set(),\n\t\tforceOptionLongNames: new Set(),\n\t\tforceOptionShortNames: new Set(),\n\t};\n}\n"
  },
  {
    "path": "test/errorBox.test.ts",
    "content": "import chalk from \"chalk\";\nimport { expect, test } from \"vitest\";\nimport * as Result from \"../src/Result\";\nimport { errorBox } from \"../src/errorBox\";\nimport { parse } from \"../src/newparser/parser\";\nimport { tokenize } from \"../src/newparser/tokenizer\";\nimport { option } from \"../src/option\";\nimport { createRegisterOptions } from \"./createRegisterOptions\";\nimport { number } from \"./test-types\";\n\ntest(\"works for multiple nodes\", async () => {\n\tconst argv =\n\t\t\"hello world --some arg --flag --some another --flag --this-is=option -abcde=f -abcde\";\n\n\tconst tokens = tokenize(argv.split(\" \"));\n\tconst tree = parse(tokens, createRegisterOptions());\n\n\tconst opt = option({\n\t\ttype: number,\n\t\tlong: \"some\",\n\t});\n\n\tconst result = await opt.parse({\n\t\tnodes: tree,\n\t\tvisitedNodes: new Set(),\n\t});\n\n\tif (Result.isOk(result)) {\n\t\tthrow new Error(\"should fail...\");\n\t}\n\n\tconst errors = errorBox(tree, result.error.errors, []);\n\texpect(errors).toMatch(\"Too many times provided\");\n});\n\ntest(\"works for a short flag\", async () => {\n\tconst argv = \"hello world -fn not_a_number hey\";\n\n\tconst tokens = tokenize(argv.split(\" \"));\n\tconst tree = parse(tokens, createRegisterOptions());\n\n\tconst opt = option({\n\t\ttype: number,\n\t\tlong: \"some\",\n\t\tshort: \"n\",\n\t});\n\n\tconst result = await opt.parse({\n\t\tnodes: tree,\n\t\tvisitedNodes: new Set(),\n\t});\n\n\tif (Result.isOk(result)) {\n\t\tthrow new Error(\"should fail...\");\n\t}\n\n\tconst errors = errorBox(tree, result.error.errors, []);\n\texpect(errors).toMatch(chalk.red(\"n not_a_number\"));\n});\n\ntest(\"works for a single node\", async () => {\n\tconst argv =\n\t\t\"hello world --flag --some not_a_number --flag --this-is=option -abcde=f -abcde\";\n\n\tconst tokens = tokenize(argv.split(\" \"));\n\tconst tree = parse(tokens, createRegisterOptions());\n\n\tconst opt = option({\n\t\ttype: number,\n\t\tlong: \"some\",\n\t});\n\n\tconst result = await opt.parse({\n\t\tnodes: tree,\n\t\tvisitedNodes: new Set(),\n\t});\n\n\tif (Result.isOk(result)) {\n\t\tthrow new Error(\"should fail...\");\n\t}\n\n\tconst errors = errorBox(tree, result.error.errors, []);\n\texpect(errors).toMatch(\"Not a number\");\n});\n\ntest(\"works when no nodes\", async () => {\n\tconst argv = \"hello world --flag --flag --this-is=option -abcde=f -abcde\";\n\n\tconst tokens = tokenize(argv.split(\" \"));\n\tconst tree = parse(tokens, createRegisterOptions());\n\n\tconst opt = option({\n\t\ttype: number,\n\t\tlong: \"some\",\n\t});\n\n\tconst result = await opt.parse({\n\t\tnodes: tree,\n\t\tvisitedNodes: new Set(),\n\t});\n\n\tif (Result.isOk(result)) {\n\t\tthrow new Error(\"should fail...\");\n\t}\n\n\tconst errors = errorBox(tree, result.error.errors, []);\n\texpect(errors).toMatch(\"No value provided for --some\");\n});\n\ntest(\"multiline non-highlight errors are indented\", () => {\n\tconst errors = errorBox(\n\t\t[],\n\t\t[\n\t\t\t{\n\t\t\t\tnodes: [],\n\t\t\t\tmessage:\n\t\t\t\t\t\"Failed to get missing value for '--token'\\n\" +\n\t\t\t\t\t\"hint: one line.\\n\" +\n\t\t\t\t\t\"╰▶ second line\",\n\t\t\t},\n\t\t],\n\t\t[],\n\t);\n\n\texpect(errors).toMatchInlineSnapshot(\n\t\t`\n\t\t\"error: found 1 error\n\n\t\t  1. Failed to get missing value for '--token'\n\t\t     hint: one line.\n\t\t     ╰▶ second line\n\n\t\thint: for more information, try ' --help'\"\n\t`,\n\t);\n});\n"
  },
  {
    "path": "test/flag.test.ts",
    "content": "import { expect, test } from \"vitest\";\nimport * as Result from \"../src/Result\";\nimport { flag } from \"../src/flag\";\nimport { parse } from \"../src/newparser/parser\";\nimport { tokenize } from \"../src/newparser/tokenizer\";\nimport { boolean } from \"../src/types\";\nimport { createRegisterOptions } from \"./createRegisterOptions\";\n\ntest(\"fails on incompatible value\", async () => {\n\tconst argv = \"--hello=world\";\n\tconst tokens = tokenize(argv.split(\" \"));\n\tconst argparser = flag({\n\t\ttype: boolean,\n\t\tlong: \"hello\",\n\t\tdescription: \"description\",\n\t});\n\tconst registerOptions = createRegisterOptions();\n\targparser.register(registerOptions);\n\tconst nodes = parse(tokens, registerOptions);\n\n\tconst result = argparser.parse({\n\t\tnodes,\n\t\tvisitedNodes: new Set(),\n\t});\n\n\tawait expect(result).resolves.toEqual(\n\t\tResult.err({\n\t\t\terrors: [\n\t\t\t\t{\n\t\t\t\t\tnodes: nodes,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t'expected value to be either \"true\" or \"false\". got: \"world\"',\n\t\t\t\t},\n\t\t\t],\n\t\t}),\n\t);\n});\n\ntest(\"defaults to false\", async () => {\n\tconst argv = \"\";\n\tconst tokens = tokenize(argv.split(\" \"));\n\tconst argparser = flag({\n\t\ttype: boolean,\n\t\tlong: \"hello\",\n\t\tdescription: \"description\",\n\t});\n\tconst registerOptions = createRegisterOptions();\n\targparser.register(registerOptions);\n\tconst nodes = parse(tokens, registerOptions);\n\n\tconst result = argparser.parse({\n\t\tnodes,\n\t\tvisitedNodes: new Set(),\n\t});\n\n\tawait expect(result).resolves.toEqual(Result.ok(false));\n});\n\ntest(\"allows short arguments\", async () => {\n\tconst argv = \"-abc\";\n\tconst tokens = tokenize(argv.split(\" \"));\n\tconst argparser = flag({\n\t\ttype: boolean,\n\t\tlong: \"hello\",\n\t\tshort: \"b\",\n\t\tdescription: \"description\",\n\t});\n\tconst registerOptions = createRegisterOptions();\n\targparser.register(registerOptions);\n\tconst nodes = parse(tokens, registerOptions);\n\n\tconst result = argparser.parse({\n\t\tnodes,\n\t\tvisitedNodes: new Set(),\n\t});\n\n\tawait expect(result).resolves.toEqual(Result.ok(true));\n});\n"
  },
  {
    "path": "test/multioption.test.ts",
    "content": "import { expect, test } from \"vitest\";\nimport * as Result from \"../src/Result\";\nimport { multioption } from \"../src/multioption\";\nimport { parse } from \"../src/newparser/parser\";\nimport { tokenize } from \"../src/newparser/tokenizer\";\nimport { array, string } from \"../src/types\";\nimport { createRegisterOptions } from \"./createRegisterOptions\";\n\ntest(\"applies default value when no option is provided\", async () => {\n\tconst argv = \"\";\n\tconst tokens = tokenize(argv.split(\" \"));\n\tconst argparser = multioption({\n\t\ttype: array(string),\n\t\tlong: \"hello\",\n\t\tdefaultValue: () => [\"world!\"],\n\t\tdescription: \"description\",\n\t});\n\tconst registerOptions = createRegisterOptions();\n\targparser.register(registerOptions);\n\tconst nodes = parse(tokens, registerOptions);\n\n\tconst result = argparser.parse({\n\t\tnodes,\n\t\tvisitedNodes: new Set(),\n\t});\n\n\tawait expect(result).resolves.toEqual(Result.ok([\"world!\"]));\n});\n\ntest(\"does not apply default value when option is provided\", async () => {\n\tconst argv = \"--hello=moshe\";\n\tconst tokens = tokenize(argv.split(\" \"));\n\tconst argparser = multioption({\n\t\ttype: array(string),\n\t\tlong: \"hello\",\n\t\tdefaultValue: () => [\"world!\"],\n\t\tdescription: \"description\",\n\t});\n\tconst registerOptions = createRegisterOptions();\n\targparser.register(registerOptions);\n\tconst nodes = parse(tokens, registerOptions);\n\n\tconst result = argparser.parse({\n\t\tnodes,\n\t\tvisitedNodes: new Set(),\n\t});\n\n\tawait expect(result).resolves.toEqual(Result.ok([\"moshe\"]));\n});\n\ntest(\"does not apply default value when options are provided\", async () => {\n\tconst argv = \"--hello=moshe --hello=haim\";\n\tconst tokens = tokenize(argv.split(\" \"));\n\tconst argparser = multioption({\n\t\ttype: array(string),\n\t\tlong: \"hello\",\n\t\tdefaultValue: () => [\"world!\"],\n\t\tdescription: \"description\",\n\t});\n\tconst registerOptions = createRegisterOptions();\n\targparser.register(registerOptions);\n\tconst nodes = parse(tokens, registerOptions);\n\n\tconst result = argparser.parse({\n\t\tnodes,\n\t\tvisitedNodes: new Set(),\n\t});\n\n\tawait expect(result).resolves.toEqual(Result.ok([\"moshe\", \"haim\"]));\n});\n\ntest(\"fails when no option is provided and applying default value fails\", async () => {\n\tconst argv = \"\";\n\tconst tokens = tokenize(argv.split(\" \"));\n\tconst argparser = multioption({\n\t\ttype: array(string),\n\t\tlong: \"hello\",\n\t\tdefaultValue: () => {\n\t\t\tthrow new Error(\"its too hot outside, stay inside sweetheart!\");\n\t\t},\n\t\tdescription: \"description\",\n\t});\n\tconst registerOptions = createRegisterOptions();\n\targparser.register(registerOptions);\n\tconst nodes = parse(tokens, registerOptions);\n\n\tconst result = argparser.parse({\n\t\tnodes,\n\t\tvisitedNodes: new Set(),\n\t});\n\n\tawait expect(result).resolves.toEqual(\n\t\tResult.err({\n\t\t\terrors: [\n\t\t\t\t{\n\t\t\t\t\tnodes: [],\n\t\t\t\t\tmessage: `Failed to resolve default value for '--hello': its too hot outside, stay inside sweetheart!`,\n\t\t\t\t},\n\t\t\t],\n\t\t}),\n\t);\n});\n\ntest(\"fallsback to `[]` when no options and no defaultValue are provided\", async () => {\n\tconst argv = \"\";\n\tconst tokens = tokenize(argv.split(\" \"));\n\tconst argparser = multioption({\n\t\ttype: array(string),\n\t\tlong: \"hello\",\n\t\tdescription: \"description\",\n\t});\n\tconst registerOptions = createRegisterOptions();\n\targparser.register(registerOptions);\n\tconst nodes = parse(tokens, registerOptions);\n\n\tconst result = argparser.parse({\n\t\tnodes,\n\t\tvisitedNodes: new Set(),\n\t});\n\n\tawait expect(result).resolves.toEqual(Result.ok([]));\n});\n"
  },
  {
    "path": "test/negative-numbers.test.ts",
    "content": "import { expect, test } from \"vitest\";\nimport { createCmd } from \"../example/negative-numbers\";\nimport { runSafely } from \"../src\";\n\ntest(\"negative numbers\", async () => {\n\tconst cmd = createCmd();\n\tconst result = new Promise<number>((resolve) => {\n\t\tcmd.handler = async ({ number }) => {\n\t\t\tresolve(number);\n\t\t};\n\t});\n\n\tconst runResult = await runSafely(cmd, [\"--number\", \"-10\"]);\n\tif (runResult._tag === \"error\") {\n\t\tthrow runResult.error;\n\t}\n\n\texpect(await result).toEqual(-10);\n});\n"
  },
  {
    "path": "test/newparser/findOption.test.ts",
    "content": "import { expect, test } from \"vitest\";\nimport { findOption } from \"../../src/newparser/findOption\";\nimport { parse } from \"../../src/newparser/parser\";\nimport { tokenize } from \"../../src/newparser/tokenizer\";\nimport { createRegisterOptions } from \"../createRegisterOptions\";\n\ntest(\"finds options\", () => {\n\tconst argv = \"hello world --some arg --flag --this-is=option -abcde=f -abcde\";\n\tconst tokens = tokenize(argv.split(\" \"));\n\tconst nodes = parse(tokens, createRegisterOptions());\n\n\tconst options = findOption(nodes, { longNames: [\"some\"], shortNames: [\"c\"] });\n\n\tconst raw = options.map((x) => ({ key: x.key, value: x.value?.node.raw }));\n\texpect(raw).toEqual([\n\t\t{ key: \"some\", value: \"arg\" },\n\t\t{ key: \"c\" },\n\t\t{ key: \"c\" },\n\t]);\n});\n"
  },
  {
    "path": "test/newparser/parser.test.ts",
    "content": "import { expect, test } from \"vitest\";\nimport { type AstNode, parse } from \"../../src/newparser/parser\";\nimport { tokenize } from \"../../src/newparser/tokenizer\";\nimport { createRegisterOptions } from \"../createRegisterOptions\";\n\ntest(\"dash in the middle of a word\", () => {\n\tconst tokens = tokenize([\"hello\", \"world\", \"you-know\", \"my\", \"friend\"]);\n\tconst tree = parse(tokens, createRegisterOptions());\n\texpect(tree).toMatchInlineSnapshot(`\n    [\n      {\n        \"index\": 0,\n        \"raw\": \"hello\",\n        \"type\": \"positionalArgument\",\n      },\n      {\n        \"index\": 6,\n        \"raw\": \"world\",\n        \"type\": \"positionalArgument\",\n      },\n      {\n        \"index\": 12,\n        \"raw\": \"you-know\",\n        \"type\": \"positionalArgument\",\n      },\n      {\n        \"index\": 21,\n        \"raw\": \"my\",\n        \"type\": \"positionalArgument\",\n      },\n      {\n        \"index\": 24,\n        \"raw\": \"friend\",\n        \"type\": \"positionalArgument\",\n      },\n    ]\n  `);\n});\n\ntest(\"parses forcePositional if it is the last token\", () => {\n\tconst argv = \"scripts/ts-node src/example/app.ts cat /tmp/a --\".split(\" \");\n\tconst tokens = tokenize(argv);\n\tconst tree = parse(tokens, createRegisterOptions());\n\texpect(tree.map((x) => x.type)).toContain<AstNode[\"type\"]>(\"forcePositional\");\n});\n\ntest(\"welp\", () => {\n\tconst argv = \"scripts/ts-node src/example/app.ts cat /tmp/a --help\".split(\n\t\t\" \",\n\t);\n\tconst tokens = tokenize(argv);\n\tconst tree = parse(tokens, createRegisterOptions());\n\texpect(tree).toMatchInlineSnapshot(`\n    [\n      {\n        \"index\": 0,\n        \"raw\": \"scripts/ts-node\",\n        \"type\": \"positionalArgument\",\n      },\n      {\n        \"index\": 16,\n        \"raw\": \"src/example/app.ts\",\n        \"type\": \"positionalArgument\",\n      },\n      {\n        \"index\": 35,\n        \"raw\": \"cat\",\n        \"type\": \"positionalArgument\",\n      },\n      {\n        \"index\": 39,\n        \"raw\": \"/tmp/a\",\n        \"type\": \"positionalArgument\",\n      },\n      {\n        \"index\": 46,\n        \"key\": \"help\",\n        \"raw\": \"--help\",\n        \"type\": \"longOption\",\n        \"value\": undefined,\n      },\n    ]\n  `);\n});\n"
  },
  {
    "path": "test/rest-parameters.test.ts",
    "content": "import path from \"node:path\";\nimport { expect, it } from \"vitest\";\nimport { app } from \"./util\";\n\nconst runAppRestExample = app(\n\tpath.join(__dirname, \"../example/rest-example.ts\"),\n);\n\nit(\"should be able to use rest parameters after the positional\", async () => {\n\tconst result = await runAppRestExample([\n\t\t\"pos\",\n\t\t\"more\",\n\t\t\"--rest\",\n\t\t\"parameters\",\n\t]);\n\texpect(JSON.parse(result.stdout)).toEqual({\n\t\tscriptName: \"pos\",\n\t\teverythingElse: [\"more\", \"--rest\", \"parameters\"],\n\t});\n});\n\nit(\"should fail if the positional is not provided\", async () => {\n\tconst result = await runAppRestExample([\"--rest\", \"parameters\"]);\n\texpect(result.exitCode).toBe(1);\n\texpect(result.stderr).toContain(\"No value provided for str\");\n});\n"
  },
  {
    "path": "test/rest.test.ts",
    "content": "import { expect, test } from \"vitest\";\nimport { rest } from \"../src\";\nimport * as Result from \"../src/Result\";\nimport { type AstNode, parse } from \"../src/newparser/parser\";\nimport { tokenize } from \"../src/newparser/tokenizer\";\nimport { createRegisterOptions } from \"./createRegisterOptions\";\n\n// test(\"fails on specific positional\", async () => {\n// \tconst argv = \"10 20 --mamma mia hello 40\";\n// \tconst tokens = tokenize(argv.split(\" \"));\n// \tconst nodes = parse(tokens, createRegisterOptions());\n// \tconst argparser = rwrestPositionals({\n// \t\ttype: number,\n// \t});\n//\n// \tconst result = argparser.parse({ nodes, visitedNodes: new Set() });\n//\n// \tawait expect(result).resolves.toEqual(\n// \t\tResult.err({\n// \t\t\terrors: [\n// \t\t\t\t{\n// \t\t\t\t\tnodes: nodes.filter((x) => x.raw === \"hello\"),\n// \t\t\t\t\tmessage: \"Not a number\",\n// \t\t\t\t},\n// \t\t\t],\n// \t\t}),\n// \t);\n// });\n\ntest(\"succeeds\", async () => {\n\tconst argv = \"10 20 --mamma mia hello --hey=ho 40\";\n\tconst tokens = tokenize(argv.split(\" \"));\n\tconst nodes = parse(tokens, createRegisterOptions());\n\tconst argparser = rest();\n\n\tconst visitedNodes = new Set<AstNode>();\n\tconst result = argparser.parse({ nodes, visitedNodes });\n\n\tawait expect(result).resolves.toEqual(\n\t\tResult.ok([\"10\", \"20\", \"--mamma\", \"mia\", \"hello\", \"--hey=ho\", \"40\"]),\n\t);\n});\n"
  },
  {
    "path": "test/restPositionals.test.ts",
    "content": "import { expect, test } from \"vitest\";\nimport * as Result from \"../src/Result\";\nimport { type AstNode, parse } from \"../src/newparser/parser\";\nimport { tokenize } from \"../src/newparser/tokenizer\";\nimport { restPositionals } from \"../src/restPositionals\";\nimport { createRegisterOptions } from \"./createRegisterOptions\";\nimport { number } from \"./test-types\";\n\ntest(\"fails on specific positional\", async () => {\n\tconst argv = \"10 20 --mamma mia hello 40\";\n\tconst tokens = tokenize(argv.split(\" \"));\n\tconst nodes = parse(tokens, createRegisterOptions());\n\tconst argparser = restPositionals({\n\t\ttype: number,\n\t});\n\n\tconst result = argparser.parse({ nodes, visitedNodes: new Set() });\n\n\tawait expect(result).resolves.toEqual(\n\t\tResult.err({\n\t\t\terrors: [\n\t\t\t\t{\n\t\t\t\t\tnodes: nodes.filter((x) => x.raw === \"hello\"),\n\t\t\t\t\tmessage: \"Not a number\",\n\t\t\t\t},\n\t\t\t],\n\t\t}),\n\t);\n});\n\ntest(\"succeeds when all unused positional decode successfuly\", async () => {\n\tconst argv = \"10 20 --mamma mia hello 40\";\n\tconst tokens = tokenize(argv.split(\" \"));\n\tconst nodes = parse(tokens, createRegisterOptions());\n\tconst argparser = restPositionals({\n\t\ttype: number,\n\t});\n\n\tconst visitedNodes = new Set<AstNode>();\n\tconst alreadyUsedNode = nodes.find((x) => x.raw === \"hello\");\n\tif (!alreadyUsedNode) {\n\t\tthrow new Error(\"Node `hello` not found. please rewrite the find function\");\n\t}\n\tvisitedNodes.add(alreadyUsedNode);\n\n\tconst result = argparser.parse({ nodes, visitedNodes });\n\n\tawait expect(result).resolves.toEqual(Result.ok([10, 20, 40]));\n});\n"
  },
  {
    "path": "test/subcommands.test.ts",
    "content": "import { expect, test, vitest } from \"vitest\";\nimport * as Result from \"../src/Result\";\nimport { command } from \"../src/command\";\nimport { flag } from \"../src/flag\";\nimport { parse } from \"../src/newparser/parser\";\nimport { tokenize } from \"../src/newparser/tokenizer\";\nimport { option } from \"../src/option\";\nimport { positional } from \"../src/positional\";\nimport { subcommands } from \"../src/subcommands\";\nimport { createRegisterOptions } from \"./createRegisterOptions\";\nimport { boolean, string } from \"./test-types\";\n\nconst logMock = vitest.fn();\n\nconst greeter = command({\n\tname: \"greeter\",\n\targs: {\n\t\tname: positional({ type: string, displayName: \"name\" }),\n\t\texclaim: flag({ type: boolean, long: \"exclaim\", short: \"e\" }),\n\t\tgreeting: option({ type: string, long: \"greeting\", short: \"g\" }),\n\t},\n\thandler: (x) => {\n\t\tlogMock(\"greeter\", x);\n\t},\n});\n\nconst howdyPrinter = command({\n\tname: \"howdy\",\n\targs: {\n\t\tname: positional({ type: string, displayName: \"name\" }),\n\t},\n\thandler: (x) => {\n\t\tlogMock(\"howdy\", x);\n\t},\n});\n\nconst subcmds = subcommands({\n\tname: \"my-cli\",\n\tcmds: {\n\t\tgreeter,\n\t\thowdy: howdyPrinter,\n\t},\n});\n\ntest(\"chooses one subcommand\", async () => {\n\tconst argv = \"greeter Gal -eg Hello\".split(\" \");\n\tconst tokens = tokenize(argv);\n\tconst registerOptions = createRegisterOptions();\n\tsubcmds.register(registerOptions);\n\tconst nodes = parse(tokens, registerOptions);\n\tconst result = await subcmds.parse({ nodes, visitedNodes: new Set() });\n\tconst expected: typeof result = Result.ok({\n\t\targs: {\n\t\t\tname: \"Gal\",\n\t\t\texclaim: true,\n\t\t\tgreeting: \"Hello\",\n\t\t},\n\t\tcommand: \"greeter\",\n\t});\n\n\texpect(result).toEqual(expected);\n});\n\ntest(\"chooses the other subcommand\", async () => {\n\tconst argv = \"howdy joe\".split(\" \");\n\tconst tokens = tokenize(argv);\n\tconst registerOptions = createRegisterOptions();\n\tsubcmds.register(registerOptions);\n\tconst nodes = parse(tokens, registerOptions);\n\tconst result = await subcmds.parse({ nodes, visitedNodes: new Set() });\n\tconst expected: typeof result = Result.ok({\n\t\tcommand: \"howdy\",\n\t\targs: {\n\t\t\tname: \"joe\",\n\t\t},\n\t});\n\n\texpect(result).toEqual(expected);\n});\n\ntest(\"fails when using unknown subcommand\", async () => {\n\tconst argv = \"--hello yes how are you joe\".split(\" \");\n\tconst tokens = tokenize(argv);\n\tconst registerOptions = createRegisterOptions();\n\tsubcmds.register(registerOptions);\n\tconst nodes = parse(tokens, registerOptions);\n\tconst result = await subcmds.parse({ nodes, visitedNodes: new Set() });\n\tconst expected: typeof result = Result.err({\n\t\terrors: [\n\t\t\t{\n\t\t\t\tnodes: nodes.filter((x) => x.raw === \"how\"),\n\t\t\t\tmessage: \"Not a valid subcommand name\",\n\t\t\t},\n\t\t],\n\t\tpartialValue: {},\n\t});\n\n\texpect(result).toEqual(expected);\n});\n\ntest(\"fails for a subcommand argument parsing issue\", async () => {\n\tconst argv = \"greeter Gal -g Hello --exclaim=hell-no\".split(\" \");\n\tconst tokens = tokenize(argv);\n\tconst registerOptions = createRegisterOptions();\n\tsubcmds.register(registerOptions);\n\tconst nodes = parse(tokens, registerOptions);\n\tconst result = await subcmds.parse({ nodes, visitedNodes: new Set() });\n\tconst expected = Result.err({\n\t\terrors: [\n\t\t\t{\n\t\t\t\tnodes: nodes.filter((x) => x.raw.includes(\"hell-no\")),\n\t\t\t\tmessage: `expected value to be either \"true\" or \"false\". got: \"hell-no\"`,\n\t\t\t},\n\t\t],\n\t\tpartialValue: {\n\t\t\tcommand: \"greeter\",\n\t\t\targs: {\n\t\t\t\tgreeting: \"Hello\",\n\t\t\t\tname: \"Gal\",\n\t\t\t},\n\t\t},\n\t});\n\n\texpect(result).toEqual(expected);\n});\n"
  },
  {
    "path": "test/test-types.ts",
    "content": "import { identity } from \"../src/from\";\nimport type { InputOf, OutputOf } from \"../src/from\";\nimport type { Type } from \"../src/type\";\n\nexport const number: Type<string, number> = {\n\tasync from(str) {\n\t\tconst decoded = Number.parseInt(str, 10);\n\n\t\tif (Number.isNaN(decoded)) {\n\t\t\tthrow new Error(\"Not a number\");\n\t\t}\n\t\treturn decoded;\n\t},\n\tdisplayName: \"number\",\n\tdescription: \"a number\",\n};\n\nexport function single<T extends Type<any, any>>(\n\tt: T,\n): Omit<T, \"from\"> & Type<InputOf<T>[], OutputOf<T>> {\n\treturn {\n\t\t...t,\n\t\tfrom(ts) {\n\t\t\tif (ts.length === 0) {\n\t\t\t\treturn { result: \"error\", message: \"No value provided\" };\n\t\t\t}\n\n\t\t\tif (ts.length > 1) {\n\t\t\t\treturn {\n\t\t\t\t\tresult: \"error\",\n\t\t\t\t\tmessage: `Too many arguments provided. Expected 1, got: ${ts.length}`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn t.from(ts[0]);\n\t\t},\n\t};\n}\n\nexport const string: Type<string, string> = {\n\t...identity(),\n\tdescription: \"a string\",\n\tdisplayName: \"str\",\n};\n\nexport const boolean: Type<boolean, boolean> = {\n\t...identity(),\n\tdescription: \"a boolean\",\n\tdisplayName: \"true/false\",\n\tdefaultValue() {\n\t\treturn false;\n\t},\n};\n\nexport function optional<T extends Type<any, any>>(\n\tt: T,\n): Type<InputOf<T>, OutputOf<T> | undefined> {\n\treturn {\n\t\t...t,\n\t\tdefaultValue(): OutputOf<T> | undefined {\n\t\t\treturn undefined;\n\t\t},\n\t};\n}\n"
  },
  {
    "path": "test/tsconfig.json",
    "content": "{\n\t\"extends\": \"../tsconfig.noEmit.json\",\n\t\"include\": [\".\"]\n}\n"
  },
  {
    "path": "test/type-inference.test.ts",
    "content": "import path from \"node:path\";\nimport { getTypes } from \"infer-types\";\nimport { expect, test } from \"vitest\";\n\ntest(\"types are inferred correctly\", () => {\n\tconst filepath = path.join(__dirname, \"../example/app.ts\");\n\tconst types = getTypes(filepath);\n\texpect(types).toEqual({\n\t\t\"cat -> stream\": \"Stream\",\n\t\t\"cat -> restStreams\": \"Stream[]\",\n\t\t\"complex -> intOrString\": \"string | number\",\n\t\t\"complex -> floatOrString\": \"string | number\",\n\t\t\"complex -> pos2\": \"string\",\n\t\t\"complex -> optionWithoutType\": \"string\",\n\t\t\"complex -> boolWithoutType\": \"boolean\",\n\t\t\"complex -> rest\": \"string[]\",\n\t\t\"greet -> greeting\": \"string\",\n\t\t\"greet -> name\": \"string\",\n\t\t\"greet -> noExclaim\": \"boolean\",\n\t});\n});\n"
  },
  {
    "path": "test/ui.test.ts",
    "content": "import path from \"node:path\";\nimport { describe, expect, test } from \"vitest\";\nimport { app } from \"./util\";\n\ntest(\"help for subcommands\", async () => {\n\tconst result = await runApp1([\"--help\"]);\n\texpect(result.all).toMatchSnapshot();\n\texpect(result.exitCode).toBe(0);\n});\n\ntest(\"invalid subcommand\", async () => {\n\tconst result = await runApp1([\"subcommand-that-doesnt-exist\"]);\n\texpect(result.all).toMatchSnapshot();\n\texpect(result.exitCode).toBe(1);\n});\n\ntest(\"help for complex command\", async () => {\n\tconst result = await runApp1([\"complex\", \"--help\"]);\n\texpect(result.all).toMatchSnapshot();\n\texpect(result.exitCode).toBe(0);\n});\n\ntest(\"too many arguments\", async () => {\n\tconst result = await runApp1([\n\t\t\"--this=will-be-an-error\",\n\t\t\"cat\",\n\t\tpath.relative(process.cwd(), path.join(__dirname, \"../package.json\")),\n\t\t\"--and-also-this\",\n\t]);\n\texpect(result.all).toMatchSnapshot();\n\texpect(result.exitCode).toBe(1);\n});\n\ntest(\"suggests a subcommand on typo\", async () => {\n\tconst result = await runApp1([\"greek\"]);\n\texpect(result.all).toMatchSnapshot();\n\texpect(result.exitCode).toBe(1);\n});\n\ntest(\"displays subcommand help if no arguments passed\", async () => {\n\tconst result = await runApp1([]);\n\texpect(result.all).toMatchSnapshot();\n\texpect(result.exitCode).toBe(0);\n});\n\ntest(\"displays nested subcommand help if no arguments passed\", async () => {\n\tconst result = await runApp1([\"composed\"]);\n\texpect(result.all).toMatchSnapshot();\n\texpect(result.exitCode).toBe(0);\n});\n\ntest(\"composes errors\", async () => {\n\tconst result = await runApp1([\n\t\t\"greet\",\n\t\t\"--times=not-a-number\",\n\t\t\"not-capitalized\",\n\t]);\n\texpect(result.all).toMatchSnapshot();\n\texpect(result.exitCode).toBe(1);\n});\n\ntest(\"multiline error\", async () => {\n\tconst result = await runApp1([\"greet\", \"Bon Jovi\"]);\n\texpect(result.all).toMatchSnapshot();\n\texpect(result.exitCode).toBe(1);\n});\n\ntest(\"help for composed subcommands\", async () => {\n\tconst result = await runApp1([\"composed\", \"--help\"]);\n\texpect(result.all).toMatchSnapshot();\n\texpect(result.exitCode).toBe(0);\n});\n\ntest(\"help for composed subcommand\", async () => {\n\tconst result = await runApp1([\"composed\", \"cat\", \"--help\"]);\n\texpect(result.all).toMatchSnapshot();\n\texpect(result.exitCode).toBe(0);\n});\n\ntest(\"asynchronous type conversion works for failures\", async () => {\n\tconst result = await runApp1([\n\t\t\"composed\",\n\t\t\"cat\",\n\t\t\"https://mock.httpstatus.io/404\",\n\t]);\n\texpect(result.all).toMatchSnapshot();\n\texpect(result.exitCode).toBe(1);\n});\n\ntest(\"asynchronous type conversion works for success\", async () => {\n\tconst result = await runApp1([\n\t\t\"composed\",\n\t\t\"cat\",\n\t\t\"https://mock.httpstatus.io/200\",\n\t]);\n\texpect(result.all).toMatchSnapshot();\n\texpect(result.exitCode).toBe(0);\n});\n\ntest(\"subcommands show their version\", async () => {\n\tconst result = await runApp1([\"--version\"]);\n\texpect(result.all).toMatchSnapshot();\n\texpect(result.exitCode).toBe(0);\n});\n\ntest(\"failures in defaultValue\", async () => {\n\tconst result = await runApp2([]);\n\texpect(result.all).toMatchSnapshot();\n\texpect(result.exitCode).toBe(1);\n});\n\ntest(\"subcommands with process.argv.slice(2)\", async () => {\n\tconst result = await runApp3([\"--help\"]);\n\texpect(result.all).toMatchSnapshot();\n\texpect(result.exitCode).toBe(0);\n});\n\ndescribe(\"allows positional arguments\", () => {\n\ttest(\"help shows them\", async () => {\n\t\tconst result = await runApp3([\"sub2\", \"--help\"]);\n\t\texpect(result.all).toMatchSnapshot();\n\t\texpect(result.exitCode).toBe(0);\n\t});\n\n\ttest(\"no positionals => all default\", async () => {\n\t\tconst result = await runApp3([\"sub2\"]);\n\t\texpect(result.all).toMatchInlineSnapshot(\n\t\t\t`\"{ name: 'anonymous', age: undefined }\"`,\n\t\t);\n\t\texpect(result.exitCode).toBe(0);\n\t});\n\n\ttest(\"expects the correct order\", async () => {\n\t\t// should fail because we get an age first and `hello` is not a number\n\t\tconst result = await runApp3([\"sub2\", \"hello\"]);\n\t\texpect(result.all).toMatchSnapshot();\n\t\texpect(result.exitCode).toBe(1);\n\t});\n\n\ttest(\"can take all the arguments\", async () => {\n\t\t// should fail because we get an age first and `hello` is not a number\n\t\tconst result = await runApp3([\"sub2\", \"10\", \"ben\"]);\n\t\texpect(result.all).toMatchInlineSnapshot(`\"{ name: 'ben', age: 10 }\"`);\n\t\texpect(result.exitCode).toBe(0);\n\t});\n});\n\ntest(\"onMissing resolves successfully\", async () => {\n\tconst result = await runApp4([]);\n\texpect(result.all).toMatchInlineSnapshot(\n\t\t`\"Result: default value, Hi, Hello from opt\"`,\n\t);\n\texpect(result.exitCode).toBe(0);\n});\n\ntest(\"onMissing failure\", async () => {\n\tconst result = await runApp5([]);\n\texpect(result.all).toMatchSnapshot();\n\texpect(result.exitCode).toBe(1);\n});\n\ntest(\"help shows onMissing option\", async () => {\n\tconst result = await runApp4([\"--help\"]);\n\texpect(result.all).toMatchSnapshot();\n\texpect(result.exitCode).toBe(0);\n});\n\ntest(\"flag onMissing resolves successfully\", async () => {\n\tconst result = await runApp6([]);\n\texpect(result.exitCode).toBe(0);\n\texpect(result.all).toContain(\"Verbose flag not provided\");\n\texpect(result.all).toContain(\"Debug flag missing\");\n\texpect(result.all).toContain(\"Force flag not set\");\n});\n\ntest(\"flag help shows onMissing as optional\", async () => {\n\tconst result = await runApp6([\"--help\"]);\n\texpect(result.exitCode).toBe(0);\n\texpect(result.all).toContain(\"[optional]\");\n});\n\ntest(\"multioption onMissing resolves successfully\", async () => {\n\tconst result = await runApp7([]);\n\texpect(result.exitCode).toBe(0);\n\texpect(result.all).toContain(\"No includes specified\");\n\texpect(result.all).toContain(\"No targets specified\");\n\texpect(result.all).toContain(\"No features specified\");\n});\n\ntest(\"multioption help shows onMissing as optional\", async () => {\n\tconst result = await runApp7([\"--help\"]);\n\texpect(result.exitCode).toBe(0);\n\texpect(result.all).toContain(\"[...optional]\");\n});\n\nconst runApp1 = app(path.join(__dirname, \"../example/app.ts\"));\nconst runApp2 = app(path.join(__dirname, \"../example/app2.ts\"));\nconst runApp3 = app(path.join(__dirname, \"../example/app3.ts\"));\nconst runApp4 = app(path.join(__dirname, \"../example/app4.ts\"));\nconst runApp5 = app(path.join(__dirname, \"../example/app5.ts\"));\nconst runApp6 = app(path.join(__dirname, \"../example/app6.ts\"));\nconst runApp7 = app(path.join(__dirname, \"../example/app7.ts\"));\n"
  },
  {
    "path": "test/util.ts",
    "content": "import { type ExecaReturnValue, execa } from \"execa\";\n\nexport function app(\n\tscriptPath: string,\n): (args: string[]) => Promise<ExecaReturnValue> {\n\treturn async (args) => {\n\t\tconst result = await execa(\n\t\t\trequire.resolve(\"tsx/cli\"),\n\t\t\t[scriptPath, ...args],\n\t\t\t{\n\t\t\t\tall: true,\n\t\t\t\treject: false,\n\t\t\t\tenv: {\n\t\t\t\t\tFORCE_COLOR: \"true\",\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t\treturn result;\n\t};\n}\n"
  },
  {
    "path": "test/utils.test.ts",
    "content": "import chalk from \"chalk\";\nimport stripAnsi from \"strip-ansi\";\nimport { describe, expect, it } from \"vitest\";\nimport { expectTypeOf } from \"vitest\";\nimport { type AllOrNothing, padNoAnsi } from \"../src/utils\";\n\ndescribe(\"padNoAnsi\", () => {\n\tit(\"pads start\", () => {\n\t\tconst expected = \"hello\".padStart(10, \" \");\n\t\tconst actual = padNoAnsi(\n\t\t\t[\n\t\t\t\tchalk.red(\"h\"),\n\t\t\t\tchalk.cyan(\"e\"),\n\t\t\t\tchalk.blue(\"l\"),\n\t\t\t\tchalk.green(\"l\"),\n\t\t\t\tchalk.red(\"o\"),\n\t\t\t].join(\"\"),\n\t\t\t10,\n\t\t\t\"start\",\n\t\t);\n\t\texpect(stripAnsi(actual)).toEqual(expected);\n\t});\n\tit(\"pads end\", () => {\n\t\tconst expected = \"hello\".padEnd(10, \" \");\n\t\tconst actual = padNoAnsi(\n\t\t\t[\n\t\t\t\tchalk.red(\"h\"),\n\t\t\t\tchalk.cyan(\"e\"),\n\t\t\t\tchalk.blue(\"l\"),\n\t\t\t\tchalk.green(\"l\"),\n\t\t\t\tchalk.red(\"o\"),\n\t\t\t].join(\"\"),\n\t\t\t10,\n\t\t\t\"end\",\n\t\t);\n\t\texpect(stripAnsi(actual)).toEqual(expected);\n\t});\n\tit(\"returns the string if it is shorter than the padding\", () => {\n\t\tconst str = chalk`{red h}{cyan e}{blue l}{green l}{red o}`;\n\t\tconst actual = padNoAnsi(str, 2, \"end\");\n\t\texpect(actual).toEqual(str);\n\t});\n});\n\nit(\"allows to provide all arguments or none\", () => {\n\ttype Person = { name: string; age: number };\n\texpectTypeOf<{ name: \"Joe\"; age: 100 }>().toExtend<AllOrNothing<Person>>();\n\texpectTypeOf<{ name: \"Joe\" }>().not.toExtend<AllOrNothing<Person>>();\n\texpectTypeOf<{}>().toExtend<AllOrNothing<Person>>();\n});\n"
  },
  {
    "path": "test/vercel-formatter.test.ts",
    "content": "import { afterEach, describe, expect, test } from \"vitest\";\nimport {\n\tcommand,\n\tflag,\n\tpositional,\n\tresetHelpFormatter,\n\trun,\n\tsetDefaultHelpFormatter,\n\tsubcommands,\n} from \"../src\";\nimport {\n\tcreateVercelFormatter,\n\tvercelFormatter,\n} from \"../src/batteries/vercel-formatter\";\n\ndescribe(\"vercelFormatter\", () => {\n\tafterEach(() => {\n\t\tresetHelpFormatter();\n\t});\n\n\ttest(\"formats subcommands help with version and logo\", async () => {\n\t\tsetDefaultHelpFormatter(\n\t\t\tcreateVercelFormatter({\n\t\t\t\tcliName: \"Vercel Sandbox CLI\",\n\t\t\t\tlogo: \"▲\",\n\t\t\t}),\n\t\t);\n\n\t\tconst create = command({\n\t\t\tname: \"create\",\n\t\t\tdescription: \"Create a new sandbox\",\n\t\t\targs: {\n\t\t\t\tconnect: flag({\n\t\t\t\t\tlong: \"connect\",\n\t\t\t\t\tshort: \"c\",\n\t\t\t\t\tdescription: \"Connect after creating\",\n\t\t\t\t}),\n\t\t\t},\n\t\t\thandler: () => {},\n\t\t});\n\n\t\tconst list = command({\n\t\t\tname: \"list\",\n\t\t\taliases: [\"ls\"],\n\t\t\tdescription: \"List sandboxes for the current project\",\n\t\t\targs: {},\n\t\t\thandler: () => {},\n\t\t});\n\n\t\tconst runCmd = command({\n\t\t\tname: \"run\",\n\t\t\tdescription: \"Create and run a command in a sandbox\",\n\t\t\targs: {\n\t\t\t\tcmd: positional({ displayName: \"cmd\", description: \"Command to run\" }),\n\t\t\t},\n\t\t\thandler: () => {},\n\t\t});\n\n\t\tconst stop = command({\n\t\t\tname: \"stop\",\n\t\t\taliases: [\"rm\"],\n\t\t\tdescription: \"Stop one or more running sandboxes\",\n\t\t\targs: {\n\t\t\t\tids: positional({ displayName: \"id...\", description: \"Sandbox IDs\" }),\n\t\t\t},\n\t\t\thandler: () => {},\n\t\t});\n\n\t\tconst copy = command({\n\t\t\tname: \"copy\",\n\t\t\taliases: [\"cp\"],\n\t\t\tdescription: \"Copy files between local and remote\",\n\t\t\targs: {\n\t\t\t\tsrc: positional({ displayName: \"src\", description: \"Source path\" }),\n\t\t\t\tdst: positional({\n\t\t\t\t\tdisplayName: \"dst\",\n\t\t\t\t\tdescription: \"Destination path\",\n\t\t\t\t}),\n\t\t\t},\n\t\t\thandler: () => {},\n\t\t});\n\n\t\tconst app = subcommands({\n\t\t\tname: \"sandbox\",\n\t\t\tversion: \"2.3.0\",\n\t\t\tdescription: \"Interfacing with Vercel Sandbox\",\n\t\t\tcmds: { create, list, run: runCmd, stop, copy },\n\t\t\texamples: [\n\t\t\t\t{\n\t\t\t\t\tdescription: \"Create a sandbox and start a shell\",\n\t\t\t\t\tcommand: \"sandbox create --connect\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"Run a command in a new sandbox\",\n\t\t\t\t\tcommand: \"sandbox run -- node -e \\\"console.log('hello')\\\"\",\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\n\t\tlet output = \"\";\n\t\tconst originalLog = console.log;\n\t\tconsole.log = (msg: string) => {\n\t\t\toutput = msg;\n\t\t};\n\n\t\ttry {\n\t\t\tawait run(app, [\"--help\"]);\n\t\t} catch {\n\t\t\t// run throws Exit\n\t\t}\n\n\t\tconsole.log = originalLog;\n\n\t\texpect(output).toMatchSnapshot();\n\t});\n\n\ttest(\"formats command help\", async () => {\n\t\tsetDefaultHelpFormatter(vercelFormatter);\n\n\t\tconst create = command({\n\t\t\tname: \"create\",\n\t\t\tversion: \"1.0.0\",\n\t\t\tdescription: \"Create a new sandbox\",\n\t\t\targs: {\n\t\t\t\tconnect: flag({\n\t\t\t\t\tlong: \"connect\",\n\t\t\t\t\tshort: \"c\",\n\t\t\t\t\tdescription: \"Connect to the sandbox after creating\",\n\t\t\t\t}),\n\t\t\t\tname: positional({\n\t\t\t\t\tdisplayName: \"name\",\n\t\t\t\t\tdescription: \"Name for the sandbox\",\n\t\t\t\t}),\n\t\t\t},\n\t\t\thandler: () => {},\n\t\t\texamples: [\n\t\t\t\t{\n\t\t\t\t\tdescription: \"Create a sandbox named 'dev'\",\n\t\t\t\t\tcommand: \"create dev\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdescription: \"Create and connect\",\n\t\t\t\t\tcommand: \"create dev --connect\",\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\n\t\tlet output = \"\";\n\t\tconst originalLog = console.log;\n\t\tconsole.log = (msg: string) => {\n\t\t\toutput = msg;\n\t\t};\n\n\t\ttry {\n\t\t\tawait run(create, [\"--help\"]);\n\t\t} catch {\n\t\t\t// run throws Exit\n\t\t}\n\n\t\tconsole.log = originalLog;\n\n\t\texpect(output).toMatchSnapshot();\n\t});\n\n\ttest(\"shows aliases as short | long format\", async () => {\n\t\tsetDefaultHelpFormatter(vercelFormatter);\n\n\t\tconst list = command({\n\t\t\tname: \"list\",\n\t\t\taliases: [\"ls\", \"l\"],\n\t\t\tdescription: \"List items\",\n\t\t\targs: {},\n\t\t\thandler: () => {},\n\t\t});\n\n\t\tconst app = subcommands({\n\t\t\tname: \"app\",\n\t\t\tcmds: { list },\n\t\t});\n\n\t\tlet output = \"\";\n\t\tconst originalLog = console.log;\n\t\tconsole.log = (msg: string) => {\n\t\t\toutput = msg;\n\t\t};\n\n\t\ttry {\n\t\t\tawait run(app, [\"--help\"]);\n\t\t} catch {\n\t\t\t// run throws Exit\n\t\t}\n\n\t\tconsole.log = originalLog;\n\n\t\t// Should show \"l | list\" (shortest alias first)\n\t\texpect(output).toContain(\"l | list\");\n\t});\n\n\ttest(\"derives argument hints from help topics\", async () => {\n\t\tsetDefaultHelpFormatter(vercelFormatter);\n\n\t\tconst exec = command({\n\t\t\tname: \"exec\",\n\t\t\tdescription: \"Execute a command\",\n\t\t\targs: {\n\t\t\t\tid: positional({ displayName: \"id\", description: \"Sandbox ID\" }),\n\t\t\t\tcmd: positional({ displayName: \"cmd\", description: \"Command\" }),\n\t\t\t},\n\t\t\thandler: () => {},\n\t\t});\n\n\t\tconst app = subcommands({\n\t\t\tname: \"app\",\n\t\t\tcmds: { exec },\n\t\t});\n\n\t\tlet output = \"\";\n\t\tconst originalLog = console.log;\n\t\tconsole.log = (msg: string) => {\n\t\t\toutput = msg;\n\t\t};\n\n\t\ttry {\n\t\t\tawait run(app, [\"--help\"]);\n\t\t} catch {\n\t\t\t// run throws Exit\n\t\t}\n\n\t\tconsole.log = originalLog;\n\n\t\t// Should show argument hints derived from positionals\n\t\texpect(output).toContain(\"<id>\");\n\t\texpect(output).toContain(\"<cmd>\");\n\t});\n});\n"
  },
  {
    "path": "tsconfig.esm.json",
    "content": "{\n\t\"$schema\": \"http://json.schemastore.org/tsconfig\",\n\t\"extends\": \"./tsconfig.json\",\n\t\"compilerOptions\": {\n\t\t\"module\": \"ESNext\",\n\t\t\"outDir\": \"./dist/esm\"\n\t}\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n\t\"include\": [\"src\"],\n\t\"exclude\": [\"dist\"],\n\t\"compilerOptions\": {\n\t\t\"target\": \"es2018\",\n\t\t\"module\": \"commonjs\",\n\t\t\"lib\": [\"ES2018\"],\n\t\t\"downlevelIteration\": true,\n\t\t\"sourceMap\": true,\n\t\t\"outDir\": \"./dist/cjs\",\n\t\t\"rootDir\": \"./src\",\n\t\t\"strict\": true,\n\t\t\"noImplicitAny\": true,\n\t\t\"strictNullChecks\": true,\n\t\t\"strictFunctionTypes\": true,\n\t\t\"strictPropertyInitialization\": true,\n\t\t\"noImplicitThis\": true,\n\t\t\"alwaysStrict\": true,\n\t\t\"noUnusedLocals\": true,\n\t\t\"noUnusedParameters\": true,\n\t\t\"noImplicitReturns\": true,\n\t\t\"noFallthroughCasesInSwitch\": true,\n\t\t\"moduleResolution\": \"node\",\n\t\t\"jsx\": \"react\",\n\t\t\"esModuleInterop\": true,\n\t\t\"declaration\": true,\n\t\t\"skipLibCheck\": true\n\t}\n}\n"
  },
  {
    "path": "tsconfig.noEmit.json",
    "content": "{\n\t\"$schema\": \"http://json.schemastore.org/tsconfig\",\n\t\"extends\": \"./tsconfig.json\",\n\t\"compilerOptions\": {\n\t\t\"rootDir\": \"./\",\n\t\t\"noEmit\": true\n\t}\n}\n"
  },
  {
    "path": "typedoc.js",
    "content": "module.exports = {\n\texclude: [\"test/**\", \"src/example/**\"],\n\texcludeExternals: true,\n\texcludeNotExported: true,\n\texcludePrivate: true,\n\thideGenerator: true,\n\tincludes: \"./src\",\n\tout: \"public\",\n\tmodule: \"commonjs\",\n\tstripInternal: \"true\",\n};\n"
  }
]