# Cliffy is a set of small TypeScript packages for building command line tools

> Cliffy splits a command line toolkit into eight independent packages for command definition, flag parsing, prompts, tables and terminal primitives, published for Deno, Node and Bun from a single JSR scope. The advantage is that you take only the pieces you need, and the cost is that the packages are published separately and one of them, testing, still only runs on Deno.

**c4spar/cliffy** — A TypeScript-first, runtime-agnostic command-line toolkit for Deno, Node.js and Bun: featuring a command framework, argument parser, interactive prompts, tables, ANSI utilities, and more.

- Repository: https://github.com/c4spar/cliffy
- Website: https://cliffy.io
- Stars: 1,185 · Forks: 81
- Language: TypeScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/c4spar-cliffy

## Seven packages rather than one framework

Cliffy is described as a TypeScript-first, runtime-agnostic command-line toolkit for building complex CLIs with Deno, Node.js and Bun. The interesting decision is what it is not. There is no single cliffy package with everything inside. The repository root shows one directory per module, ansi, command, flags, keycode, keypress, prompt, table and testing, plus internal, examples, tasks, bench and upgrade directories.

That granularity is the design argument. A tool that only needs an argument parser and a table should not have to pull in a prompt system that reads from a TTY, and a library that registers signal handlers and manipulates the cursor is a different kind of dependency from one that formats text. Splitting the toolkit means the dependency graph of your CLI matches the features you actually turned on.

The table in the README names each package and what it does. The ansi module gives chainable ANSI escape sequences. The command module creates complex and type-safe command line tools with built-in input validation, auto generated help and shell completions. The flags module parses command line arguments and is used by the command module. The keycode module parses ANSI key codes, and the keypress module listens to keypress events through Promise, AsyncIterator and EventTarget APIs. The prompt module creates simple and powerful interactive prompts. The table module creates CLI tables with border, padding and nested tables. The testing module is described as experimental helper functions for testing.

Every one of these ships with its own version, its own download counters and its own runtime support row, which is a lot of metadata to maintain and also a useful signal about how the project is structured.

## The command package is the one that carries the type safety claim

The word type-safe appears in the description of the command module, and that is the claim worth examining, because it is where a CLI framework either delivers or does not.

The command module is described as creating complex and type-safe command line tools with built-in input validation, auto generated help and shell completions and more. Those four capabilities are what turns argument handling into a CLI framework rather than a parser. Input validation means a flag that expects a number rejects something else at parse time, and generated help means the help text is derived from the same definitions the parser uses, so the two cannot drift apart. Shell completions are generated from the same source, so a new subcommand is completable the moment it is declared.

The flags module underneath does the actual parsing and is documented as being used by the command module, which keeps the layering clean: if you only need parsing, you depend on flags alone, and if you need the command layer, the parser is already there. The repository has a dedicated examples directory per module, including examples/command.ts and an examples/command/ directory, and a bench directory, which suggests the performance of the parsing path was measured rather than assumed.

The trade-off is bundle discipline on your side. A type-safe command definition is a typed object graph, and the number of options you expose determines how much of that graph has to exist at runtime. There is no mention in the README of a tree shaking guarantee, so the practical question of how much ends up in a Node bundle is one the documentation does not answer.

## The terminal primitives are a separate dependency from the command layer

Three of the packages exist to deal with the terminal itself, and they are useful far outside a command line application. The ansi module provides chainable ANSI escape sequences, which is a small thing to do well and a common source of ugly code when done by hand.

The keycode module parses ANSI key codes, and the keypress module listens to keypress events with Promise, AsyncIterator and EventTarget APIs. Offering three different consumption styles for the same event is a deliberate choice, and it maps onto how people actually write this code today: a Promise suits a one off read, an AsyncIterator suits a loop that yields keys, and an EventTarget suits an application that already has an event architecture.

The prompt module is described as creating simple and powerful interactive prompts, and it is the package that depends on the terminal layer rather than the other way around. The table module, with border, padding and nested tables, is purely about output formatting and has no terminal dependency at all, which is why it can be used in a log line or a file.

For a library author this is the part of Cliffy most likely to be adopted piecemeal. The prompt module is the obvious candidate for anyone who has written a confirm dialog by hand, and the table module is the obvious candidate for anyone printing aligned data in a terminal without a dependency.

## The testing package is the one that breaks the runtime agnostic promise

The runtime column at the end of the package table is where the exceptions live, and there is exactly one. Every package is listed for Deno, Node and Bun except the testing module, which is marked Deno only. The description calls it experimental, and the runtime column confirms the restriction.

That is the honest boundary of the runtime agnostic claim, and it is worth noticing that the restriction is on the testing helper rather than on the core. It suggests the helpers depend on Deno specific test facilities, which is a different situation from a package that could not be ported because of terminal handling. If your CI runs on Node, the testing package is simply not an option, and the rest of the toolkit still is.

The experimental label also carries a versioning consequence. An experimental helper is one whose API may change between minor versions without the same care as a stable package, and Cliffy publishes each package independently, so the experimental package can move without the others.

The repository also has a .deno-version, a .node-version and a .bun-version file at the root, and the CI workflow badge points at a test.yml workflow. Those three files are a quiet indication that the project tests against specific runtime versions rather than whatever happens to be installed, which is what you want from a toolkit whose whole premise is that one codebase runs in three places.

## Distribution runs through JSR and deno.land

Cliffy is published as a set of scoped packages on JSR, with the scope badge at the top of the README pointing at jsr.io/@cliffy, and each package in the table has its own JSR link, for example jsr.io/@cliffy/command and jsr.io/@cliffy/prompt. There is also a deno.land badge for the cliffy module, which points at the Deno module registry, and the presence of both badges means the packages are reachable from the two registries a TypeScript CLI author is likely to use.

The homepage is cliffy.io, and the documentation lives at cliffy.io/docs, with a separate page per package such as cliffy.io/docs/command/ and cliffy.io/docs/prompt/. A Discord badge is also present for community discussion.

Versioning has been quick lately. The recent releases are v1.3.1 and v1.3.0, both on 2026-09-18, then v1.2.1 on 2026-06-04. Two releases on the same day is normal for a monorepo publishing several packages, and it also means the release process is automated. The last push to the repository was on 2026-09-18, and the project is not archived. A CHANGELOG.md is present at the root, which is the file to read before a major version bump.

The licence is MIT, with the text in the LICENSE file. For a library that is likely to end up inside a proprietary CLI, that permissive grant is the least interesting part of the decision and also the easiest one to satisfy.

## The alternative is assembling the same pieces per runtime

The comparison that matters here is not against another JavaScript CLI framework, since Cliffy is one of the few that genuinely spans three runtimes. The alternative is the one most teams start from: use the argument handling each runtime ships, write the table formatting by hand, and hand roll the prompt.

The difference in approach is that Cliffy centralises the terminal facing code. Node, Deno and Bun each have their own conventions for reading arguments and their own behaviour around stdin, and a codebase that has to run on all three ends up with conditional code at exactly the points where the terminal is involved. Cliffy pushes that variation into its own packages so the application code stays uniform.

The cost of that centralisation is that you take on a dependency that has to keep working across three runtime ecosystems, and the runtime column in the package table is where you check that. The testing helper is already Deno only, which is a small reminder that the abstraction is not complete.

If your CLI is internal, has two flags and runs on one runtime, none of this is worth it. If it is distributed, has subcommands, and is expected to run wherever the user has Node, the case for a shared toolkit with generated help and completions is much stronger, and Cliffy is one of the few options that keeps the same code on all three.

## Conclusion

Cliffy suits TypeScript teams building a real CLI who want typed commands, generated help and shell completions without writing a parser, and who want to run the same code on Deno, Node and Bun. Do not adopt it for a single flag script, where the platform's own argument handling is enough. Verify first which packages you need against the runtime column in the package table, because the testing helper is Deno only, and check the changelog before a major version bump since the 1.3 line is still moving.

## FAQ

### Which runtimes does Cliffy support?

Cliffy is described as runtime agnostic and works with Deno, Node.js and Bun. The package table lists the command, flags, prompt, table, ansi, keycode and keypress packages for all three, while the testing module is marked as Deno only.

### What packages does Cliffy publish?

There are eight, each published separately on JSR under the @cliffy scope: ansi for chainable escape sequences, command for type-safe command line tools, flags for argument parsing, keycode for ANSI key codes, keypress for keypress events, prompt for interactive prompts, table for CLI tables and testing for experimental test helpers.

### Does Cliffy generate help text and shell completions?

Yes. The command module is described as providing built-in input validation, auto generated help and shell completions, on top of the flags module that does the argument parsing.

### How do I install Cliffy?

The README does not list an install command, it points to the JSR scope at jsr.io/@cliffy and to the deno.land module registry, with per package pages linked from the package table and the documentation hosted at cliffy.io/docs.

### Is the Cliffy testing helper production ready?

The package table describes the testing module as experimental helper functions for testing, and its runtime column lists Deno only, so it is not usable from Node or Bun and its API may still change.

## Sources

- [c4spar/cliffy on GitHub](https://github.com/c4spar/cliffy)
- [License: MIT](https://github.com/c4spar/cliffy/blob/main/LICENSE)
- [Project website](https://cliffy.io)
- [README](https://github.com/c4spar/cliffy/blob/main/README.md)
- [Releases](https://github.com/c4spar/cliffy/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/c4spar-cliffy
