Getting Started
Here's a quick walkthrough of the simple
getting_started.py
example application which demonstrates many features of cmd2:
The following animation shows the cat, echo, and intro commands in action:

If you don't want to type as we go, here is the complete source (you can click to expand and then click the Copy button in the top-right):
getting_started.py
#!/usr/bin/env python3
"""An example cmd2 application demonstrating many common features.
Features demonstrated include all of the following:
1) Colorizing/stylizing output
2) Persistent history
3) How to run an initialization script at startup
4) How to group and categorize commands when displaying them in help
5) Opting-in to using the ipy command to run an IPython shell
6) Allowing access to your application in py and ipy
7) Displaying an intro banner upon starting your application
8) Using a custom prompt
9) How to make custom attributes settable at runtime
10) Shortcuts for commands
11) Persistent bottom toolbar with realtime status updates
12) Right prompt which displays contextual information
13) Background thread to update the content displayed by the bottom toolbar outside of the UI thread to keep things responsive
14) Using preloop() and postloop() hooks to start and stop a background thread
15) Using the with_annotated decorator to parse typed command arguments
16) Using the with_argparser decorator to parse command arguments with a custom parser
"""
import argparse
import datetime
import pathlib
import sys
import threading
from typing import Annotated
from prompt_toolkit.application import get_app
from prompt_toolkit.formatted_text import AnyFormattedText
from rich.style import Style
from rich.text import Text
import cmd2
from cmd2 import (
Color,
stylize,
)
from cmd2.annotated import Option
class BasicApp(cmd2.Cmd):
"""Cmd2 application to demonstrate many common features."""
DEFAULT_CATEGORY = "My Custom Commands"
def __init__(self) -> None:
"""Initialize the cmd2 application."""
# Startup script that defines a couple aliases for running shell commands
alias_script = pathlib.Path(__file__).absolute().parent / ".cmd2rc"
# Create a shortcut for one of our commands
shortcuts = cmd2.DEFAULT_SHORTCUTS
shortcuts.update({"&": "intro"})
super().__init__(
auto_suggest=True,
enable_bottom_toolbar=True,
enable_rprompt=True,
include_ipy=True,
persistent_history_file="cmd2_history.dat",
refresh_interval=0.5, # refresh the UI twice a second to keep the bottom toolbar timestamp current
shortcuts=shortcuts,
startup_script=str(alias_script),
)
# Prints an intro banner once upon application startup
self.intro = (
stylize(
"Welcome to cmd2!",
style=Style(color=Color.GREEN1, bgcolor=Color.GRAY0, bold=True),
)
+ " Note the full Unicode support: 😇 💩"
+ " and the persistent bottom bar with realtime status updates!"
)
# Show this as the prompt when asking for input
self.prompt = "myapp> "
# Allow access to your application in py and ipy via self
self.self_in_py = True
# Color to output text in with echo command
self.foreground_color = Color.CYAN.value
# Make foreground_color settable at runtime
fg_colors = [c.value for c in Color]
self.add_settable(
cmd2.Settable(
"foreground_color",
str,
Text.assemble(
"Foreground color to use with echo command ",
"(Options: ",
Text("Green", Style(color=Color.GREEN)),
", ",
Text("Red", Style(color=Color.RED)),
", ",
Text("Blue", Style(color=Color.BLUE)),
", ...)",
),
self,
choices=fg_colors,
)
)
# Initialize background thread state for the bottom toolbar
self._toolbar_state = {"now": ""}
self._toolbar_lock = threading.Lock()
self._stop_thread_event = threading.Event()
self._toolbar_thread: threading.Thread | None = None
def _update_toolbar_state(self) -> None:
"""Background thread worker to update toolbar state continuously."""
while not self._stop_thread_event.is_set():
# Get the current time in ISO format with 0.01s precision
dt = datetime.datetime.now(datetime.timezone.utc).astimezone()
now = dt.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-4] + dt.strftime("%z")
with self._toolbar_lock:
self._toolbar_state["now"] = now
# Sleep to yield CPU, polling 4 times a second
self._stop_thread_event.wait(0.25)
def preloop(self) -> None:
"""Hook method executed once when the cmdloop() method is called."""
self._stop_thread_event.clear()
self._toolbar_thread = threading.Thread(target=self._update_toolbar_state, daemon=True)
self._toolbar_thread.start()
def postloop(self) -> None:
"""Hook method executed once when the cmdloop() method is about to return."""
if self._toolbar_thread and self._toolbar_thread.is_alive():
self._stop_thread_event.set()
self._toolbar_thread.join()
def get_bottom_toolbar(self) -> AnyFormattedText:
left_text = sys.argv[0]
with self._toolbar_lock:
now = self._toolbar_state.get("now", "")
# Fetch the terminal width to calculate padding for right-alignment.
# If called outside a running app loop (e.g., in unit tests), get_app()
# safely returns a dummy app with an 80-column fallback.
cols = get_app().output.get_size().columns
padding_size = cols - len(left_text) - len(now)
if padding_size < 1:
padding_size = 1
padding = " " * padding_size
# Return formatted text for prompt-toolkit
return [
("ansigreen", left_text),
("", padding),
("ansicyan", now),
]
def get_rprompt(self) -> AnyFormattedText:
current_working_directory = pathlib.Path.cwd()
style = "bg:ansired fg:ansiwhite"
text = f"cwd={current_working_directory}"
return [(style, text)]
@cmd2.with_annotated
def do_cat(
self,
path: pathlib.Path, # Required positional argument with type annotation, tab-completes filesystem paths automatically
numbered: Annotated[ # Optional flag argument with type annotation, default value, and help text
bool, Option("-n", "--number", help_text="prefix each line with its number")
] = False,
) -> None:
"""Print a file's contents. `path` tab-completes filesystem paths automatically.
Try:
cat <TAB> # path completes files/dirs -- no completer wired
cat notes.txt
cat notes.txt -n # -n / --number, declared via Option metadata
cat notes.txt --no-number
"""
text = path.read_text()
lines = text.splitlines()
if numbered:
numbered_lines = []
for index, line in enumerate(lines, start=1):
numbered_lines.append(f"{index}: {line}")
self.ppaged("\n".join(numbered_lines))
else:
# Just print the contents using a pager
self.ppaged(path.read_text())
def do_intro(self, _: cmd2.Statement) -> None:
"""Display the intro banner.
This command uses raw statement parsing. In general, we strongly recommend against this approach. But since this
command effectively takes no arguments, it is safe to use raw statement parsing here.
The & key is also used as a shortcut for this command, so you can also type & to display the intro banner.
"""
self.poutput(self.intro)
@staticmethod
def _build_echo_parser() -> cmd2.Cmd2ArgumentParser:
"""Parser factory method for use with the echo command."""
echo_parser = cmd2.Cmd2ArgumentParser(description="Command that echoes input.")
echo_parser.add_argument("-u", "--upper", action="store_true", help="uppercase the output")
echo_parser.add_argument("-r", "--repeat", type=int, default=1, help="output [n] times")
echo_parser.add_argument("words", nargs="+", help="words to print")
return echo_parser
@cmd2.with_argparser(_build_echo_parser)
def do_echo(self, args: argparse.Namespace) -> None:
"""Command using with_argparser decorator for parsing arguments."""
output_str = " ".join(args.words)
if args.upper:
output_str = output_str.upper()
for _ in range(args.repeat):
self.poutput(
stylize(
output_str,
style=Style(color=self.foreground_color),
)
)
if __name__ == "__main__":
app = BasicApp()
sys.exit(app.cmdloop())
Basic Application
The example defines BasicApp as a subclass of cmd2.Cmd:
At the end of the file, the application creates an instance of that class and passes control to the cmd2.Cmd.cmdloop method:
Run the example from the repository root:
The application displays its intro banner and the custom myapp> prompt. Because BasicApp
subclasses cmd2.Cmd, it also includes cmd2's built-in commands and features. Type quit to
exit.
Create a Setting
cmd2 includes robust support for Settings. The example stores the color
used by the echo command in foreground_color, then exposes that attribute as a runtime setting.
The choices are the color values supported by cmd2.Color:
# Color to output text in with echo command
self.foreground_color = Color.CYAN.value
# Make foreground_color settable at runtime
fg_colors = [c.value for c in Color]
self.add_settable(
cmd2.Settable(
"foreground_color",
str,
Text.assemble(
"Foreground color to use with echo command ",
"(Options: ",
Text("Green", Style(color=Color.GREEN)),
", ",
Text("Red", Style(color=Color.RED)),
", ",
Text("Blue", Style(color=Color.BLUE)),
", ...)",
),
self,
choices=fg_colors,
)
)
The cmd2.Cmd.add_settable method registers a cmd2.utils.Settable that validates new values
against fg_colors. Use the built-in set command to inspect or change it:
The first command displays the current value. The second changes the color used by subsequent echo
output.
Commands
Methods whose names start with do_ become commands. BasicApp defines three commands: cat,
echo, and intro. Each one demonstrates a different way to process arguments.
cat
The cat command uses cmd2.with_annotated to build its argument parser from type annotations.
The pathlib.Path annotation enables path completion, and cmd2.annotated.Option defines the
optional -n/--number flag:
@cmd2.with_annotated
def do_cat(
self,
path: pathlib.Path, # Required positional argument with type annotation, tab-completes filesystem paths automatically
numbered: Annotated[ # Optional flag argument with type annotation, default value, and help text
bool, Option("-n", "--number", help_text="prefix each line with its number")
] = False,
) -> None:
"""Print a file's contents. `path` tab-completes filesystem paths automatically.
Try:
cat <TAB> # path completes files/dirs -- no completer wired
cat notes.txt
cat notes.txt -n # -n / --number, declared via Option metadata
cat notes.txt --no-number
"""
text = path.read_text()
lines = text.splitlines()
if numbered:
numbered_lines = []
for index, line in enumerate(lines, start=1):
numbered_lines.append(f"{index}: {line}")
self.ppaged("\n".join(numbered_lines))
else:
# Just print the contents using a pager
self.ppaged(path.read_text())
The command uses cmd2.Cmd.ppaged so longer files can be viewed in a pager. Try it on the startup script included with the example:
echo
The echo command demonstrates cmd2.with_argparser. Its parser factory defines options for
uppercasing and repeating the output, plus one or more words to print:
@staticmethod
def _build_echo_parser() -> cmd2.Cmd2ArgumentParser:
"""Parser factory method for use with the echo command."""
echo_parser = cmd2.Cmd2ArgumentParser(description="Command that echoes input.")
echo_parser.add_argument("-u", "--upper", action="store_true", help="uppercase the output")
echo_parser.add_argument("-r", "--repeat", type=int, default=1, help="output [n] times")
echo_parser.add_argument("words", nargs="+", help="words to print")
return echo_parser
@cmd2.with_argparser(_build_echo_parser)
def do_echo(self, args: argparse.Namespace) -> None:
"""Command using with_argparser decorator for parsing arguments."""
output_str = " ".join(args.words)
if args.upper:
output_str = output_str.upper()
for _ in range(args.repeat):
self.poutput(
stylize(
output_str,
style=Style(color=self.foreground_color),
)
)
The decorator parses the command line and passes an argparse.Namespace to do_echo(). It also
generates command help from the parser. The method styles the text with the configured foreground
color and writes it with cmd2.Cmd.poutput, which supports cmd2 output redirection:
intro
The intro command takes no arguments, so it demonstrates the raw cmd2.Statement interface:
def do_intro(self, _: cmd2.Statement) -> None:
"""Display the intro banner.
This command uses raw statement parsing. In general, we strongly recommend against this approach. But since this
command effectively takes no arguments, it is safe to use raw statement parsing here.
The & key is also used as a shortcut for this command, so you can also type & to display the intro banner.
"""
self.poutput(self.intro)
Typing intro displays the same banner that the application shows at startup.
Shortcuts
cmd2 has several capabilities to simplify repetitive user input:
Shortcuts, Aliases, and Macros. Let's add a shortcut to
our application. Shortcuts are character strings that can be used instead of a command name. For
example, cmd2 has support for a shortcut ! which runs the shell command. So instead of typing
this:
you can type this:
The example adds & as a shortcut for the intro command:
The shortcuts dictionary is then passed to the cmd2.Cmd initializer with the rest of the
application configuration. Starting with cmd2.DEFAULT_SHORTCUTS retains the built-in shortcuts;
calling .update() adds the new shortcut or overrides an existing one with the same key.
Use the built-in shortcuts command to list them, or type & to invoke intro:
History
cmd2 tracks the history of the commands that users enter. As a developer, you don't need to do
anything to enable this functionality, you get it for free. If you want the history of commands to
persist between invocations of your application, you'll need to do a little work. The
History page has all the details.
Users can access command history using two methods:
- The prompt-toolkit library which provides a pure Python replacement for the GNU readline library which is fully cross-platform compatible
- The
historycommand which is built-in tocmd2
From the prompt in a cmd2-based application, you can press Control-p to move to the previously
entered command, and Control-n to move to the next command. You can also search through the
command history using Control-r.
By default, prompt-toolkit provides Emacs-style key bindings which will be familiar to users of
the GNU Readline library. You can refer to the
readline cheat sheet or you can dig into the
Prompt Toolkit User Manual
for all the details, including instructions for customizing the key bindings.
The history command allows a user to view the command history, and select commands from history by
number, range, string search, or regular expression. With the selected commands, users can:
- Re-run the commands
- Edit the selected commands in a text editor, and run them after the text editor exits
- Save the commands to a file
- Run the commands, saving both the commands and their output to a file
Learn more about the history command by typing history -h at any cmd2 input prompt, or by
exploring Command History For Users.
Conclusion
You've just created a simple, but functional command line application. With minimal work on your
part, the application leverages many robust features of cmd2. To learn more you can:
- Dive into all of the Features that
cmd2provides - Look at more Examples
- Browse the API Reference