Python’s cmd module provides a framework for building line-oriented interactive command interpreters. import cmd. Cmd: subclass cmd.Cmd; define do_NAME(self, arg) methods for commands; help_NAME docstrings or methods for help. cmdloop: self.cmdloop(intro="Welcome") — starts the REPL; reads lines, splits on first space, dispatches to do_NAME(arg); exits on EOF or do_NAME returning True. onecmd: self.onecmd("cmd arg1 arg2") — dispatch one line programmatically. default: def default(self, line) — called for unknown commands (default prints *** Unknown syntax). emptyline: def emptyline(self) — called on empty input (default re-runs last command; override to do nothing). precmd/postcmd: hooks before/after every command. completedefault: tab completion fallback; complete_NAME(text, line, begidx, endidx) for per-command completion. prompt: self.prompt = "myapp> ". identchars: characters valid in command names. ruler: "=" drawn in help headers. doc_header/undoc_header. self.stop = True exits cmdloop. help command built-in: prints do_NAME.__doc__. subclasses can chain with cmd.Cmd.__init__(self, stdin=..., stdout=...). Claude Code generates multi-command admin shells, debug REPLs, interactive data explorers, and scriptable CLI interpreters.
CLAUDE.md for cmd
## cmd Stack
- Stdlib: import cmd, shlex
- Class: class MyShell(cmd.Cmd): prompt = "app> "
- Command: def do_list(self, arg): ... # "list" command
- Help: def do_list(self, arg): """List all items."""
- Complete: def complete_list(self, text, line, begidx, endidx): return matches
- Exit: def do_quit(self, arg): return True # True stops cmdloop
- Run: MyShell().cmdloop()
cmd Shell Pipeline
# app/cmdshell.py — base shell, history, argparse commands, subcommands, testing
from __future__ import annotations
import cmd
import shlex
import sys
from dataclasses import dataclass, field
from io import StringIO
from typing import Any
# ─────────────────────────────────────────────────────────────────────────────
# 1. Base shell with common features
# ─────────────────────────────────────────────────────────────────────────────
class BaseShell(cmd.Cmd):
"""
cmd.Cmd subclass with sensible defaults:
- emptyline() is a no-op (not re-run last command)
- EOF exits cleanly
- 'quit' / 'exit' / 'q' all exit
- error() helper for consistent error output
- history stored in self._history
Example:
class MyApp(BaseShell):
prompt = "myapp> "
def do_greet(self, arg):
"""Greet someone. Usage: greet <name>"""
print(f"Hello, {arg or 'world'}!")
MyApp().cmdloop()
"""
prompt = "shell> "
intro = None
doc_header = "Commands (type help <command>):"
undoc_header = "Undocumented commands:"
ruler = "-"
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._history: list[str] = []
def emptyline(self) -> None:
"""Do nothing on empty line (override cmd.Cmd default of re-running last command)."""
def default(self, line: str) -> bool | None:
self.error(f"Unknown command: {line.split()[0]!r} (type 'help' for commands)")
return None
def error(self, msg: str) -> None:
print(f"Error: {msg}", file=self.stdout)
def precmd(self, line: str) -> str:
if line.strip():
self._history.append(line.strip())
return line
def do_quit(self, arg: str) -> bool:
"""Exit the shell."""
return True
do_exit = do_quit
do_q = do_quit
def do_EOF(self, arg: str) -> bool:
"""Exit on Ctrl-D."""
print()
return True
def do_history(self, arg: str) -> None:
"""Show command history."""
if not self._history:
print(" (no history)")
for i, cmd_line in enumerate(self._history, 1):
print(f" {i:3d} {cmd_line}")
def do_clear(self, arg: str) -> None:
"""Clear the terminal screen."""
import os
os.system("cls" if sys.platform == "win32" else "clear")
# ─────────────────────────────────────────────────────────────────────────────
# 2. Argument-parsing helper
# ─────────────────────────────────────────────────────────────────────────────
def parse_args(arg: str) -> list[str]:
"""
Split a readline arg string into tokens using POSIX shell rules.
Handles quoted strings: parse_args('one "two three" four') → ['one', 'two three', 'four']
Example:
def do_add(self, arg):
tokens = parse_args(arg)
if len(tokens) < 2: return self.error("Usage: add <name> <value>")
name, value = tokens[0], tokens[1]
"""
try:
return shlex.split(arg)
except ValueError:
return arg.split()
# ─────────────────────────────────────────────────────────────────────────────
# 3. Key-value store shell (concrete example)
# ─────────────────────────────────────────────────────────────────────────────
class KVShell(BaseShell):
"""
Interactive shell for a simple in-memory key-value store.
Demonstrates do_*/complete_*/help_ patterns.
Example:
KVShell().cmdloop("Welcome to KV shell. Type 'help' for commands.")
"""
prompt = "kv> "
def __init__(self) -> None:
super().__init__()
self._store: dict[str, str] = {}
# ── commands ──────────────────────────────────────────────────────────────
def do_set(self, arg: str) -> None:
"""Set a key-value pair. Usage: set <key> <value>"""
tokens = parse_args(arg)
if len(tokens) < 2:
return self.error("Usage: set <key> <value>")
key, value = tokens[0], " ".join(tokens[1:])
self._store[key] = value
print(f" {key} = {value!r}")
def do_get(self, arg: str) -> None:
"""Get the value for a key. Usage: get <key>"""
key = arg.strip()
if not key:
return self.error("Usage: get <key>")
val = self._store.get(key)
if val is None:
print(f" (not found: {key!r})")
else:
print(f" {key} = {val!r}")
def do_del(self, arg: str) -> None:
"""Delete a key. Usage: del <key>"""
key = arg.strip()
if key in self._store:
del self._store[key]
print(f" deleted {key!r}")
else:
self.error(f"Not found: {key!r}")
def do_list(self, arg: str) -> None:
"""List all keys (optionally filter by prefix). Usage: list [prefix]"""
prefix = arg.strip()
items = [(k, v) for k, v in sorted(self._store.items())
if k.startswith(prefix)]
if not items:
print(" (empty)")
for k, v in items:
print(f" {k:20s} = {v!r}")
def do_import(self, arg: str) -> None:
"""Load key=value pairs from a file. Usage: import <path>"""
path = arg.strip()
if not path:
return self.error("Usage: import <path>")
try:
with open(path) as f:
for line in f:
line = line.strip()
if "=" in line and not line.startswith("#"):
k, _, v = line.partition("=")
self._store[k.strip()] = v.strip()
print(f" imported from {path!r}")
except OSError as e:
self.error(str(e))
def do_export(self, arg: str) -> None:
"""Write all key=value pairs to a file. Usage: export <path>"""
path = arg.strip()
if not path:
return self.error("Usage: export <path>")
with open(path, "w") as f:
for k, v in sorted(self._store.items()):
f.write(f"{k}={v}\n")
print(f" exported {len(self._store)} keys to {path!r}")
# ── completion ────────────────────────────────────────────────────────────
def complete_get(self, text: str, line: str, begidx: int, endidx: int) -> list[str]:
return [k for k in self._store if k.startswith(text)]
def complete_del(self, text: str, line: str, begidx: int, endidx: int) -> list[str]:
return [k for k in self._store if k.startswith(text)]
def complete_set(self, text: str, line: str, begidx: int, endidx: int) -> list[str]:
tokens = parse_args(line)
# Only complete the key (first token after command)
if len(tokens) <= 1 or (len(tokens) == 2 and not line.endswith(" ")):
return [k for k in self._store if k.startswith(text)]
return []
# ─────────────────────────────────────────────────────────────────────────────
# 4. Scriptable command runner (non-interactive)
# ─────────────────────────────────────────────────────────────────────────────
def run_script(
shell_cls: type,
commands: list[str],
**shell_kwargs,
) -> str:
"""
Run a list of commands through a cmd.Cmd subclass non-interactively.
Returns all output as a string.
Example:
output = run_script(KVShell, [
"set name Alice",
"set score 99",
"list",
"get name",
])
print(output)
"""
stdin = StringIO("\n".join(commands) + "\nEOF\n")
stdout = StringIO()
shell = shell_cls(stdin=stdin, stdout=stdout, **shell_kwargs)
shell.use_rawinput = False # use stdin.readline() instead of input()
shell.cmdloop()
return stdout.getvalue()
# ─────────────────────────────────────────────────────────────────────────────
# 5. Subcommand dispatcher mixin
# ─────────────────────────────────────────────────────────────────────────────
class SubcommandMixin:
"""
Mixin to add 'cmd subcommand args' dispatch inside a do_cmd method.
Subclass and call dispatch_subcommand(name, arg, subs) from a do_* method.
Example:
class MyShell(SubcommandMixin, BaseShell):
def do_user(self, arg):
self.dispatch_subcommand("user", arg, {
"add": self._user_add,
"remove": self._user_remove,
"list": self._user_list,
})
"""
def dispatch_subcommand(
self,
cmd_name: str,
arg: str,
subs: dict[str, Any],
) -> None:
tokens = parse_args(arg)
if not tokens:
print(f" Subcommands: {', '.join(sorted(subs))}")
return
sub = tokens[0]
rest = " ".join(tokens[1:])
fn = subs.get(sub)
if fn is None:
print(f" Unknown subcommand: {sub!r}", file=getattr(self, "stdout", sys.stdout))
print(f" Available: {', '.join(sorted(subs))}", file=getattr(self, "stdout", sys.stdout))
else:
fn(rest)
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== cmd demo (non-interactive) ===")
# ── run KVShell via script ─────────────────────────────────────────────────
print("\n--- KVShell scripted run ---")
output = run_script(KVShell, [
"set name Alice",
"set score 99",
"set tag admin",
"list",
"get name",
"del tag",
"list",
"history",
])
for line in output.splitlines():
print(" ", line)
# ── completion smoke test ──────────────────────────────────────────────────
print("\n--- completion ---")
shell = KVShell()
# Pre-populate store
shell._store = {"alpha": "1", "beta": "2", "gamma": "3"}
matches = shell.complete_get("al", "get al", 4, 6)
print(f" complete_get('al') → {matches}")
# ── parse_args ────────────────────────────────────────────────────────────
print("\n--- parse_args ---")
cases = [
'simple arg',
'"quoted value" arg2',
'key "value with spaces"',
]
for c in cases:
print(f" {c!r} → {parse_args(c)}")
print("\n=== done ===")
For the readline alternative — readline (stdlib) provides the lower-level line-editing and Tab-completion primitives that cmd.Cmd uses internally; you get direct control over set_completer, parse_and_bind, and history file management — use readline directly when building a prompt loop with input() that needs custom history or completion but doesn’t fit the do_* method dispatch model; use cmd.Cmd when your CLI has a structured set of named commands with built-in help wiring and per-command Tab completion via complete_* methods. For the click / typer alternative — click (PyPI) and typer (PyPI) build single-invocation CLI tools (git-style app command --flag) rather than interactive REPLs; they provide argument parsing, type checking, --help generation, and shell completion scripts automatically — use click/typer for non-interactive command-line tools that are invoked once per call; use cmd.Cmd for interactive multi-command shells that users run in a loop (db> set key val, db> list, db> quit). The Claude Skills 360 bundle includes cmd skill sets covering BaseShell with history/error/quit/EOF built-ins, KVShell concrete example with set/get/del/list/import/export commands and complete_* hooks, parse_args() shlex helper, run_script() non-interactive test runner, and SubcommandMixin for cmd sub args dispatch patterns. Start with the free tier to try interactive shell patterns and cmd pipeline code generation.