cmd2 declares its version from a git tag and carries two documentation toolchains
cmd2 - quickly build feature-rich and user-friendly interactive command line applications in Python
At a glance
- What is it?
- A framework for building interactive Python command-line applications on top of the standard library's cmd module, with argument parsing, generated help, completion, history, scripting and output styling provided by the framework. The repository is careful and modern in most places, with two or three configurations that suggest a migration in progress.
- Who is it for?
- cmd2 fits a team writing an internal tool that starts as a script and grows into a scriptable interface, who wants completion, history and generated help without assembling those from separate libraries, and who can live with a Python 3.11 floor. It does not fit a project that must run on older interpreters, or one that needs a hard dependency ceiling, since all four runtime requirements carry lower bounds only.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 10, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two ways to declare arguments, one of them a decorator
The framework's central promise is that one declaration produces three things: validation, help text and completion. There are now two ways to make that declaration. The long-standing path is an argument parser per command, and a single parser definition is what drives all three outputs. The newer path is annotation-based and lives in its own module:
from typing import Annotated
import cmd2
from cmd2.annotated import Argument, Option
class App(cmd2.Cmd):
"""A small interactive application."""
@cmd2.with_annotated
def do_greet(
self,
name: Annotated[str, Argument(help_text="person to greet")],
count: Annotated[int, Option(help_text="number of greetings")] = 1,
) -> None:
"""Greet a person."""
for _ in range(count):
self.poutput(f"Hello, {name}!")
if __name__ == "__main__":
App().cmdloop()A decorator on the method turns annotated parameters into a positional argument and an option, in the style made familiar by a different command-line framework. Both styles are supported, which is the right call for a project with an existing user base, and it is also the main thing to decide before writing code, because the two produce different-looking commands and mixing them inside one application means learning both.
The version number is read from a git tag
There is no version string anywhere in the project file. It is declared dynamic, and the build requirements pull in a plugin that derives it from the repository's tags at build time. That is a good practice for a library with a long history and a weekly release rhythm, and it has two practical consequences. A source checkout with no tags produces a version that is not a release, and a distribution built from a shallow clone or an exported archive rather than a clone may not produce a usable version at all. For anyone vendoring the source or building wheels in an offline pipeline, that is the line to check first. The release history itself is unremarkable and healthy: three patch releases in three consecutive weeks through late August and early September 2026.
A 3.11 floor, a 3.15 classifier and free threading marked stable
The interpreter support declaration is generous in one direction and strict in the other. The floor is Python 3.11, while the classifier list runs from 3.11 through 3.15 and includes a stable rating for free-threaded builds. That floor is worth noting because the module the framework subclasses, the standard library's interactive command base, has been available for far longer and works on much older interpreters. Dropping older versions is a deliberate choice rather than a technical constraint. The free-threading classifier is the more forward-looking statement: it tells an installer that the library is expected to behave under a no-global-lock interpreter, which is a claim the test matrix has to be backing.
Four dependencies, every one of them open-ended
The runtime requirement list is four entries: a terminal toolkit, a clipboard helper, a rich text rendering library, and an argument-parsing integration for it. All four are lower bounds with no upper caps. That is the modern convention and it keeps installs uneventful, and it also means you cannot get a reproducible environment from the package metadata alone, since a resolver is free to take a newer major release of any of the four. The page calls the package pure-Python with a small dependency footprint, which is accurate about its own source and slightly incomplete about the install: the terminal toolkit is the significant one, and it brings its own compiled and platform-specific pieces. Four is genuinely few for what this does, but few is not the same as none.
Two documentation toolchains are configured and one is driven
The repository root holds a MkDocs configuration, a read-the-docs configuration, and a dependency group that includes a newer static site generator along with an API documentation plugin. The Makefile is unambiguous about which one is current: the documentation targets build and serve through the newer generator, and one of them runs a build in strict mode specifically to fail on warnings or errors. That is a good target to have. But it leaves the older configuration file sitting in the root with nothing pointing at it, so anyone arriving expecting to extend the documentation has two entry points and one supported path. If you are contributing docs, the strict build target tells you which generator will actually be judged.
make install needs a package manager, a specific interpreter and npm
The single setup target does four unrelated things. It installs a named Python version through a modern environment manager, syncs the project against it, installs the project's git hooks through a pre-commit runner, and then runs an npm install. The last step is the surprise, and it exists because of the two configuration files: the only Node dependencies in the repository are a formatter and its plugin for TOML files, wired in through a pre-commit hook so the project file stays formatted alongside the Python. So a contributor setting up a Python library needs Node installed, and a contributor who skips that step has a pre-commit hook that will fail the first time it runs. The target is a convenience wrapper and it assumes a machine with both runtimes.
Two type checkers, and the test suite forces UTF-8
The quality targets are unusually thorough and two details stand out. First, static typing is run twice, by a well-known checker and by a newer one written by a different author, both wired into the same aggregate target and also exposed separately. Running two independent checkers catches disagreements between them, which is a genuine argument rather than redundancy. Second, the test target launches the interpreter with a flag that forces UTF-8 mode for the whole run. That flag is a direct consequence of the feature set: the framework advertises accepting Unicode in commands, arguments, file names and output, running UTF-8 command scripts, and test coverage of all of it, and forcing the mode means the suite fails rather than silently degrading on a machine with a legacy default encoding.
Editorial conclusion
cmd2 fits a team writing an internal tool that starts as a script and grows into a scriptable interface, who wants completion, history and generated help without assembling those from separate libraries, and who can live with a Python 3.11 floor. It does not fit a project that must run on older interpreters, or one that needs a hard dependency ceiling, since all four runtime requirements carry lower bounds only. Before adopting it, decide which argument-declaration style you want, because the parser route and the annotation decorator route coexist and are not interchangeable, and check whether the documentation tooling you plan to extend is the MkDocs configuration or the newer generator. The newest release is 4.2.4 from 2026-09-08 and the last push is dated 2026-10-02.
Frequently asked questions
What do I subclass to build a cmd2 application?
The standard library's interactive command base, adding methods for each command and running the command loop. A minimal example subclasses it, decorates one method, and calls the loop under a main guard.
How do I declare command arguments in cmd2?
Two ways. You can attach an argument parser per command, and a single parser drives validation, help and completion. Or you can annotate the parameters and apply the framework's decorator, which builds a positional argument and an option from type annotations in a style similar to a different command-line library.
What are cmd2's runtime dependencies?
Four: a terminal toolkit, a clipboard helper, a rich rendering library and an argument-parsing integration for it. All four are declared with lower bounds only and no upper caps, so nothing in the metadata pins an upper bound for you.
Which Python versions does cmd2 support?
Python 3.11 and later. The classifier list runs from 3.11 through 3.15 and includes a stable rating for free-threaded builds. The floor is stricter than the standard library module it builds on, which supports much older interpreters.
Can a cmd2 application run operating system commands and Python scripts?
Yes, by design. There is a built-in shell command with an exclamation-mark shortcut, output can be redirected to files or the clipboard or piped through other shell commands, and Python scripts can run inside the application for loops, branching and integration with its own commands and data.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/python-cmd2-cmd2)