This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
cmd2 is a framework for building interactive command-line applications (REPLs) in Python. It
extends the stdlib cmd module and is built on prompt-toolkit (interactive input, key bindings,
completion, toolbars) and Rich / rich-argparse (all output rendering, help formatting,
theming). Requires Python >= 3.11. Environment/package management is uv.
All work happens inside the uv-managed venv — run Python via uv run ..., and uvx ... for one-off
tools. Never pip install; if a new dependency is needed, state why before adding it.
make install # create the venv, install prek git hooks + prettier (one time)
make check # lock check + prek (ruff format/lint, prettier, typos) + ty + mypy
make test # pytest with coverage over tests/
make docs-test # verify the docs build cleanly (zensical)
make docs # build + serve docs with live reload
make help # list all targetsmake check, make test, and make docs-test must all pass before committing. Run make check
before creating or modifying any .py file as well.
Narrower targets: make format, make lint, make mypy, make ty, make typecheck.
Running a single test or file (coverage flags come from pyproject.toml addopts, so pass
--no-cov when you just want a fast run):
uv run pytest tests/test_cmd2.py --no-cov
uv run pytest tests/test_cmd2.py::test_base_help -x --no-cov
uv run pytest -k "toolbar" --no-covOn Windows-sensitive code, note make test runs pytest under python -Xutf8.
cmd2/cmd2.py (the Cmd class) is the core and by far the largest module. The flow for one line of
input is:
cmdloop()→_cmdloop()drives a prompt-toolkitPromptSession(created in_create_prompt_session), not readline. Completion, history, lexing, and key bindings are all supplied by prompt-toolkit adapters incmd2/pt_utils.py(Cmd2Completer,Cmd2History,Cmd2Lexer).onecmd_plus_hooks()parses the line into aStatement(cmd2/parsing.py— handles shortcuts, aliases, macros, terminators, multiline commands, redirection and pipe tokens), then runs the plugin hook chain._redirect_output()swapsself.stdoutfor a file or subprocess pipe when the statement has redirection, and restores it in thefinallypath.onecmd()looks up and calls thedo_*method.
Hooks (cmd2/plugin.py dataclasses, registered via register_postparsing_hook,
register_precmd_hook, register_postcmd_hook, register_cmdfinalization_hook,
register_preloop_hook, register_postloop_hook) are the supported extension points; the legacy
precmd/postcmd overrides still exist but are weaker.
Three layers, all producing a Cmd2ArgumentParser:
cmd2/argparse_utils.py—Cmd2ArgumentParserplus monkey-patching that extends argparse with rangenargstuples, per-argument completion metadata, and subcommand records.cmd2/decorators.py—@with_argparser,@with_argument_list,@with_category,@as_subcommand_toattach parsers/metadata todo_*methods.cmd2/annotated.py— the newer, still-experimental Typer-style path:@with_annotatedbuilds a parser from a function's type hints, withArgument/Optionmetadata viatyping.Annotated.
Parsers are built lazily and cached by the CommandParsers class in cmd2.py (_build_parser), so
that CommandSet-supplied subcommands can be attached and detached at runtime.
cmd2/argparse_completer.py walks a parser to produce tab completions; completion results are
Completions/CompletionItem objects from cmd2/completion.py.
Never print directly — go through Cmd.print_to / poutput / perror / pwarning / pfeedback /
ppaged, which route to Rich consoles defined in cmd2/rich_utils.py (Cmd2GeneralConsole,
Cmd2RichArgparseConsole, Cmd2ExceptionConsole). Styling flows one way: cmd2/styles.py
(style-name StrEnum + defaults) → cmd2/theme.py (single global theme, updated in-place for Rich
and exposed to prompt-toolkit through a DynamicStyle) → cmd2/pt_utils.py (rich_to_pt_style
converts Rich styles into prompt-toolkit ones). Adding a color or style means touching
styles.py/colors.py/theme.py, not hardcoding ANSI.
cmd2/command_toolbar.py and cmd2/pager.py are the trickiest parts of the codebase: they keep a
prompt-toolkit application alive during synchronous command execution (bottom toolbar refresh,
paging) while stdout is proxied and redirection may be active. Changes there need care around
threads, signals, and Windows behavior.
cmd2/command_set.py (CommandSet) lets commands live in separate classes that are registered and
unregistered at runtime via Cmd.register_command_set; cmd2/py_bridge.py exposes the app to
embedded Python/IPython shells and run_pyscript while keeping isolation.
- Ruff is authoritative for format and lint (
ruff.toml, line length 127, double quotes). Do not suppress lint errors in code you write; broad ignores already exist forexamples/andtests/. - Both
mypy --strictandtymust pass on thecmd2package. Full type annotations are required on all library code (excluded:tests/,examples/,docs/). - Docstrings are enforced by pydocstyle rules; public API items are documented in
docs/api/*.mdand rendered by mkdocstrings, so keep docstrings accurate when changing signatures. - Anything not documented under
docs/api/is not public API (cmd2/constants.pysays so explicitly). - Add user-visible changes to
CHANGELOG.mdunder the current in-progress version heading. mainis the branch for the next PATCH release; MAJOR/MINOR work happens on a branch named for the target version. Releases are tagged and published frommain.- Do not commit spec, plan, or markdown documents without asking first. Save plans to
~/.superpowers/plans/rather than the project directory.
Never add "Co-Authored-By" lines to commits. Do not include Claude attribution in commit messages, PR descriptions, or any git metadata. Do not include Claude session URL in commit messages, PR descriptions, or any git metadata.