Releases: python-cmd2/cmd2
Release list
4.2.4 (September 8, 2026)
What's Changed
- uv run does its own sync of the project env with default groups by @tleonhardt in #1746
- Add aggregation jobs to maxtrix workflows by @tleonhardt in #1747
- Keep the right prompt out of the committed prompt frame by @tleonhardt in #1749
- Write redirected and piped output as UTF-8, and stop the suite inheriting colour settings by @tleonhardt in #1753
- Fix ty errors present in version 0.0.79 which came out yesterday by @tleonhardt in #1754
Full Changelog: 4.2.3...4.2.4
4.2.3 (September 2, 2026)
What's Changed
- Fix ty unresolved-attribute warnings and add mypy back by @tleonhardt in #1737
- Update ruff to version 0.16.5 by @tleonhardt in #1738
- Bump github/codeql-action from 4.37.8 to 4.37.9 by @dependabot[bot] in #1740
- Update readme by @tleonhardt in #1739
Full Changelog: 4.2.2...4.2.3
4.2.2 (August 25, 2026)
- Documentation Improvements
- Improved documentation in attempt at making some recommended best-practices more discoverable
- Fixed the broken
cmd_as_argumentexample
4.2.1 (August 22, 2026)
- Enhancements
- Added bracketed paste support so multiple pasted commands execute sequentially and multiline commands continue as expected.
- Bug Fixes
- Enabled Ctrl-Z suspension at the prompt
4.2.0 (August 6, 2026)
- Enhancements
@with_annotatedargument groups can now contain anArgumentBlock's arguments. AGroupmember names a command-line argument, and a block expands into one argument per field, so its fields are named:Group("host", "port").
- Breaking Changes
- A
Groupmember now names an argument rather than a parameter. The two differ only for anArgumentBlockparameter, which is expanded away and has no argument of its own:Group("conn")now raisesValueErrorpointing at the block's fields. It previously produced an empty argument group ingroups=and aKeyErrorinmutually_exclusive_groups=. - Whether a
Groupmember exists is now validated when the parser is built rather than at decoration time, because a block's field names cannot be known without resolving its type hint, and resolving hints eagerly would break forward-referenced annotations. A typo in a member name still raisesValueError, but on first use of that command rather than at class definition. The spec-shape rules (a member listed twice, a member in two groups,required=Trueon a plain group, the mutex nesting rules) are unaffected and still hard-fail at decoration time.
- A
- Bug Fixes
- Fixed
@with_annotated(base_command=True)not listing its subcommands under the positional arguments section of the parent command's--help, unlikeargparseandCmd2ArgumentParser. They were placed in an untitled section of their own instead. Passingsubcommand_titleorsubcommand_descriptionstill gives the subcommands a dedicated section (#1715). - Fix
@with_annotateddecorator so usingArgumentBlockworks with groups (#1718). - Fixed bug where already sorted
choices_providerresults were being re-sorted (#1727).
- Fixed
4.1.2 (July 16, 2026)
What's Changed
- Updated getting_started.py example to remove multi-line command usage by @tleonhardt in #1713
- [Settable] description argument can now be rich text by @neoniobium in #1712
- Removed redundant ipython import which slowed application startup time by @kmvanbrunt in #1717
New Contributors
- @neoniobium made their first contribution in #1712
Full Changelog: 4.1.1...4.1.2
4.1.1 (July 9, 2026)
What's Changed
- Fixed mypy 2.2.0 failures by @tleonhardt in #1709
- Added ability to use Rich renderables as alert messages. by @kmvanbrunt in #1710
Full Changelog: 4.1.0...4.1.1
4.1.0 (July 7, 2026)
- Breaking Changes
- Renamed the
bottom_toolbarargument inCmd.__init__()toenable_bottom_toolbar. It is also now strictly an__init__parameter and not an instance attribute. complete_in_threadis now strictly an__init__parameter and not an instance attribute ofCmd.get_rprompt()is now only called if theenable_rpromptargument inCmd.__init__()is set toTrue.
- Renamed the
- Bug Fixes
- Fixed type hinting so that methods decorated with
with_annotatedno longer trigger spurious mypy errors and preserve their original signature. - Fixed cmd2 bypassing NO_COLOR and allow_style when setting prompt-toolkit's color depth.
- Fixed type hinting so that methods decorated with
- Enhancements
- New
cmd2.Cmdparameters- complete_in_thread: (boolean) if
True, then completion will run in a separate thread. IfFalsethen completion runs in the main thread and causes it to block if slow. Defaults toTrue. - refresh_interval: (float) How often, in seconds, to automatically refresh the UI. Defaults to 0.0. This is used for bottom toolbars and right prompts which have dynamic content needing to be refreshed at regular intervals and not just when a key is pressed.
- complete_in_thread: (boolean) if
- Improved getting_started.py example
- Shows how to properly and safely use a background thread to update the bottom toolbar with dynamic content
- Demonstrates how to use the
@with_argparserand@with_argument_blockdecorators for parsing command arguments
- New
- Experimental features
@with_annotatednow supportsfrozenset[T]collection parameters, alongside the existinglist[T],set[T], andtuple[T, ...]collection types.@with_annotatedmutually exclusive groups now accept atitle/descriptionto render the group as a titled help section (argparse's one supported nesting, a mutex inside an argument group), declared in one place with no pairedgroups=entry.@with_annotatednow validatesgroups/mutually_exclusive_groupsspecs eagerly at decoration time, so a misconfigured group (a member that names no parameter, a parameter placed in two groups, a mutex group spanning or partially overlapping argument groups, a titled section declared in two places, orGroup(required=True)on a plain group) hard-fails when the class is defined instead of being deferred to first command use where the error was swallowed. The checks read parameter names only, so forward-referenced annotations still decorate cleanly.Argument/Optionaccept a newallow_unknown_entryflag forEnumparameters. When set, a command-line token matched by neither a member value nor name is routed through the enum's own_missing_hook, so an enum can resolve aliases, alternate spellings, or special keywords. A token that_missing_declines (returnsNone) is still rejected.@with_annotatednow supports a union ofEnumsubclasses (e.g.EnumA | EnumB). Each member keeps its own converter and a token resolves to the first member that accepts it, so when two members share a representation the earlier one in the union wins. A member whose_missing_raises on a token declines it (the next member is still tried) rather than aborting the union, and a merged "choose from ..." error is raised only when every member declines. Unions containing aLiteralor any non-Enummember are still rejected as ambiguous.Argument/Optionaccept newconverterandpreprocesshooks for custom string conversion, giving@with_annotatedparity with a hand-builtadd_argument(type=...)(a rawtype=in the metadata is still rejected).converteris aCallable[[str], Any]that replaces the inferredtype=converter; because it owns the conversion, the annotation may be any type (an otherwise unsupported type likedatetime, or an otherwise-ambiguous multi-member union likeint | str, becomes legal) and the inferredchoices/completer are dropped.preprocessis aCallable[[str], str]that runs before the inferred converter, transforming the raw token while keeping the inferredtype=/choices/completer (e.g.preprocess=str.loweron anEnum). The two are mutually exclusive on one parameter and neither may be combined with a value-less action.@with_annotatednow supports reusable argument blocks: a parameter typed with a@dataclassthat subclasses the newcmd2.ArgumentBlocktrait expands each field into a flat command-line argument, and the parsed values are reconstructed into a dataclass instance passed to the command, letting several commands reuse the same fields without duplication. See the annotated documentation.- A command can share an argument block with its subcommands via
cmd2_base_args/cmd2_parent_argsparameters, passing parent-level options down without redeclaring them.
4.0.0 (June 5, 2026)
Summary
cmd2 now has a dependency on prompt-toolkit which serves as a pure-Python cross-platform replacement for GNU Readline. Previously, cmd2 had used different readline dependencies on each Operating System (OS) which was at times a very frustrating developer and user experience due to small inconsistencies in these different readline libraries. Now we have consistent cross-platform support for tab-completion, user terminal input, and history. Additionally, this opens up some cool advanced features such as support for syntax highlighting of user input while typing, auto-suggestions similar to those provided by the fish shell, and the option for a persistent bottom bar that can display realtime status updates while the prompt is displayed.
Details
- Breaking Changes
- Removed all use of
readlinebuilt-in module and underlying platform libraries - Deleted
cmd2.rl_utilsmodule which dealt with importing the properreadlinemodule for each platform and provided utility functions related toreadline - Added a dependency on
prompt-toolkitand a newcmd2.pt_utilsmodule with supporting utilities - Dropped support for Python 3.10.
cmd2now requires Python 3.11 or later - Removed Transcript Testing feature set along with the
history -toption for generating transcript files and thecmd2.transcriptmodule- This was an extremely brittle regression testing framework which should never have been built into cmd2
- We recommend using pytest for unit and integration tests and Robot Framework for acceptance tests. Both of these frameworks can be used to create tests which are far more reliable and less brittle.
- Async specific:
prompt-toolkitstarts its ownasyncioevent loop in everycmd2application- Removed
cmd2.Cmd.terminal_lockas it is no longer required to support things likecmd2.Cmd.async_alert - Removed
cmd2.Cmd.async_refresh_promptandcmd2.Cmd.need_prompt_refreshas they are no longer needed
- Removed
completerfunctions must now return acmd2.Completionsobject instead oflist[str].choices_providerfunctions must now return acmd2.Choicesobject instead oflist[str].- An argparse argument's
descriptive_headersfield is now calledtable_columns. CompletionItem.descriptive_datais now calledCompletionItem.table_data.- Removed
DEFAULT_DESCRIPTIVE_HEADERS. This means you must definetable_columnswhen usingCompletionItem.table_datadata. Cmd.default_sort_keymoved toutils.DEFAULT_STR_SORT_KEY.- Moved completion state data, which previously resided in
Cmd, into other classes.Cmd.matches_sorted->Completions.is_sortedandChoices.is_sortedCmd.completion_hint->Completions.hintCmd.formatted_completions->Completions.table(Now a Rich Table)Cmd.allow_appended_space/allow_closing_quote->Completions.allow_finalization
- Removed
Cmd.matches_delimitedsince it's no longer used. - Removed
flag_based_completeandindex_based_completefunctions since their functionality is already provided in arpgarse-based completion. - Changed
Statement.multiline_commandfrom a string to a bool. - Made
Statement.arg_lista property which generates the list on-demand. - Renamed
Statement.outputtoStatement.redirector. - Renamed
Statement.output_totoStatement.redirect_to. - Removed
Statement.pipe_tosince it can be handled byStatement.redirectorandStatement.redirect_to. - Changed
StatementParser.parse_command_only()to return aPartialStatementobject. - Renamed
Macro.arg_listtoMacro.args. - Removed
terminal_utils.pysinceprompt-toolkitprovides this functionality. - Replaced
async_alert()andasync_update_prompt()with a single function calledadd_alert(). This new function is thread-safe and does not require you to acquire a mutex before calling it like the previous functions did. - Removed
Cmd.default_to_shell. - Removed
Cmd.rulersincecmd2no longer uses it. - All parsers used with
cmd2commands must be an instance ofCmd2ArgumentParseror a child class of it. - Renamed
set_default_argument_parser_type()toset_default_argument_parser(). - Renamed
set_default_ap_completer_type()toset_default_argparse_completer(). - Removed
set_ap_completer_type()andget_ap_completer_type()sincecompleter_classis now a public member ofCmd2ArgumentParser. - Moved
set_parser_prog()toCmd2ArgumentParser.update_prog(). - Renamed
cmd2_handlertocmd2_subcommand_funcin theargparse.Namespacefor clarity. - Removed
Cmd2AttributeWrapperclass.argparse.Namespaceobjects passed to command functions now contain direct attributes forcmd2_statementandcmd2_subcommand_func. - Renamed
cmd2/command_definition.pytocmd2/command_set.py. - Removed
Cmd.doc_headerand thewith_default_categorydecorator. Help categorization is now driven by theDEFAULT_CATEGORYclass variable (see Simplified command categorization in the Enhancements section below for details). - Removed
Cmd.undoc_headersince all commands are now considered categorized. - Renamed
Cmd.cmd_func()toCmd.get_command_func(). cmd2no longer sets a default title for a subparsers group. If you desire a title, you will need to pass one in like thisparser.add_subparsers(title="subcommands"). This is standardargparsebehavior.TextGroupnow implementsHelpFormatterRenderable(see Enhancements section below for more details).- Removed
formatter_creatorparameter fromTextGroup.__init__(). - Removed
Cmd2ArgumentParser.create_text_group()method.
- Removed
argparseandRichintegration refactoring:- Renamed
argparse_custommodule toargparse_utils. - Moved the following classes from
argparse_utilstorich_utils:Cmd2HelpFormatterArgumentDefaultsCmd2HelpFormatterMetavarTypeCmd2HelpFormatterRawDescriptionCmd2HelpFormatterRawTextCmd2HelpFormatterTextGroup
- Replaced the global
APP_THEMEconstant inrich_utils.pywithget_theme(),reset_theme(), andupdate_theme()functions intheme.pyto support lazy initialization and safer in-place updates of the theme.
- Renamed
- Renamed
Cmd._command_parserstoCmd.command_parsers. - Removed
RichPrintKwargsTypedDictin favor of usingMapping[str, Any], allowing for greater flexibility in passing keyword arguments toconsole.print()calls. - Removed
always_show_hintsettable as it provided a poor user experience withprompt-toolkit cmd2redirection only captures output directed toself.stdout(e.g., viaself.poutput()). Standardprint()calls write directly tosys.stdoutand are not captured. However,print()calls withinpyscriptsand the interactive Python shell are treated as command output and sent toself.stdout, allowing them to be captured.- Verbose help table descriptions are no longer generated from help function output. The system now relies exclusively on command function docstrings.
- Removed
feedback_to_outputsettable and changedcmd2.Cmd.pfeedbackto always print toself.stdout - Removed
Cmd.parseline()since it was unused and merely wrappedStatementParser.parse_command_only().
- Removed all use of
- Enhancements
- New
cmd2.Cmdparameters- auto_suggest: (boolean) if
True, provide fish shell style auto-suggestions. These are grayed-out hints based on history. User can press right-arrow key to accept the provided suggestion. - bottom toolbar: (boolean) if
True, present a persistent bottom toolbar capable of displaying realtime status information while the prompt is displayed, see thecmd2.Cmd2.get_bottom_toolbarmethod that can be overridden as well as the updatedgetting_started.pyexample
- auto_suggest: (boolean) if
- New
cmd2.Cmdmethods- get_bottom_toolbar: populates bottom toolbar if
bottom_toolbarisTrue - get_rprompt: override to populate right prompt
- pre_prompt: hook method that is called before the prompt is displayed, but after
prompt-toolkitevent loop has started - read_secret: read secrets like passwords without displaying them to the terminal
- ppretty: a cmd2-compatible replacement for
rich.pretty.pprint()
- get_bottom_toolbar: populates bottom toolbar if
- New settables:
- max_column_completion_results: (int) Maximum number of completion results to display in a single column
- traceback_show_locals: (bool) Display local variables in tracebacks
cmd2.Cmd.selecthas been revamped to use the choice function fromprompt-toolkitwhen both stdin and stdout are TTYs- Add support for Python 3.15 by fixing various bugs related to internal
argparsechanges - Added
common_prefixmethod tocmd2.string_utilsmodule as a replacement foros.path.commonprefixsince that is now deprecated in Python 3.15 - Simplified command categorization:
- By default, all commands in a class are grouped under its
DEFAULT_CATEGORY. - Individual commands can still be manually moved using the
with_category()decorator. - For more details and examples, see the Help documentation and the
examples/default_categories.pyfile.
- By default, all commands in a class are grouped under its
CommandSetis now a generic class, which allows devel...
- New
4.0.0-rc2 (2026-06-04)
What's Changed
- feat: Experimental annotated argparse by @KelvinChung2000 in #1666
- Refactor and standardize argparse-related naming and metadata by @kmvanbrunt in #1670
- Breaking Changes
- Renamed
set_default_argument_parser_type()toset_default_argument_parser(). - Renamed
set_default_ap_completer_type()toset_default_argparse_completer(). - Renamed
cmd2_subcmd_handlertocmd2_subcommand_funcin theargparse.Namespacefor clarity.
- Renamed
New Contributors
- @KelvinChung2000 made their first contribution in #1666
Full Changelog: 4.0.0-rc1...4.0.0-rc2