diff --git a/CHANGELOG.md b/CHANGELOG.md
index c6fbd466..31daa079 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,4 @@
-## 1.0.3 (TBD, 2020)
+## 1.1.0 (TBD, 2020)
 * Bug Fixes
     * Fixed issue where subcommand usage text could contain a subcommand alias instead of the actual name
 * Enhancements
@@ -14,6 +14,15 @@
           documentation for an overview.
         * See [table_creation.py](https://github.com/python-cmd2/cmd2/blob/master/examples/table_creation.py)
           for an example.
+    * Added the following exceptions to the public API
+        * `SkipPostcommandHooks` - Custom exception class for when a command has a failure bad enough to skip
+          post command hooks, but not bad enough to print the exception to the user.
+        * `Cmd2ArgparseError` - A `SkipPostcommandHooks` exception for when a command fails to parse its arguments.
+          Normally argparse raises a `SystemExit` exception in these cases. To avoid stopping the command
+          loop, catch the `SystemExit` and raise this instead. If you still need to run post command hooks
+          after parsing fails, just return instead of raising an exception.
+    * Added explicit handling of `SystemExit`. If a command raises this exception, the command loop will be
+      gracefully stopped.
     
 ## 1.0.2 (April 06, 2020)
 * Bug Fixes
diff --git a/cmd2/__init__.py b/cmd2/__init__.py
index eb5c275d..d49427f2 100644
--- a/cmd2/__init__.py
+++ b/cmd2/__init__.py
@@ -25,6 +25,7 @@ from .argparse_custom import DEFAULT_ARGUMENT_PARSER
 from .cmd2 import Cmd
 from .constants import COMMAND_NAME, DEFAULT_SHORTCUTS
 from .decorators import with_argument_list, with_argparser, with_argparser_and_unknown_args, with_category
+from .exceptions import Cmd2ArgparseError, SkipPostcommandHooks
 from .parsing import Statement
 from .py_bridge import CommandResult
 from .utils import categorize, CompletionError, Settable
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 49c181f1..73f7aa79 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -46,7 +46,7 @@ from . import ansi, constants, plugin, utils
 from .argparse_custom import DEFAULT_ARGUMENT_PARSER, CompletionItem
 from .clipboard import can_clip, get_paste_buffer, write_to_paste_buffer
 from .decorators import with_argparser
-from .exceptions import Cmd2ArgparseError, Cmd2ShlexError, EmbeddedConsoleExit, EmptyStatement, RedirectionError
+from .exceptions import Cmd2ShlexError, EmbeddedConsoleExit, EmptyStatement, RedirectionError, SkipPostcommandHooks
 from .history import History, HistoryItem
 from .parsing import Macro, MacroArg, Statement, StatementParser, shlex_split
 from .rl_utils import RlType, rl_get_point, rl_make_safe_prompt, rl_set_prompt, rl_type, rl_warning, vt100_support
@@ -1587,9 +1587,9 @@ class Cmd(cmd.Cmd):
 
         :param line: command line to run
         :param add_to_history: If True, then add this command to history. Defaults to True.
-        :param raise_keyboard_interrupt: if True, then KeyboardInterrupt exceptions will be raised. This is used when
-                                         running commands in a loop to be able to stop the whole loop and not just
-                                         the current command. Defaults to False.
+        :param raise_keyboard_interrupt: if True, then KeyboardInterrupt exceptions will be raised if stop isn't already
+                                         True. This is used when running commands in a loop to be able to stop the whole
+                                         loop and not just the current command. Defaults to False.
         :param py_bridge_call: This should only ever be set to True by PyBridge to signify the beginning
                                of an app() call from Python. It is used to enable/disable the storage of the
                                command's stdout.
@@ -1667,26 +1667,35 @@ class Cmd(cmd.Cmd):
                     if py_bridge_call:
                         # Stop saving command's stdout before command finalization hooks run
                         self.stdout.pause_storage = True
-        except KeyboardInterrupt as ex:
-            if raise_keyboard_interrupt:
-                raise ex
-        except (Cmd2ArgparseError, EmptyStatement):
+        except (SkipPostcommandHooks, EmptyStatement):
             # Don't do anything, but do allow command finalization hooks to run
             pass
         except Cmd2ShlexError as ex:
             self.perror("Invalid syntax: {}".format(ex))
         except RedirectionError as ex:
             self.perror(ex)
+        except KeyboardInterrupt as ex:
+            if raise_keyboard_interrupt and not stop:
+                raise ex
+        except SystemExit:
+            stop = True
         except Exception as ex:
             self.pexcept(ex)
         finally:
-            stop = self._run_cmdfinalization_hooks(stop, statement)
+            try:
+                stop = self._run_cmdfinalization_hooks(stop, statement)
+            except KeyboardInterrupt as ex:
+                if raise_keyboard_interrupt and not stop:
+                    raise ex
+            except SystemExit:
+                stop = True
+            except Exception as ex:
+                self.pexcept(ex)
 
         return stop
 
     def _run_cmdfinalization_hooks(self, stop: bool, statement: Optional[Statement]) -> bool:
         """Run the command finalization hooks"""
-
         with self.sigint_protection:
             if not sys.platform.startswith('win') and self.stdin.isatty():
                 # Before the next command runs, fix any terminal problems like those
@@ -1695,15 +1704,12 @@ class Cmd(cmd.Cmd):
                 proc = subprocess.Popen(['stty', 'sane'])
                 proc.communicate()
 
-        try:
-            data = plugin.CommandFinalizationData(stop, statement)
-            for func in self._cmdfinalization_hooks:
-                data = func(data)
-            # retrieve the final value of stop, ignoring any
-            # modifications to the statement
-            return data.stop
-        except Exception as ex:
-            self.pexcept(ex)
+        data = plugin.CommandFinalizationData(stop, statement)
+        for func in self._cmdfinalization_hooks:
+            data = func(data)
+        # retrieve the final value of stop, ignoring any
+        # modifications to the statement
+        return data.stop
 
     def runcmds_plus_hooks(self, cmds: List[Union[HistoryItem, str]], *, add_to_history: bool = True,
                            stop_on_keyboard_interrupt: bool = True) -> bool:
@@ -3894,7 +3900,7 @@ class Cmd(cmd.Cmd):
 
         IMPORTANT: This function will not print an alert unless it can acquire self.terminal_lock to ensure
                    a prompt is onscreen.  Therefore it is best to acquire the lock before calling this function
-                   to guarantee the alert prints.
+                   to guarantee the alert prints and to avoid raising a RuntimeError.
 
         :param alert_msg: the message to display to the user
         :param new_prompt: if you also want to change the prompt that is displayed, then include it here
@@ -3956,7 +3962,7 @@ class Cmd(cmd.Cmd):
 
         IMPORTANT: This function will not update the prompt unless it can acquire self.terminal_lock to ensure
                    a prompt is onscreen.  Therefore it is best to acquire the lock before calling this function
-                   to guarantee the prompt changes.
+                   to guarantee the prompt changes and to avoid raising a RuntimeError.
 
                    If user is at a continuation prompt while entering a multiline command, the onscreen prompt will
                    not change. However self.prompt will still be updated and display immediately after the multiline
@@ -3971,9 +3977,9 @@ class Cmd(cmd.Cmd):
 
         Raises a `RuntimeError` if called while another thread holds `terminal_lock`.
 
-        IMPORTANT: This function will not set the title unless it can acquire self.terminal_lock to avoid
-                   writing to stderr while a command is running. Therefore it is best to acquire the lock
-                   before calling this function to guarantee the title changes.
+        IMPORTANT: This function will not set the title unless it can acquire self.terminal_lock to avoid writing
+                   to stderr while a command is running. Therefore it is best to acquire the lock before calling
+                   this function to guarantee the title changes and to avoid raising a RuntimeError.
 
         :param title: the new window title
         """
diff --git a/cmd2/exceptions.py b/cmd2/exceptions.py
index 635192e1..8a7fd81f 100644
--- a/cmd2/exceptions.py
+++ b/cmd2/exceptions.py
@@ -1,16 +1,33 @@
 # coding=utf-8
-"""Custom exceptions for cmd2.  These are NOT part of the public API and are intended for internal use only."""
+"""Custom exceptions for cmd2"""
 
 
-class Cmd2ArgparseError(Exception):
+############################################################################################################
+# The following exceptions are part of the public API
+############################################################################################################
+
+class SkipPostcommandHooks(Exception):
     """
-    Custom exception class for when a command has an error parsing its arguments.
-    This can be raised by argparse decorators or the command functions themselves.
-    The main use of this exception is to tell cmd2 not to run Postcommand hooks.
+    Custom exception class for when a command has a failure bad enough to skip post command
+    hooks, but not bad enough to print the exception to the user.
     """
     pass
 
 
+class Cmd2ArgparseError(SkipPostcommandHooks):
+    """
+    A ``SkipPostcommandHooks`` exception for when a command fails to parse its arguments.
+    Normally argparse raises a SystemExit exception in these cases. To avoid stopping the command
+    loop, catch the SystemExit and raise this instead. If you still need to run post command hooks
+    after parsing fails, just return instead of raising an exception.
+    """
+    pass
+
+
+############################################################################################################
+# The following exceptions are NOT part of the public API and are intended for internal use only.
+############################################################################################################
+
 class Cmd2ShlexError(Exception):
     """Raised when shlex fails to parse a command line string in StatementParser"""
     pass
diff --git a/docs/api/exceptions.rst b/docs/api/exceptions.rst
new file mode 100644
index 00000000..8ef0a61f
--- /dev/null
+++ b/docs/api/exceptions.rst
@@ -0,0 +1,11 @@
+cmd2.exceptions
+===============
+
+Custom cmd2 exceptions
+
+
+.. autoclass:: cmd2.exceptions.SkipPostcommandHooks
+    :members:
+
+.. autoclass:: cmd2.exceptions.Cmd2ArgparseError
+    :members:
diff --git a/docs/api/index.rst b/docs/api/index.rst
index 7b66a684..cc899ba1 100644
--- a/docs/api/index.rst
+++ b/docs/api/index.rst
@@ -19,37 +19,39 @@ This documentation is for ``cmd2`` version |version|.
    :hidden:
 
    cmd
-   decorators
-   parsing
+   ansi
    argparse_completer
    argparse_custom
-   ansi
-   utils
+   constants
+   decorators
+   exceptions
    history
+   parsing
    plugin
    py_bridge
    table_creator
-   constants
+   utils
 
 **Modules**
 
 - :ref:`api/cmd:cmd2.Cmd` - functions and attributes of the main
   class in this library
-- :ref:`api/decorators:cmd2.decorators` - decorators for ``cmd2``
-  commands
-- :ref:`api/parsing:cmd2.parsing` - classes for parsing and storing
-  user input
+- :ref:`api/ansi:cmd2.ansi` - convenience classes and functions for generating
+  ANSI escape sequences to style text in the terminal
 - :ref:`api/argparse_completer:cmd2.argparse_completer` - classes for
   ``argparse``-based tab completion
 - :ref:`api/argparse_custom:cmd2.argparse_custom` - classes and functions
   for extending ``argparse``
-- :ref:`api/ansi:cmd2.ansi` - convenience classes and functions for generating
-  ANSI escape sequences to style text in the terminal
-- :ref:`api/utils:cmd2.utils` - various utility classes and functions
+- :ref:`api/constants:cmd2.constants` - just like it says on the tin
+- :ref:`api/decorators:cmd2.decorators` - decorators for ``cmd2``
+  commands
+- :ref:`api/exceptions:cmd2.exceptions` - custom ``cmd2`` exceptions
 - :ref:`api/history:cmd2.history` - classes for storing the history
   of previously entered commands
+- :ref:`api/parsing:cmd2.parsing` - classes for parsing and storing
+  user input
 - :ref:`api/plugin:cmd2.plugin` - data classes for hook methods
 - :ref:`api/py_bridge:cmd2.py_bridge` - classes for bridging calls from the
   embedded python environment to the host app
 - :ref:`api/table_creator:cmd2.table_creator` - table creation module
-- :ref:`api/constants:cmd2.constants` - just like it says on the tin
+- :ref:`api/utils:cmd2.utils` - various utility classes and functions
diff --git a/docs/features/commands.rst b/docs/features/commands.rst
index 823c3ca6..13a4ac1f 100644
--- a/docs/features/commands.rst
+++ b/docs/features/commands.rst
@@ -129,7 +129,7 @@ it should stop prompting for user input and cleanly exit. ``cmd2`` already
 includes a ``quit`` command, but if you wanted to make another one called
 ``finis`` you could::
 
-    def do_finis(self, line):
+    def do_finish(self, line):
         """Exit the application"""
         return True
 
@@ -186,6 +186,19 @@ catch it and display it for you. The `debug` :ref:`setting
 name and message. If `debug` is `true`, ``cmd2`` will display a traceback, and
 then display the exception name and message.
 
+There are a few exceptions which commands can raise that do not print as
+described above:
+
+- :attr:`cmd2.exceptions.SkipPostcommandHooks` - all postcommand hooks are
+  skipped and no exception prints
+- :attr:`cmd2.exceptions.Cmd2ArgparseError` - behaves like
+  ``SkipPostcommandHooks``
+- ``SystemExit`` - ``stop`` will be set to ``True`` in an attempt to stop the
+  command loop
+- ``KeyboardInterrupt`` - raised if running in a text script and ``stop`` isn't
+  already True to stop the script
+
+All other ``BaseExceptions`` are not caught by ``cmd2`` and will be raised
 
 Disabling or Hiding Commands
 ----------------------------
diff --git a/docs/features/hooks.rst b/docs/features/hooks.rst
index fec8e258..4c615586 100644
--- a/docs/features/hooks.rst
+++ b/docs/features/hooks.rst
@@ -291,6 +291,12 @@ blindly returns ``False``, a prior hook's requst to exit the application will
 not be honored. It's best to return the value you were passed unless you have a
 compelling reason to do otherwise.
 
+To purposefully and silently skip postcommand hooks, commands can raise any of
+of the following exceptions.
+
+- :attr:`cmd2.exceptions.SkipPostcommandHooks`
+- :attr:`cmd2.exceptions.Cmd2ArgparseError`
+
 
 Command Finalization Hooks
 --------------------------

Version 1.0.2 changed the way SystemExit exceptions raised from do_command methods are handled
I have a cmd2 application which does it's own argparsing inside of the `do_command()` methods, i.e. it does not use any of the cmd2 built-in arg parsing functionality. This is done by calling `argparse.parse_args()` as one of the first lines of code inside the `do_command()` method.

In 1.0.1 if a `do_command()` method raises a `SystemExit` exception, as `parse_args()` does if given the '-h' option or if a syntax error occurs, that exception would be swallowed by `cmd2` and the command prompt would be printed again.

In 1.0.2, that exception is not swallowed and the `cmd2` app terminates.

You can replicate this issue by adding a `raise SystemExit` to the `do_speak()` method in `examples/first_app.py`, and running it with the two versions of `cmd2`.

The behavior of other raised exceptions does not appear to have changed between 1.0.1 and 1.0.2. For example if you raise a ValueError, you get:
```
EXCEPTION of type 'ValueError' occurred with message: ''
To enable full traceback, run the following command: 'set debug true'
```
in both versions of `cmd2`.

This change in behavior is caused by the change in the logic on the last two lines of the `onecmd_plus_hooks()` method made between 1.0.1 and 1.0.2.

In the short term, I request that we quickly revert this change in behavior. After that, I suggest we discuss the desired behavior is for `cmd2` when a `SystemExit` exception is raised, and then intentionally implement and document that behavior.
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index 33f75c9e..bc0e0a94 100755
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -466,6 +466,17 @@ def test_in_script(request):
 
     assert "WE ARE IN SCRIPT" in out[-1]
 
+def test_system_exit_in_command(base_app, capsys):
+    """Test raising SystemExit from a command"""
+    import types
+
+    def do_system_exit(self, _):
+        raise SystemExit
+    setattr(base_app, 'do_system_exit', types.MethodType(do_system_exit, base_app))
+
+    stop = base_app.onecmd_plus_hooks('system_exit')
+    assert stop
+
 def test_output_redirection(base_app):
     fd, filename = tempfile.mkstemp(prefix='cmd2_test', suffix='.txt')
     os.close(fd)
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index 132361a6..e49cbbfc 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -222,11 +222,24 @@ class Plugin:
         self.called_cmdfinalization += 1
         raise ValueError
 
+    def cmdfinalization_hook_system_exit(self, data: cmd2.plugin.CommandFinalizationData) -> \
+            cmd2.plugin.CommandFinalizationData:
+        """A command finalization hook which raises a SystemExit"""
+        self.called_cmdfinalization += 1
+        raise SystemExit
+
+    def cmdfinalization_hook_keyboard_interrupt(self, data: cmd2.plugin.CommandFinalizationData) -> \
+            cmd2.plugin.CommandFinalizationData:
+        """A command finalization hook which raises a KeyboardInterrupt"""
+        self.called_cmdfinalization += 1
+        raise KeyboardInterrupt
+
     def cmdfinalization_hook_not_enough_parameters(self) -> plugin.CommandFinalizationData:
         """A command finalization hook with no parameters."""
         pass
 
-    def cmdfinalization_hook_too_many_parameters(self, one: plugin.CommandFinalizationData, two: str) -> plugin.CommandFinalizationData:
+    def cmdfinalization_hook_too_many_parameters(self, one: plugin.CommandFinalizationData, two: str) -> \
+            plugin.CommandFinalizationData:
         """A command finalization hook with too many parameters."""
         return one
 
@@ -256,6 +269,10 @@ class PluggedApp(Plugin, cmd2.Cmd):
         """Repeat back the arguments"""
         self.poutput(statement)
 
+    def do_skip_postcmd_hooks(self, _):
+        self.poutput("In do_skip_postcmd_hooks")
+        raise exceptions.SkipPostcommandHooks
+
     parser = Cmd2ArgumentParser(description="Test parser")
     parser.add_argument("my_arg", help="some help text")
 
@@ -847,6 +864,46 @@ def test_cmdfinalization_hook_exception(capsys):
     assert err
     assert app.called_cmdfinalization == 1
 
+def test_cmdfinalization_hook_system_exit(capsys):
+    app = PluggedApp()
+    app.register_cmdfinalization_hook(app.cmdfinalization_hook_system_exit)
+    stop = app.onecmd_plus_hooks('say hello')
+    assert stop
+    assert app.called_cmdfinalization == 1
+
+def test_cmdfinalization_hook_keyboard_interrupt(capsys):
+    app = PluggedApp()
+    app.register_cmdfinalization_hook(app.cmdfinalization_hook_keyboard_interrupt)
+
+    # First make sure KeyboardInterrupt isn't raised unless told to
+    stop = app.onecmd_plus_hooks('say hello', raise_keyboard_interrupt=False)
+    assert not stop
+    assert app.called_cmdfinalization == 1
+
+    # Now enable raising the KeyboardInterrupt
+    app.reset_counters()
+    with pytest.raises(KeyboardInterrupt):
+        stop = app.onecmd_plus_hooks('say hello', raise_keyboard_interrupt=True)
+    assert not stop
+    assert app.called_cmdfinalization == 1
+
+    # Now make sure KeyboardInterrupt isn't raised if stop is already True
+    app.reset_counters()
+    stop = app.onecmd_plus_hooks('quit', raise_keyboard_interrupt=True)
+    assert stop
+    assert app.called_cmdfinalization == 1
+
+def test_skip_postcmd_hooks(capsys):
+    app = PluggedApp()
+    app.register_postcmd_hook(app.postcmd_hook)
+    app.register_cmdfinalization_hook(app.cmdfinalization_hook)
+
+    # Cause a SkipPostcommandHooks exception and verify no postcmd stuff runs but cmdfinalization_hook still does
+    app.onecmd_plus_hooks('skip_postcmd_hooks')
+    out, err = capsys.readouterr()
+    assert "In do_skip_postcmd_hooks" in out
+    assert app.called_postcmd == 0
+    assert app.called_cmdfinalization == 1
 
 def test_cmd2_argparse_exception(capsys):
     """

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-mock
apt-get update apt-get install -y gcc
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.16
anyio==4.9.0
attrs==25.3.0
babel==2.17.0
backports.tarfile==1.2.0
cachetools==5.5.2
certifi==2025.1.31
cffi==1.17.1
chardet==5.2.0
charset-normalizer==3.4.1
click==8.1.8
-e git+https://github.com/python-cmd2/cmd2.git@d4653e6fccf0bc15d04075110769c11befb22819#egg=cmd2
codecov==2.1.13
colorama==0.4.6
coverage==7.8.0
cryptography==44.0.2
distlib==0.3.9
doc8==1.1.2
docutils==0.21.2
exceptiongroup==1.2.2
filelock==3.18.0
flake8==7.2.0
h11==0.14.0
id==1.5.0
idna==3.10
imagesize==1.4.1
importlib_metadata==8.6.1
iniconfig==2.1.0
invoke==2.2.0
jaraco.classes==3.4.0
jaraco.context==6.0.1
jaraco.functools==4.1.0
jeepney==0.9.0
Jinja2==3.1.6
keyring==25.6.0
markdown-it-py==3.0.0
MarkupSafe==3.0.2
mccabe==0.7.0
mdurl==0.1.2
more-itertools==10.6.0
nh3==0.2.21
packaging==24.2
pbr==6.1.1
platformdirs==4.3.7
pluggy==1.5.0
pycodestyle==2.13.0
pycparser==2.22
pyflakes==3.3.1
Pygments==2.19.1
pyperclip==1.9.0
pyproject-api==1.9.0
pytest==8.3.5
pytest-cov==6.0.0
pytest-mock==3.14.0
readme_renderer==44.0
requests==2.32.3
requests-toolbelt==1.0.0
restructuredtext_lint==1.4.0
rfc3986==2.0.0
rich==14.0.0
SecretStorage==3.3.3
sniffio==1.3.1
snowballstemmer==2.2.0
Sphinx==7.4.7
sphinx-autobuild==2024.10.3
sphinx-rtd-theme==3.0.2
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
starlette==0.46.1
stevedore==5.4.1
tomli==2.2.1
tox==4.25.0
twine==6.1.0
typing_extensions==4.13.0
urllib3==2.3.0
uvicorn==0.34.0
virtualenv==20.29.3
watchfiles==1.0.4
wcwidth==0.2.13
websockets==15.0.1
zipp==3.21.0

name: cmd2
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.16
      - anyio==4.9.0
      - attrs==25.3.0
      - babel==2.17.0
      - backports-tarfile==1.2.0
      - cachetools==5.5.2
      - certifi==2025.1.31
      - cffi==1.17.1
      - chardet==5.2.0
      - charset-normalizer==3.4.1
      - click==8.1.8
      - codecov==2.1.13
      - colorama==0.4.6
      - coverage==7.8.0
      - cryptography==44.0.2
      - distlib==0.3.9
      - doc8==1.1.2
      - docutils==0.21.2
      - exceptiongroup==1.2.2
      - filelock==3.18.0
      - flake8==7.2.0
      - h11==0.14.0
      - id==1.5.0
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - invoke==2.2.0
      - jaraco-classes==3.4.0
      - jaraco-context==6.0.1
      - jaraco-functools==4.1.0
      - jeepney==0.9.0
      - jinja2==3.1.6
      - keyring==25.6.0
      - markdown-it-py==3.0.0
      - markupsafe==3.0.2
      - mccabe==0.7.0
      - mdurl==0.1.2
      - more-itertools==10.6.0
      - nh3==0.2.21
      - packaging==24.2
      - pbr==6.1.1
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - pycodestyle==2.13.0
      - pycparser==2.22
      - pyflakes==3.3.1
      - pygments==2.19.1
      - pyperclip==1.9.0
      - pyproject-api==1.9.0
      - pytest==8.3.5
      - pytest-cov==6.0.0
      - pytest-mock==3.14.0
      - readme-renderer==44.0
      - requests==2.32.3
      - requests-toolbelt==1.0.0
      - restructuredtext-lint==1.4.0
      - rfc3986==2.0.0
      - rich==14.0.0
      - secretstorage==3.3.3
      - sniffio==1.3.1
      - snowballstemmer==2.2.0
      - sphinx==7.4.7
      - sphinx-autobuild==2024.10.3
      - sphinx-rtd-theme==3.0.2
      - sphinxcontrib-applehelp==2.0.0
      - sphinxcontrib-devhelp==2.0.0
      - sphinxcontrib-htmlhelp==2.1.0
      - sphinxcontrib-jquery==4.1
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==2.0.0
      - sphinxcontrib-serializinghtml==2.0.0
      - starlette==0.46.1
      - stevedore==5.4.1
      - tomli==2.2.1
      - tox==4.25.0
      - twine==6.1.0
      - typing-extensions==4.13.0
      - urllib3==2.3.0
      - uvicorn==0.34.0
      - virtualenv==20.29.3
      - watchfiles==1.0.4
      - wcwidth==0.2.13
      - websockets==15.0.1
      - zipp==3.21.0
prefix: /opt/conda/envs/cmd2

tests/test_cmd2.py::test_system_exit_in_command tests/test_plugin.py::test_cmdfinalization_hook_system_exit tests/test_plugin.py::test_cmdfinalization_hook_keyboard_interrupt tests/test_plugin.py::test_skip_postcmd_hooks tests/test_plugin.py::test_cmd2_argparse_exception
tests/test_cmd2.py::test_not_in_main_thread tests/test_cmd2.py::test_which_editor_good
tests/test_cmd2.py::test_version tests/test_cmd2.py::test_empty_statement tests/test_cmd2.py::test_base_help tests/test_cmd2.py::test_base_help_verbose tests/test_cmd2.py::test_base_argparse_help tests/test_cmd2.py::test_base_invalid_option tests/test_cmd2.py::test_base_shortcuts tests/test_cmd2.py::test_command_starts_with_shortcut tests/test_cmd2.py::test_base_show tests/test_cmd2.py::test_base_show_long tests/test_cmd2.py::test_set tests/test_cmd2.py::test_set_val_empty tests/test_cmd2.py::test_set_val_is_flag tests/test_cmd2.py::test_set_not_supported tests/test_cmd2.py::test_set_no_settables tests/test_cmd2.py::test_set_allow_style[Never-True-Never] tests/test_cmd2.py::test_set_allow_style[neVeR-True-Never] tests/test_cmd2.py::test_set_allow_style[Terminal-True-Terminal] tests/test_cmd2.py::test_set_allow_style[TeRMInal-True-Terminal] tests/test_cmd2.py::test_set_allow_style[Always-True-Always] tests/test_cmd2.py::test_set_allow_style[AlWaYs-True-Always] tests/test_cmd2.py::test_set_allow_style[invalid-False-Terminal] tests/test_cmd2.py::test_set_onchange_hook tests/test_cmd2.py::test_base_shell tests/test_cmd2.py::test_shell_last_result tests/test_cmd2.py::test_base_py tests/test_cmd2.py::test_base_error tests/test_cmd2.py::test_run_script tests/test_cmd2.py::test_run_script_with_empty_args tests/test_cmd2.py::test_run_script_with_nonexistent_file tests/test_cmd2.py::test_run_script_with_directory tests/test_cmd2.py::test_run_script_with_empty_file tests/test_cmd2.py::test_run_script_with_binary_file tests/test_cmd2.py::test_run_script_with_python_file tests/test_cmd2.py::test_run_script_with_utf8_file tests/test_cmd2.py::test_run_script_nested_run_scripts tests/test_cmd2.py::test_runcmds_plus_hooks tests/test_cmd2.py::test_runcmds_plus_hooks_ctrl_c tests/test_cmd2.py::test_relative_run_script tests/test_cmd2.py::test_relative_run_script_with_odd_file_names[nothingweird] tests/test_cmd2.py::test_relative_run_script_with_odd_file_names[has tests/test_cmd2.py::test_relative_run_script_with_odd_file_names["is_double_quoted"] tests/test_cmd2.py::test_relative_run_script_with_odd_file_names['is_single_quoted'] tests/test_cmd2.py::test_relative_run_script_requires_an_argument tests/test_cmd2.py::test_in_script tests/test_cmd2.py::test_output_redirection tests/test_cmd2.py::test_output_redirection_to_nonexistent_directory tests/test_cmd2.py::test_output_redirection_to_too_long_filename tests/test_cmd2.py::test_feedback_to_output_true tests/test_cmd2.py::test_feedback_to_output_false tests/test_cmd2.py::test_disallow_redirection tests/test_cmd2.py::test_pipe_to_shell tests/test_cmd2.py::test_pipe_to_shell_and_redirect tests/test_cmd2.py::test_pipe_to_shell_error tests/test_cmd2.py::test_base_timing tests/test_cmd2.py::test_base_debug tests/test_cmd2.py::test_debug_not_settable tests/test_cmd2.py::test_remove_settable_keyerror tests/test_cmd2.py::test_edit_file tests/test_cmd2.py::test_edit_file_with_odd_file_names[nothingweird] tests/test_cmd2.py::test_edit_file_with_odd_file_names[has tests/test_cmd2.py::test_edit_file_with_odd_file_names["is_double_quoted"] tests/test_cmd2.py::test_edit_file_with_odd_file_names['is_single_quoted'] tests/test_cmd2.py::test_edit_file_with_spaces tests/test_cmd2.py::test_edit_blank tests/test_cmd2.py::test_base_py_interactive tests/test_cmd2.py::test_base_cmdloop_with_startup_commands tests/test_cmd2.py::test_base_cmdloop_without_startup_commands tests/test_cmd2.py::test_cmdloop_without_rawinput tests/test_cmd2.py::test_stty_sane tests/test_cmd2.py::test_precmd_hook_success tests/test_cmd2.py::test_precmd_hook_failure tests/test_cmd2.py::test_interrupt_quit tests/test_cmd2.py::test_interrupt_noquit tests/test_cmd2.py::test_default_to_shell tests/test_cmd2.py::test_ansi_prompt_not_esacped tests/test_cmd2.py::test_ansi_prompt_escaped tests/test_cmd2.py::test_custom_command_help tests/test_cmd2.py::test_custom_help_menu tests/test_cmd2.py::test_help_undocumented tests/test_cmd2.py::test_help_overridden_method tests/test_cmd2.py::test_help_cat_base tests/test_cmd2.py::test_help_cat_verbose tests/test_cmd2.py::test_select_options tests/test_cmd2.py::test_select_invalid_option_too_big tests/test_cmd2.py::test_select_invalid_option_too_small tests/test_cmd2.py::test_select_list_of_strings tests/test_cmd2.py::test_select_list_of_tuples tests/test_cmd2.py::test_select_uneven_list_of_tuples tests/test_cmd2.py::test_select_eof tests/test_cmd2.py::test_select_ctrl_c tests/test_cmd2.py::test_help_with_no_docstring tests/test_cmd2.py::test_which_editor_bad tests/test_cmd2.py::test_multiline_complete_empty_statement_raises_exception tests/test_cmd2.py::test_multiline_complete_statement_without_terminator tests/test_cmd2.py::test_multiline_complete_statement_with_unclosed_quotes tests/test_cmd2.py::test_multiline_input_line_to_statement tests/test_cmd2.py::test_clipboard_failure tests/test_cmd2.py::test_commandresult_truthy tests/test_cmd2.py::test_commandresult_falsy tests/test_cmd2.py::test_is_text_file_bad_input tests/test_cmd2.py::test_eof tests/test_cmd2.py::test_echo tests/test_cmd2.py::test_read_input_rawinput_true tests/test_cmd2.py::test_read_input_rawinput_false tests/test_cmd2.py::test_read_command_line_eof tests/test_cmd2.py::test_poutput_string tests/test_cmd2.py::test_poutput_zero tests/test_cmd2.py::test_poutput_empty_string tests/test_cmd2.py::test_poutput_none tests/test_cmd2.py::test_poutput_ansi_always tests/test_cmd2.py::test_poutput_ansi_never tests/test_cmd2.py::test_get_alias_completion_items tests/test_cmd2.py::test_get_macro_completion_items tests/test_cmd2.py::test_get_settable_completion_items tests/test_cmd2.py::test_alias_no_subcommand tests/test_cmd2.py::test_alias_create tests/test_cmd2.py::test_alias_create_with_quoted_value tests/test_cmd2.py::test_alias_create_invalid_name[""] tests/test_cmd2.py::test_alias_create_invalid_name[#] tests/test_cmd2.py::test_alias_create_invalid_name[!no_shortcut] tests/test_cmd2.py::test_alias_create_invalid_name[">"] tests/test_cmd2.py::test_alias_create_invalid_name["no>pe"] tests/test_cmd2.py::test_alias_create_invalid_name["no tests/test_cmd2.py::test_alias_create_invalid_name["nopipe|"] tests/test_cmd2.py::test_alias_create_invalid_name["noterm;"] tests/test_cmd2.py::test_alias_create_invalid_name[noembedded"quotes] tests/test_cmd2.py::test_alias_create_with_command_name tests/test_cmd2.py::test_alias_create_with_macro_name tests/test_cmd2.py::test_alias_that_resolves_into_comment tests/test_cmd2.py::test_alias_list_invalid_alias tests/test_cmd2.py::test_alias_delete tests/test_cmd2.py::test_alias_delete_all tests/test_cmd2.py::test_alias_delete_non_existing tests/test_cmd2.py::test_alias_delete_no_name tests/test_cmd2.py::test_multiple_aliases tests/test_cmd2.py::test_macro_no_subcommand tests/test_cmd2.py::test_macro_create tests/test_cmd2.py::test_macro_create_with_quoted_value tests/test_cmd2.py::test_macro_create_invalid_name[""] tests/test_cmd2.py::test_macro_create_invalid_name[#] tests/test_cmd2.py::test_macro_create_invalid_name[!no_shortcut] tests/test_cmd2.py::test_macro_create_invalid_name[">"] tests/test_cmd2.py::test_macro_create_invalid_name["no>pe"] tests/test_cmd2.py::test_macro_create_invalid_name["no tests/test_cmd2.py::test_macro_create_invalid_name["nopipe|"] tests/test_cmd2.py::test_macro_create_invalid_name["noterm;"] tests/test_cmd2.py::test_macro_create_invalid_name[noembedded"quotes] tests/test_cmd2.py::test_macro_create_with_command_name tests/test_cmd2.py::test_macro_create_with_alias_name tests/test_cmd2.py::test_macro_create_with_args tests/test_cmd2.py::test_macro_create_with_escaped_args tests/test_cmd2.py::test_macro_usage_with_missing_args tests/test_cmd2.py::test_macro_usage_with_exta_args tests/test_cmd2.py::test_macro_create_with_missing_arg_nums tests/test_cmd2.py::test_macro_create_with_invalid_arg_num tests/test_cmd2.py::test_macro_create_with_unicode_numbered_arg tests/test_cmd2.py::test_macro_create_with_missing_unicode_arg_nums tests/test_cmd2.py::test_macro_that_resolves_into_comment tests/test_cmd2.py::test_macro_list_invalid_macro tests/test_cmd2.py::test_macro_delete tests/test_cmd2.py::test_macro_delete_all tests/test_cmd2.py::test_macro_delete_non_existing tests/test_cmd2.py::test_macro_delete_no_name tests/test_cmd2.py::test_multiple_macros tests/test_cmd2.py::test_nonexistent_macro tests/test_cmd2.py::test_perror_style tests/test_cmd2.py::test_perror_no_style tests/test_cmd2.py::test_pwarning_style tests/test_cmd2.py::test_pwarning_no_style tests/test_cmd2.py::test_ppaged tests/test_cmd2.py::test_ppaged_blank tests/test_cmd2.py::test_ppaged_none tests/test_cmd2.py::test_ppaged_strips_ansi_when_redirecting tests/test_cmd2.py::test_ppaged_strips_ansi_when_redirecting_if_always tests/test_cmd2.py::test_parseline_empty tests/test_cmd2.py::test_parseline tests/test_cmd2.py::test_onecmd_raw_str_continue tests/test_cmd2.py::test_onecmd_raw_str_quit tests/test_cmd2.py::test_onecmd_add_to_history tests/test_cmd2.py::test_get_all_commands tests/test_cmd2.py::test_get_help_topics tests/test_cmd2.py::test_get_help_topics_hidden tests/test_cmd2.py::test_exit_code_default tests/test_cmd2.py::test_exit_code_nonzero tests/test_cmd2.py::test_ansi_pouterr_always_tty tests/test_cmd2.py::test_ansi_pouterr_always_notty tests/test_cmd2.py::test_ansi_terminal_tty tests/test_cmd2.py::test_ansi_terminal_notty tests/test_cmd2.py::test_ansi_never_tty tests/test_cmd2.py::test_ansi_never_notty tests/test_cmd2.py::test_disable_and_enable_category tests/test_cmd2.py::test_enable_enabled_command tests/test_cmd2.py::test_disable_fake_command tests/test_cmd2.py::test_disable_command_twice tests/test_cmd2.py::test_disabled_command_not_in_history tests/test_cmd2.py::test_disabled_message_command_name tests/test_cmd2.py::test_startup_script tests/test_cmd2.py::test_startup_script_with_odd_file_names[nothingweird] tests/test_cmd2.py::test_startup_script_with_odd_file_names[has tests/test_cmd2.py::test_startup_script_with_odd_file_names["is_double_quoted"] tests/test_cmd2.py::test_startup_script_with_odd_file_names['is_single_quoted'] tests/test_cmd2.py::test_transcripts_at_init tests/test_plugin.py::test_register_preloop_hook_too_many_parameters tests/test_plugin.py::test_register_preloop_hook_with_return_annotation tests/test_plugin.py::test_preloop_hook tests/test_plugin.py::test_preloop_hooks tests/test_plugin.py::test_register_postloop_hook_too_many_parameters tests/test_plugin.py::test_register_postloop_hook_with_wrong_return_annotation tests/test_plugin.py::test_postloop_hook tests/test_plugin.py::test_postloop_hooks tests/test_plugin.py::test_preparse tests/test_plugin.py::test_postparsing_hook_too_many_parameters tests/test_plugin.py::test_postparsing_hook_undeclared_parameter_annotation tests/test_plugin.py::test_postparsing_hook_wrong_parameter_annotation tests/test_plugin.py::test_postparsing_hook_undeclared_return_annotation tests/test_plugin.py::test_postparsing_hook_wrong_return_annotation tests/test_plugin.py::test_postparsing_hook tests/test_plugin.py::test_postparsing_hook_stop_first tests/test_plugin.py::test_postparsing_hook_stop_second tests/test_plugin.py::test_postparsing_hook_emptystatement_first tests/test_plugin.py::test_postparsing_hook_emptystatement_second tests/test_plugin.py::test_postparsing_hook_exception tests/test_plugin.py::test_register_precmd_hook_parameter_count tests/test_plugin.py::test_register_precmd_hook_no_parameter_annotation tests/test_plugin.py::test_register_precmd_hook_wrong_parameter_annotation tests/test_plugin.py::test_register_precmd_hook_no_return_annotation tests/test_plugin.py::test_register_precmd_hook_wrong_return_annotation tests/test_plugin.py::test_precmd_hook tests/test_plugin.py::test_precmd_hook_emptystatement_first tests/test_plugin.py::test_precmd_hook_emptystatement_second tests/test_plugin.py::test_register_postcmd_hook_parameter_count tests/test_plugin.py::test_register_postcmd_hook_no_parameter_annotation tests/test_plugin.py::test_register_postcmd_hook_wrong_parameter_annotation tests/test_plugin.py::test_register_postcmd_hook_no_return_annotation tests/test_plugin.py::test_register_postcmd_hook_wrong_return_annotation tests/test_plugin.py::test_postcmd tests/test_plugin.py::test_postcmd_exception_first tests/test_plugin.py::test_postcmd_exception_second tests/test_plugin.py::test_register_cmdfinalization_hook_parameter_count tests/test_plugin.py::test_register_cmdfinalization_hook_no_parameter_annotation tests/test_plugin.py::test_register_cmdfinalization_hook_wrong_parameter_annotation tests/test_plugin.py::test_register_cmdfinalization_hook_no_return_annotation tests/test_plugin.py::test_register_cmdfinalization_hook_wrong_return_annotation tests/test_plugin.py::test_cmdfinalization tests/test_plugin.py::test_cmdfinalization_stop_first tests/test_plugin.py::test_cmdfinalization_stop_second tests/test_plugin.py::test_cmdfinalization_hook_exception
cmd2/cmd2.py docs/features/hooks.rst docs/api/exceptions.rst CHANGELOG.md cmd2/__init__.py docs/api/index.rst docs/features/commands.rst cmd2/exceptions.py
cmd2/cmd2.py docs/features/hooks.rst docs/api/exceptions.rst CHANGELOG.md cmd2/__init__.py docs/api/index.rst docs/features/commands.rst cmd2/exceptions.py