https://github.com/medismailben updated 
https://github.com/llvm/llvm-project/pull/210430

>From 445cc9fd587c5f0ae99c55f637a8661ea82a52a3 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <[email protected]>
Date: Fri, 17 Jul 2026 16:38:42 -0700
Subject: [PATCH] [lldb/script] Migrate ParsedCommand & raw commands onto
 ScriptedPythonInterface

Give both `command script add -c` (raw class) and `-P` (ParsedCommand) a
single, shared `ScriptedCommandInterface`/`ScriptedCommandPythonInterface`,
built on the existing `ScriptedPythonInterface` machinery: `CreatePluginObject`
delegates to the base template, and every other method goes through
`Dispatch()`.

Add a lighter `ScriptedCommand` ABC template (`scripted_command.py`) for raw
mode; the existing `ParsedCommand`/`LLDBOptionValueParser` classes are kept
as-is.

Extend `ScriptedPythonInterface` with a few `Transform` overloads
(`DebuggerSP`, `std::vector<StringRef>`, `CommandReturnObject &`) so those
argument types route through `Dispatch` without extra plumbing.

Delete the standalone SWIG bridge functions this plugin was the sole caller
of. `CommandObjectScriptingObjectRaw` and `CommandObjectScriptingObjectParsed`
now hold a `ScriptedCommandInterfaceSP` instead of a raw
`StructuredData::GenericSP`; `command script add`'s `DoExecute` constructs
the interface once via `CreateScriptedCommandInterface()->CreatePluginObject`
for either flag.

Signed-off-by: Med Ismail Bennani <[email protected]>
---
 lldb/bindings/python/CMakeLists.txt           |   1 +
 lldb/bindings/python/python-wrapper.swig      | 184 +------
 lldb/docs/CMakeLists.txt                      |   1 +
 .../python/templates/scripted_command.py      |  86 +++
 lldb/include/lldb/API/SBCommandReturnObject.h |  18 +-
 lldb/include/lldb/API/SBDebugger.h            |   1 +
 .../Interfaces/ScriptedCommandInterface.h     |  83 +++
 .../lldb/Interpreter/ScriptInterpreter.h      |  88 +--
 lldb/include/lldb/lldb-enumerations.h         |   4 +-
 lldb/include/lldb/lldb-forward.h              |   3 +
 lldb/source/API/SBCommandReturnObject.cpp     |  49 +-
 .../source/Commands/CommandObjectCommands.cpp | 199 +++----
 lldb/source/Interpreter/ScriptInterpreter.cpp |  18 +
 .../ScriptInterpreter/Python/CMakeLists.txt   |   1 +
 .../ScriptInterpreterPythonInterfaces.cpp     |   2 +
 .../ScriptInterpreterPythonInterfaces.h       |   1 +
 .../ScriptedCommandPythonInterface.cpp        | 247 +++++++++
 .../ScriptedCommandPythonInterface.h          |  81 +++
 .../Interfaces/ScriptedPythonInterface.cpp    |  55 ++
 .../Interfaces/ScriptedPythonInterface.h      |  36 ++
 .../Python/SWIGPythonBridge.h                 |  33 +-
 .../Python/ScriptInterpreterPython.cpp        | 506 +-----------------
 .../Python/ScriptInterpreterPythonImpl.h      |  52 +-
 .../Python/PythonTestSuite.cpp                |  51 +-
 24 files changed, 806 insertions(+), 994 deletions(-)
 create mode 100644 lldb/examples/python/templates/scripted_command.py
 create mode 100644 
lldb/include/lldb/Interpreter/Interfaces/ScriptedCommandInterface.h
 create mode 100644 
lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedCommandPythonInterface.cpp
 create mode 100644 
lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedCommandPythonInterface.h

diff --git a/lldb/bindings/python/CMakeLists.txt 
b/lldb/bindings/python/CMakeLists.txt
index d29b143c1408c..6ffdf9ccafabc 100644
--- a/lldb/bindings/python/CMakeLists.txt
+++ b/lldb/bindings/python/CMakeLists.txt
@@ -120,6 +120,7 @@ function(finish_swig_python swig_target 
lldb_python_bindings_dir lldb_python_tar
     "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_breakpoint.py"
     "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_hook.py"
     
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_stackframe_recognizer.py"
+    "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_command.py"
     )
 
   if(APPLE)
diff --git a/lldb/bindings/python/python-wrapper.swig 
b/lldb/bindings/python/python-wrapper.swig
index 2392737402e20..c4071b3f7b5b1 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -213,25 +213,6 @@ PythonObject 
lldb_private::python::SWIGBridge::LLDBSwigPythonCreateSyntheticProv
   return PythonObject();
 }
 
-PythonObject 
lldb_private::python::SWIGBridge::LLDBSwigPythonCreateCommandObject(
-    const char *python_class_name, const char *session_dictionary_name,
-    lldb::DebuggerSP debugger_sp) {
-  if (python_class_name == NULL || python_class_name[0] == '\0' ||
-      !session_dictionary_name)
-    return PythonObject();
-
-  PyErr_Cleaner py_err_cleaner(true);
-  auto dict = PythonModule::MainModule().ResolveName<PythonDictionary>(
-      session_dictionary_name);
-  auto pfunc = PythonObject::ResolveNameWithDictionary<PythonCallable>(
-      python_class_name, dict);
-
-  if (!pfunc.IsAllocated())
-    return PythonObject();
-
-  return pfunc(SWIGBridge::ToSWIGWrapper(std::move(debugger_sp)), dict);
-}
-
 // wrapper that calls an optional instance member of an object taking no
 // arguments
 static PyObject *LLDBSwigPython_CallOptionalMember(
@@ -497,6 +478,32 @@ void 
*lldb_private::python::LLDBSWIGPython_CastPyObjectToSBError(PyObject * data
   return sb_ptr;
 }
 
+void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBCommandReturnObject(
+    PyObject *data) {
+  lldb::SBCommandReturnObject *sb_ptr = nullptr;
+
+  int valid_cast = SWIG_ConvertPtr(
+      data, (void **)&sb_ptr, SWIGTYPE_p_lldb__SBCommandReturnObject, 0);
+
+  if (valid_cast == -1)
+    return NULL;
+
+  return sb_ptr;
+}
+
+void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBDebugger(
+    PyObject *data) {
+  lldb::SBDebugger *sb_ptr = nullptr;
+
+  int valid_cast =
+      SWIG_ConvertPtr(data, (void **)&sb_ptr, SWIGTYPE_p_lldb__SBDebugger, 0);
+
+  if (valid_cast == -1)
+    return NULL;
+
+  return sb_ptr;
+}
+
 void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBEvent(PyObject * 
data) {
   lldb::SBEvent *sb_ptr = nullptr;
 
@@ -639,145 +646,6 @@ bool 
lldb_private::python::SWIGBridge::LLDBSwigPythonCallCommand(
   return true;
 }
 
-bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallCommandObject(
-    PyObject *implementor, lldb::DebuggerSP debugger, const char *args,
-    lldb_private::CommandReturnObject &cmd_retobj,
-    lldb::ExecutionContextRefSP exe_ctx_ref_sp) {
-
-  PyErr_Cleaner py_err_cleaner(true);
-
-  PythonObject self(PyRefType::Borrowed, implementor);
-  auto pfunc = self.ResolveName<PythonCallable>("__call__");
-
-  if (!pfunc.IsAllocated())
-    return false;
-
-  auto cmd_retobj_arg = SWIGBridge::ToSWIGWrapper(cmd_retobj);
-
-  pfunc(SWIGBridge::ToSWIGWrapper(std::move(debugger)), PythonString(args),
-        SWIGBridge::ToSWIGWrapper(exe_ctx_ref_sp), cmd_retobj_arg.obj());
-
-  return true;
-}
-
-std::optional<std::string>
-lldb_private::python::SWIGBridge::LLDBSwigPythonGetRepeatCommandForScriptedCommand(PyObject
 *implementor,
-                                               std::string &command) {
-  PyErr_Cleaner py_err_cleaner(true);
-
-  PythonObject self(PyRefType::Borrowed, implementor);
-  auto pfunc = self.ResolveName<PythonCallable>("get_repeat_command");
-  // If not implemented, repeat the exact command.
-  if (!pfunc.IsAllocated())
-    return std::nullopt;
-
-  PythonString command_str(command);
-  PythonObject result = pfunc(command_str);
-
-  // A return of None is the equivalent of nullopt - means repeat
-  // the command as is:
-  if (result.IsNone())
-    return std::nullopt;
-
-  return result.Str().GetString().str();
-}
-
-StructuredData::DictionarySP
-lldb_private::python::SWIGBridge::LLDBSwigPythonHandleArgumentCompletionForScriptedCommand(PyObject
 *implementor,
-    std::vector<llvm::StringRef> &args_vec, size_t args_pos, size_t 
pos_in_arg) {
-
-  PyErr_Cleaner py_err_cleaner(true);
-
-  PythonObject self(PyRefType::Borrowed, implementor);
-  auto pfunc = self.ResolveName<PythonCallable>("handle_argument_completion");
-  // If this isn't implemented, return an empty dict to signal falling back to 
default completion:
-  if (!pfunc.IsAllocated())
-    return {};
-
-  PythonList args_list(PyInitialValue::Empty);
-  for (auto elem : args_vec)
-    args_list.AppendItem(PythonString(elem));
-
-  PythonObject result = pfunc(args_list, PythonInteger(args_pos), 
PythonInteger(pos_in_arg));
-  // Returning None means do the ordinary completion
-  if (result.IsNone())
-    return {};
-
-  // Convert the return dictionary to a DictionarySP.
-  StructuredData::ObjectSP result_obj_sp = result.CreateStructuredObject();
-  if (!result_obj_sp)
-    return {};
-
-  StructuredData::DictionarySP dict_sp(new 
StructuredData::Dictionary(result_obj_sp));
-  if (dict_sp->GetType() == lldb::eStructuredDataTypeInvalid)
-    return {};
-  return dict_sp;
-}
-
-StructuredData::DictionarySP
-lldb_private::python::SWIGBridge::LLDBSwigPythonHandleOptionArgumentCompletionForScriptedCommand(PyObject
 *implementor,
-    llvm::StringRef &long_option, size_t pos_in_arg) {
-
-  PyErr_Cleaner py_err_cleaner(true);
-
-  PythonObject self(PyRefType::Borrowed, implementor);
-  auto pfunc = 
self.ResolveName<PythonCallable>("handle_option_argument_completion");
-  // If this isn't implemented, return an empty dict to signal falling back to 
default completion:
-  if (!pfunc.IsAllocated())
-    return {};
-
-  PythonObject result = pfunc(PythonString(long_option), 
PythonInteger(pos_in_arg));
-  // Returning None means do the ordinary completion
-  if (result.IsNone())
-    return {};
-
-  // Returning a boolean:
-  // True means the completion was handled, but there were no completions
-  // False means that the completion was not handled, again, do the ordinary 
completion:
-  if (result.GetObjectType() == PyObjectType::Boolean) {
-    if (!result.IsTrue())
-      return {};
-    // Make up a completion dictionary with the right element:
-    StructuredData::DictionarySP dict_sp(new StructuredData::Dictionary());
-    dict_sp->AddBooleanItem("no-completion", true);
-    return dict_sp;
-  }
-
-
-  // Convert the return dictionary to a DictionarySP.
-  StructuredData::ObjectSP result_obj_sp = result.CreateStructuredObject();
-  if (!result_obj_sp)
-    return {};
-
-  StructuredData::DictionarySP dict_sp(new 
StructuredData::Dictionary(result_obj_sp));
-  if (dict_sp->GetType() == lldb::eStructuredDataTypeInvalid)
-    return {};
-  return dict_sp;
-}
-
-#include "lldb/Interpreter/CommandReturnObject.h"
-
-bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallParsedCommandObject(
-    PyObject *implementor, lldb::DebuggerSP debugger, 
lldb_private::StructuredDataImpl &args_impl,
-    lldb_private::CommandReturnObject &cmd_retobj,
-    lldb::ExecutionContextRefSP exe_ctx_ref_sp) {
-
-  PyErr_Cleaner py_err_cleaner(true);
-
-  PythonObject self(PyRefType::Borrowed, implementor);
-  auto pfunc = self.ResolveName<PythonCallable>("__call__");
-
-  if (!pfunc.IsAllocated()) {
-    cmd_retobj.AppendError("Could not find '__call__' method in implementation 
class");
-    return false;
-  }
-
-  pfunc(SWIGBridge::ToSWIGWrapper(std::move(debugger)), 
SWIGBridge::ToSWIGWrapper(args_impl),
-        SWIGBridge::ToSWIGWrapper(exe_ctx_ref_sp), 
SWIGBridge::ToSWIGWrapper(cmd_retobj).obj());
-
-  return true;
-}
-
 PythonObject lldb_private::python::SWIGBridge::LLDBSWIGPythonCreateOSPlugin(
     const char *python_class_name, const char *session_dictionary_name,
     const lldb::ProcessSP &process_sp) {
diff --git a/lldb/docs/CMakeLists.txt b/lldb/docs/CMakeLists.txt
index dd091836dc1aa..53647990f7842 100644
--- a/lldb/docs/CMakeLists.txt
+++ b/lldb/docs/CMakeLists.txt
@@ -33,6 +33,7 @@ if (LLDB_ENABLE_PYTHON AND SPHINX_FOUND)
       COMMAND "${CMAKE_COMMAND}" -E copy 
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_breakpoint.py" 
"${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/"
       COMMAND "${CMAKE_COMMAND}" -E copy 
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_hook.py" 
"${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/"
       COMMAND "${CMAKE_COMMAND}" -E copy 
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_stackframe_recognizer.py"
 "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/"
+      COMMAND "${CMAKE_COMMAND}" -E copy 
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_command.py" 
"${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/"
       COMMENT "Copying lldb.py to pretend its a Python package.")
 
     add_dependencies(lldb-python-doc-package swig_wrapper_python)
diff --git a/lldb/examples/python/templates/scripted_command.py 
b/lldb/examples/python/templates/scripted_command.py
new file mode 100644
index 0000000000000..d9b18ea4376aa
--- /dev/null
+++ b/lldb/examples/python/templates/scripted_command.py
@@ -0,0 +1,86 @@
+from abc import ABCMeta, abstractmethod
+from typing import Optional
+
+import lldb
+
+
+class ScriptedCommand(metaclass=ABCMeta):
+    """
+    The base class for a scripted (raw) command.
+
+    A raw command receives the unparsed argument string exactly as the user
+    typed it, and is responsible for any parsing it needs. For a command
+    with a table-driven option/argument parser, see `ParsedCommand` instead.
+    Register it with `command script add -c <ClassName> ...`.
+
+    Most of the base class methods are `@abstractmethod` that need to be
+    overwritten by the inheriting class.
+    """
+
+    def __init__(self, debugger: lldb.SBDebugger):
+        """Construct a scripted command.
+
+        Args:
+            debugger (lldb.SBDebugger): The debugger this command is being
+                added to.
+        """
+        pass
+
+    @abstractmethod
+    def __call__(
+        self,
+        debugger: lldb.SBDebugger,
+        args: str,
+        exe_ctx: lldb.SBExecutionContext,
+        result: lldb.SBCommandReturnObject,
+    ) -> None:
+        """Execute the command.
+
+        Args:
+            debugger (lldb.SBDebugger): The debugger the command runs
+                against.
+            args (str): The raw, unparsed argument string.
+            exe_ctx (lldb.SBExecutionContext): The execution context.
+            result (lldb.SBCommandReturnObject): Write command output/errors
+                here.
+        """
+        pass
+
+    def get_short_help(self) -> Optional[str]:
+        """A one-line description shown by `help`.
+
+        Returns:
+            str: The short help string.
+        """
+        pass
+
+    def get_long_help(self) -> Optional[str]:
+        """The full help text shown by `help <command>`.
+
+        Returns:
+            str: The long help string.
+        """
+        pass
+
+    def get_flags(self) -> int:
+        """Command flags (a bitmask of `lldb.eCommandRequires*`/
+        `lldb.eCommandProcessMustBe*` etc.) controlling when this command is
+        available.
+
+        Returns:
+            int: The flags bitmask. Defaults to 0 (no restrictions).
+        """
+        return 0
+
+    def get_repeat_command(self, command: str) -> Optional[str]:
+        """Customize what runs when the user presses Enter to repeat this
+        command.
+
+        Args:
+            command (str): The command line that was run.
+
+        Returns:
+            str: The command line to run on repeat. Defaults to `None`,
+            meaning repeat the original command unmodified.
+        """
+        pass
diff --git a/lldb/include/lldb/API/SBCommandReturnObject.h 
b/lldb/include/lldb/API/SBCommandReturnObject.h
index 6386bd250afa5..ca7debfc4e575 100644
--- a/lldb/include/lldb/API/SBCommandReturnObject.h
+++ b/lldb/include/lldb/API/SBCommandReturnObject.h
@@ -17,10 +17,25 @@
 
 namespace lldb_private {
 class CommandPluginInterfaceImplementation;
-class SBCommandReturnObjectImpl;
+class CommandReturnObject;
 namespace python {
 class SWIGBridge;
 }
+
+class SBCommandReturnObjectImpl {
+public:
+  SBCommandReturnObjectImpl();
+  SBCommandReturnObjectImpl(CommandReturnObject &ref);
+  SBCommandReturnObjectImpl(const SBCommandReturnObjectImpl &rhs);
+  SBCommandReturnObjectImpl &operator=(const SBCommandReturnObjectImpl &rhs);
+  ~SBCommandReturnObjectImpl();
+
+  CommandReturnObject *get() const { return m_ptr; }
+
+private:
+  CommandReturnObject *m_ptr;
+  bool m_owned = true;
+};
 } // namespace lldb_private
 
 namespace lldb {
@@ -144,6 +159,7 @@ class LLDB_API SBCommandReturnObject {
 
   friend class lldb_private::CommandPluginInterfaceImplementation;
   friend class lldb_private::python::SWIGBridge;
+  friend class lldb_private::ScriptInterpreter;
 
   SBCommandReturnObject(lldb_private::CommandReturnObject &ref);
 
diff --git a/lldb/include/lldb/API/SBDebugger.h 
b/lldb/include/lldb/API/SBDebugger.h
index 14b8350902811..3e302f121f5ec 100644
--- a/lldb/include/lldb/API/SBDebugger.h
+++ b/lldb/include/lldb/API/SBDebugger.h
@@ -678,6 +678,7 @@ class LLDB_API SBDebugger {
 protected:
   friend class lldb_private::CommandPluginInterfaceImplementation;
   friend class lldb_private::python::SWIGBridge;
+  friend class lldb_private::ScriptInterpreter;
   friend class lldb_private::SystemInitializerFull;
 
   SBDebugger(const lldb::DebuggerSP &debugger_sp);
diff --git 
a/lldb/include/lldb/Interpreter/Interfaces/ScriptedCommandInterface.h 
b/lldb/include/lldb/Interpreter/Interfaces/ScriptedCommandInterface.h
new file mode 100644
index 0000000000000..a302c8d766eb9
--- /dev/null
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedCommandInterface.h
@@ -0,0 +1,83 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_INTERPRETER_INTERFACES_SCRIPTEDCOMMANDINTERFACE_H
+#define LLDB_INTERPRETER_INTERFACES_SCRIPTEDCOMMANDINTERFACE_H
+
+#include "ScriptedInterface.h"
+#include "lldb/lldb-private.h"
+
+namespace lldb_private {
+/// Interface for a scripted command, covering both the raw (`command script
+/// add -c`, `__call__(debugger, args, exe_ctx, result)`) and parsed
+/// (`command script add -P`, option/argument table driven) calling
+/// conventions. Both are instantiated through the same Python class lookup,
+/// and differ only in which of RunRawCommand/RunParsedCommand gets called.
+class ScriptedCommandInterface : virtual public ScriptedInterface {
+public:
+  virtual llvm::Expected<StructuredData::GenericSP>
+  CreatePluginObject(llvm::StringRef class_name,
+                     lldb::DebuggerSP debugger_sp) = 0;
+
+  virtual bool RunRawCommand(llvm::StringRef args,
+                             ScriptedCommandSynchronicity synchronicity,
+                             CommandReturnObject &cmd_retobj, Status &error,
+                             const ExecutionContext &exe_ctx) {
+    return false;
+  }
+
+  virtual bool RunParsedCommand(Args &args,
+                                ScriptedCommandSynchronicity synchronicity,
+                                CommandReturnObject &cmd_retobj, Status &error,
+                                const ExecutionContext &exe_ctx) {
+    return false;
+  }
+
+  virtual std::optional<std::string> GetRepeatCommand(Args &args) {
+    return std::nullopt;
+  }
+
+  virtual StructuredData::DictionarySP
+  HandleArgumentCompletion(std::vector<llvm::StringRef> &args, size_t args_pos,
+                           size_t char_in_arg) {
+    return {};
+  }
+
+  virtual StructuredData::DictionarySP
+  HandleOptionArgumentCompletion(llvm::StringRef &long_option,
+                                 size_t char_in_arg) {
+    return {};
+  }
+
+  virtual bool GetShortHelp(std::string &dest) {
+    dest.clear();
+    return false;
+  }
+
+  virtual bool GetLongHelp(std::string &dest) {
+    dest.clear();
+    return false;
+  }
+
+  virtual uint32_t GetFlags() { return 0; }
+
+  virtual StructuredData::ObjectSP GetOptionsDefinition() { return {}; }
+
+  virtual StructuredData::ObjectSP GetArgumentsDefinition() { return {}; }
+
+  virtual void OptionParsingStarted() {}
+
+  virtual bool SetOptionValue(ExecutionContext *exe_ctx,
+                              llvm::StringRef long_option,
+                              llvm::StringRef value) {
+    return false;
+  }
+};
+} // namespace lldb_private
+
+#endif // LLDB_INTERPRETER_INTERFACES_SCRIPTEDCOMMANDINTERFACE_H
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h 
b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index 0e65cb4b8ac4a..9d3e7e6f32ff5 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -263,11 +263,6 @@ class ScriptInterpreter : public PluginInterface {
     return StructuredData::ObjectSP();
   }
 
-  virtual StructuredData::GenericSP
-  CreateScriptCommandObject(const char *class_name) {
-    return StructuredData::GenericSP();
-  }
-
   virtual StructuredData::ObjectSP
   LoadPluginModule(const FileSpec &file_spec, lldb_private::Status &error) {
     return StructuredData::ObjectSP();
@@ -394,42 +389,6 @@ class ScriptInterpreter : public PluginInterface {
     return false;
   }
 
-  virtual bool RunScriptBasedCommand(
-      StructuredData::GenericSP impl_obj_sp, llvm::StringRef args,
-      ScriptedCommandSynchronicity synchronicity,
-      lldb_private::CommandReturnObject &cmd_retobj, Status &error,
-      const lldb_private::ExecutionContext &exe_ctx) {
-    return false;
-  }
-
-  virtual bool RunScriptBasedParsedCommand(
-      StructuredData::GenericSP impl_obj_sp, Args& args,
-      ScriptedCommandSynchronicity synchronicity,
-      lldb_private::CommandReturnObject &cmd_retobj, Status &error,
-      const lldb_private::ExecutionContext &exe_ctx) {
-    return false;
-  }
-
-  virtual std::optional<std::string>
-  GetRepeatCommandForScriptedCommand(StructuredData::GenericSP impl_obj_sp,
-                                     Args &args) {
-    return std::nullopt;
-  }
-
-  virtual StructuredData::DictionarySP
-  HandleArgumentCompletionForScriptedCommand(
-      StructuredData::GenericSP impl_obj_sp, std::vector<llvm::StringRef> 
&args,
-      size_t args_pos, size_t char_in_arg) {
-    return {};
-  }
-
-  virtual StructuredData::DictionarySP
-  HandleOptionArgumentCompletionForScriptedCommand(
-      StructuredData::GenericSP impl_obj_sp, llvm::StringRef &long_name,
-      size_t char_in_arg) {
-    return {};
-  }
-
   virtual bool RunScriptFormatKeyword(const char *impl_function,
                                       Process *process, std::string &output,
                                       Status &error) {
@@ -468,43 +427,6 @@ class ScriptInterpreter : public PluginInterface {
     return false;
   }
 
-  virtual bool
-  GetShortHelpForCommandObject(StructuredData::GenericSP cmd_obj_sp,
-                               std::string &dest) {
-    dest.clear();
-    return false;
-  }
-
-  virtual StructuredData::ObjectSP
-  GetOptionsForCommandObject(StructuredData::GenericSP cmd_obj_sp) {
-    return {};
-  }
-
-  virtual StructuredData::ObjectSP
-  GetArgumentsForCommandObject(StructuredData::GenericSP cmd_obj_sp) {
-    return {};
-  }
-
-  virtual bool SetOptionValueForCommandObject(
-      StructuredData::GenericSP cmd_obj_sp, ExecutionContext *exe_ctx,
-      llvm::StringRef long_option, llvm::StringRef value) {
-    return false;
-  }
-
-  virtual void
-  OptionParsingStartedForCommandObject(StructuredData::GenericSP cmd_obj_sp) {}
-
-  virtual uint32_t
-  GetFlagsForCommandObject(StructuredData::GenericSP cmd_obj_sp) {
-    return 0;
-  }
-
-  virtual bool GetLongHelpForCommandObject(StructuredData::GenericSP 
cmd_obj_sp,
-                                           std::string &dest) {
-    dest.clear();
-    return false;
-  }
-
   virtual bool CheckObjectExists(const char *name) { return false; }
 
   virtual bool
@@ -586,6 +508,10 @@ class ScriptInterpreter : public PluginInterface {
     return {};
   }
 
+  virtual lldb::ScriptedCommandInterfaceSP CreateScriptedCommandInterface() {
+    return {};
+  }
+
   virtual StructuredData::ObjectSP
   CreateStructuredDataFromScriptObject(ScriptObject obj) {
     return {};
@@ -658,6 +584,12 @@ class ScriptInterpreter : public PluginInterface {
   lldb::BreakpointLocationSP GetOpaqueTypeFromSBBreakpointLocation(
       const lldb::SBBreakpointLocation &break_loc) const;
 
+  CommandReturnObject *GetOpaqueTypeFromSBCommandReturnObject(
+      const lldb::SBCommandReturnObject &cmd_retobj) const;
+
+  lldb::DebuggerSP
+  GetOpaqueTypeFromSBDebugger(const lldb::SBDebugger &debugger) const;
+
   lldb::ProcessAttachInfoSP
   GetOpaqueTypeFromSBAttachInfo(const lldb::SBAttachInfo &attach_info) const;
 
diff --git a/lldb/include/lldb/lldb-enumerations.h 
b/lldb/include/lldb/lldb-enumerations.h
index 93c252b55de99..f73d0085f2320 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -268,7 +268,9 @@ enum ScriptedExtension {
   eScriptedExtensionScriptedThread,
   eScriptedExtensionScriptedFrame,
   eScriptedExtensionScriptedStackFrameRecognizer,
-  kLastScriptedExtension = eScriptedExtensionScriptedStackFrameRecognizer
+  eScriptedExtensionScriptedCommand,
+  eScriptedExtensionParsedCommand,
+  kLastScriptedExtension = eScriptedExtensionParsedCommand
 };
 
 /// Register numbering types.
diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h
index 157aa5743f016..2572aa0dc344b 100644
--- a/lldb/include/lldb/lldb-forward.h
+++ b/lldb/include/lldb/lldb-forward.h
@@ -193,6 +193,7 @@ class ScriptedFrameInterface;
 class ScriptedFrameProviderInterface;
 class ScriptedMetadata;
 class ScriptedBreakpointInterface;
+class ScriptedCommandInterface;
 class ScriptedHookInterface;
 class ScriptedPlatformInterface;
 class ScriptedProcessInterface;
@@ -438,6 +439,8 @@ typedef 
std::shared_ptr<lldb_private::ScriptedBreakpointInterface>
     ScriptedBreakpointInterfaceSP;
 typedef std::shared_ptr<lldb_private::ScriptedStackFrameRecognizerInterface>
     ScriptedStackFrameRecognizerInterfaceSP;
+typedef std::shared_ptr<lldb_private::ScriptedCommandInterface>
+    ScriptedCommandInterfaceSP;
 typedef std::shared_ptr<lldb_private::Section> SectionSP;
 typedef std::unique_ptr<lldb_private::SectionList> SectionListUP;
 typedef std::weak_ptr<lldb_private::Section> SectionWP;
diff --git a/lldb/source/API/SBCommandReturnObject.cpp 
b/lldb/source/API/SBCommandReturnObject.cpp
index 62cc7a1f05573..7c71d9da8374d 100644
--- a/lldb/source/API/SBCommandReturnObject.cpp
+++ b/lldb/source/API/SBCommandReturnObject.cpp
@@ -25,30 +25,27 @@
 using namespace lldb;
 using namespace lldb_private;
 
-class lldb_private::SBCommandReturnObjectImpl {
-public:
-  SBCommandReturnObjectImpl() : m_ptr(new CommandReturnObject(false)) {}
-  SBCommandReturnObjectImpl(CommandReturnObject &ref)
-      : m_ptr(&ref), m_owned(false) {}
-  SBCommandReturnObjectImpl(const SBCommandReturnObjectImpl &rhs)
-      : m_ptr(new CommandReturnObject(*rhs.m_ptr)), m_owned(rhs.m_owned) {}
-  SBCommandReturnObjectImpl &operator=(const SBCommandReturnObjectImpl &rhs) {
-    SBCommandReturnObjectImpl copy(rhs);
-    std::swap(*this, copy);
-    return *this;
-  }
-  // rvalue ctor+assignment are not used by SBCommandReturnObject.
-  ~SBCommandReturnObjectImpl() {
-    if (m_owned)
-      delete m_ptr;
-  }
+SBCommandReturnObjectImpl::SBCommandReturnObjectImpl()
+    : m_ptr(new CommandReturnObject(false)) {}
 
-  CommandReturnObject &operator*() const { return *m_ptr; }
+SBCommandReturnObjectImpl::SBCommandReturnObjectImpl(CommandReturnObject &ref)
+    : m_ptr(&ref), m_owned(false) {}
 
-private:
-  CommandReturnObject *m_ptr;
-  bool m_owned = true;
-};
+SBCommandReturnObjectImpl::SBCommandReturnObjectImpl(
+    const SBCommandReturnObjectImpl &rhs)
+    : m_ptr(new CommandReturnObject(*rhs.m_ptr)), m_owned(rhs.m_owned) {}
+
+SBCommandReturnObjectImpl &
+SBCommandReturnObjectImpl::operator=(const SBCommandReturnObjectImpl &rhs) {
+  SBCommandReturnObjectImpl copy(rhs);
+  std::swap(*this, copy);
+  return *this;
+}
+
+SBCommandReturnObjectImpl::~SBCommandReturnObjectImpl() {
+  if (m_owned)
+    delete m_ptr;
+}
 
 SBCommandReturnObject::SBCommandReturnObject()
     : m_opaque_up(new SBCommandReturnObjectImpl()) {
@@ -221,19 +218,19 @@ void SBCommandReturnObject::AppendWarning(const char 
*message) {
 }
 
 CommandReturnObject *SBCommandReturnObject::operator->() const {
-  return &**m_opaque_up;
+  return m_opaque_up->get();
 }
 
 CommandReturnObject *SBCommandReturnObject::get() const {
-  return &**m_opaque_up;
+  return m_opaque_up->get();
 }
 
 CommandReturnObject &SBCommandReturnObject::operator*() const {
-  return **m_opaque_up;
+  return *m_opaque_up->get();
 }
 
 CommandReturnObject &SBCommandReturnObject::ref() const {
-  return **m_opaque_up;
+  return *m_opaque_up->get();
 }
 
 bool SBCommandReturnObject::GetDescription(SBStream &description) {
diff --git a/lldb/source/Commands/CommandObjectCommands.cpp 
b/lldb/source/Commands/CommandObjectCommands.cpp
index 8f006768ecc9a..b29059bd3cf22 100644
--- a/lldb/source/Commands/CommandObjectCommands.cpp
+++ b/lldb/source/Commands/CommandObjectCommands.cpp
@@ -16,6 +16,7 @@
 #include "lldb/Interpreter/CommandInterpreter.h"
 #include "lldb/Interpreter/CommandOptionArgumentTable.h"
 #include "lldb/Interpreter/CommandReturnObject.h"
+#include "lldb/Interpreter/Interfaces/ScriptedCommandInterface.h"
 #include "lldb/Interpreter/OptionArgParser.h"
 #include "lldb/Interpreter/OptionValueBoolean.h"
 #include "lldb/Interpreter/OptionValueString.h"
@@ -1119,19 +1120,19 @@ class CommandObjectPythonFunction : public 
CommandObjectRaw {
 /// substitution).
 class CommandObjectScriptingObjectRaw : public CommandObjectRaw {
 public:
-  CommandObjectScriptingObjectRaw(CommandInterpreter &interpreter,
-                                  std::string name,
-                                  StructuredData::GenericSP cmd_obj_sp,
-                                  ScriptedCommandSynchronicity synch,
-                                  CompletionType completion_type)
-      : CommandObjectRaw(interpreter, name), m_cmd_obj_sp(cmd_obj_sp),
-        m_synchro(synch), m_fetched_help_short(false),
-        m_fetched_help_long(false), m_completion_type(completion_type) {
+  CommandObjectScriptingObjectRaw(
+      CommandInterpreter &interpreter, std::string name,
+      lldb::ScriptedCommandInterfaceSP cmd_interface_sp,
+      ScriptedCommandSynchronicity synch, CompletionType completion_type)
+      : CommandObjectRaw(interpreter, name),
+        m_cmd_interface_sp(cmd_interface_sp), m_synchro(synch),
+        m_fetched_help_short(false), m_fetched_help_long(false),
+        m_completion_type(completion_type) {
     StreamString stream;
     stream.Printf("For more information run 'help %s'", name.c_str());
     SetHelp(stream.GetString());
-    if (ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter())
-      GetFlags().Set(scripter->GetFlagsForCommandObject(cmd_obj_sp));
+    if (m_cmd_interface_sp)
+      GetFlags().Set(m_cmd_interface_sp->GetFlags());
   }
 
   ~CommandObjectScriptingObjectRaw() override = default;
@@ -1151,22 +1152,19 @@ class CommandObjectScriptingObjectRaw : public 
CommandObjectRaw {
 
   std::optional<std::string> GetRepeatCommand(Args &args,
                                               uint32_t index) override {
-    ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
-    if (!scripter)
+    if (!m_cmd_interface_sp)
       return std::nullopt;
 
-    return scripter->GetRepeatCommandForScriptedCommand(m_cmd_obj_sp, args);
+    return m_cmd_interface_sp->GetRepeatCommand(args);
   }
 
   llvm::StringRef GetHelp() override {
     if (m_fetched_help_short)
       return CommandObjectRaw::GetHelp();
-    ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
-    if (!scripter)
+    if (!m_cmd_interface_sp)
       return CommandObjectRaw::GetHelp();
     std::string docstring;
-    m_fetched_help_short =
-        scripter->GetShortHelpForCommandObject(m_cmd_obj_sp, docstring);
+    m_fetched_help_short = m_cmd_interface_sp->GetShortHelp(docstring);
     if (!docstring.empty())
       SetHelp(docstring);
 
@@ -1177,13 +1175,11 @@ class CommandObjectScriptingObjectRaw : public 
CommandObjectRaw {
     if (m_fetched_help_long)
       return CommandObjectRaw::GetHelpLong();
 
-    ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
-    if (!scripter)
+    if (!m_cmd_interface_sp)
       return CommandObjectRaw::GetHelpLong();
 
     std::string docstring;
-    m_fetched_help_long =
-        scripter->GetLongHelpForCommandObject(m_cmd_obj_sp, docstring);
+    m_fetched_help_long = m_cmd_interface_sp->GetLongHelp(docstring);
     if (!docstring.empty())
       SetHelpLong(docstring);
     return CommandObjectRaw::GetHelpLong();
@@ -1192,15 +1188,13 @@ class CommandObjectScriptingObjectRaw : public 
CommandObjectRaw {
 protected:
   void DoExecute(llvm::StringRef raw_command_line,
                  CommandReturnObject &result) override {
-    ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
-
     Status error;
 
     result.SetStatus(eReturnStatusInvalid);
 
-    if (!scripter ||
-        !scripter->RunScriptBasedCommand(m_cmd_obj_sp, raw_command_line,
-                                         m_synchro, result, error, m_exe_ctx)) 
{
+    if (!m_cmd_interface_sp ||
+        !m_cmd_interface_sp->RunRawCommand(raw_command_line, m_synchro, result,
+                                           error, m_exe_ctx)) {
       result.AppendError(error.AsCString());
     } else {
       // Don't change the status if the command already set it...
@@ -1214,7 +1208,7 @@ class CommandObjectScriptingObjectRaw : public 
CommandObjectRaw {
   }
 
 private:
-  StructuredData::GenericSP m_cmd_obj_sp;
+  lldb::ScriptedCommandInterfaceSP m_cmd_interface_sp;
   ScriptedCommandSynchronicity m_synchro;
   bool m_fetched_help_short : 1;
   bool m_fetched_help_long : 1;
@@ -1238,23 +1232,16 @@ class CommandObjectScriptingObjectParsed : public 
CommandObjectParsed {
 private: 
   class CommandOptions : public Options {
   public:
-    CommandOptions(CommandInterpreter &interpreter, 
-        StructuredData::GenericSP cmd_obj_sp) : m_interpreter(interpreter), 
-            m_cmd_obj_sp(cmd_obj_sp) {}
+    CommandOptions(CommandInterpreter &interpreter,
+                   lldb::ScriptedCommandInterfaceSP cmd_interface_sp)
+        : m_cmd_interface_sp(cmd_interface_sp) {}
 
     ~CommandOptions() override = default;
 
     Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
                           ExecutionContext *execution_context) override {
       Status error;
-      ScriptInterpreter *scripter = 
-        m_interpreter.GetDebugger().GetScriptInterpreter();
-      if (!scripter) {
-        return Status::FromErrorString(
-            "No script interpreter for SetOptionValue.");
-        return error;
-      }
-      if (!m_cmd_obj_sp) {
+      if (!m_cmd_interface_sp) {
         return Status::FromErrorString(
             "SetOptionValue called with empty cmd_obj.");
         return error;
@@ -1268,10 +1255,10 @@ class CommandObjectScriptingObjectParsed : public 
CommandObjectParsed {
       // Pass the long option, since you aren't actually required to have a
       // short_option, and for those options the index or short option 
character
       // aren't meaningful on the python side.
-      const char * long_option = 
-        m_options_definition_up.get()[option_idx].long_option;
-      bool success = scripter->SetOptionValueForCommandObject(m_cmd_obj_sp, 
-        execution_context, long_option, option_arg);
+      const char *long_option =
+          m_options_definition_up.get()[option_idx].long_option;
+      bool success = m_cmd_interface_sp->SetOptionValue(
+          execution_context, long_option, option_arg);
       if (!success)
         return Status::FromErrorStringWithFormatv(
             "Error setting option: {0} to {1}", long_option, option_arg);
@@ -1279,12 +1266,10 @@ class CommandObjectScriptingObjectParsed : public 
CommandObjectParsed {
     }
 
     void OptionParsingStarting(ExecutionContext *execution_context) override {
-      ScriptInterpreter *scripter = 
-        m_interpreter.GetDebugger().GetScriptInterpreter();
-      if (!scripter || !m_cmd_obj_sp)
+      if (!m_cmd_interface_sp)
         return;
 
-      scripter->OptionParsingStartedForCommandObject(m_cmd_obj_sp);
+      m_cmd_interface_sp->OptionParsingStarted();
     }
 
     llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
@@ -1292,7 +1277,7 @@ class CommandObjectScriptingObjectParsed : public 
CommandObjectParsed {
         return {};
       return llvm::ArrayRef(m_options_definition_up.get(), m_num_options);
     }
-    
+
     static Status ParseUsageMaskFromArray(StructuredData::ObjectSP obj_sp, 
         size_t counter, uint32_t &usage_mask) {
       // If the usage entry is not provided, we use LLDB_OPT_SET_ALL.
@@ -1726,10 +1711,7 @@ class CommandObjectScriptingObjectParsed : public 
CommandObjectParsed {
                                    OptionElementVector &option_vec,
                                    int opt_element_index,
                                    CommandInterpreter &interpreter) override {
-      ScriptInterpreter *scripter =
-          interpreter.GetDebugger().GetScriptInterpreter();
-
-      if (!scripter)
+      if (!m_cmd_interface_sp)
         return;
 
       ExecutionContext exe_ctx = interpreter.GetExecutionContext();
@@ -1746,9 +1728,8 @@ class CommandObjectScriptingObjectParsed : public 
CommandObjectParsed {
       // regular option completer handle that:
       StructuredData::DictionarySP completion_dict_sp;
       if (!is_enum)
-        completion_dict_sp =
-            scripter->HandleOptionArgumentCompletionForScriptedCommand(
-                m_cmd_obj_sp, option_name, request.GetCursorCharPos());
+        completion_dict_sp = 
m_cmd_interface_sp->HandleOptionArgumentCompletion(
+            option_name, request.GetCursorCharPos());
 
       if (!completion_dict_sp) {
         Options::HandleOptionArgumentCompletion(request, option_vec,
@@ -1809,19 +1790,17 @@ class CommandObjectScriptingObjectParsed : public 
CommandObjectParsed {
     std::vector<std::vector<EnumValueStorage>> m_enum_storage;
     std::vector<std::vector<OptionEnumValueElement>> m_enum_vector;
     std::vector<std::string> m_usage_container;
-    CommandInterpreter &m_interpreter;
-    StructuredData::GenericSP m_cmd_obj_sp;
+    lldb::ScriptedCommandInterfaceSP m_cmd_interface_sp;
     static std::unordered_set<std::string> g_string_storer;
   };
 
 public:
-  static CommandObjectSP Create(CommandInterpreter &interpreter, 
-                std::string name,
-                StructuredData::GenericSP cmd_obj_sp,
-                ScriptedCommandSynchronicity synch, 
-                CommandReturnObject &result) {
+  static CommandObjectSP
+  Create(CommandInterpreter &interpreter, std::string name,
+         lldb::ScriptedCommandInterfaceSP cmd_interface_sp,
+         ScriptedCommandSynchronicity synch, CommandReturnObject &result) {
     CommandObjectSP new_cmd_sp(new CommandObjectScriptingObjectParsed(
-        interpreter, name, cmd_obj_sp, synch));
+        interpreter, name, cmd_interface_sp, synch));
 
     CommandObjectScriptingObjectParsed *parsed_cmd 
         = static_cast<CommandObjectScriptingObjectParsed *>(new_cmd_sp.get());
@@ -1843,34 +1822,33 @@ class CommandObjectScriptingObjectParsed : public 
CommandObjectParsed {
     return new_cmd_sp;
   }
 
-  CommandObjectScriptingObjectParsed(CommandInterpreter &interpreter,
-                               std::string name,
-                               StructuredData::GenericSP cmd_obj_sp,
-                               ScriptedCommandSynchronicity synch)
-      : CommandObjectParsed(interpreter, name.c_str()), 
-        m_cmd_obj_sp(cmd_obj_sp), m_synchro(synch), 
-        m_options(interpreter, cmd_obj_sp), m_fetched_help_short(false), 
+  CommandObjectScriptingObjectParsed(
+      CommandInterpreter &interpreter, std::string name,
+      lldb::ScriptedCommandInterfaceSP cmd_interface_sp,
+      ScriptedCommandSynchronicity synch)
+      : CommandObjectParsed(interpreter, name.c_str()),
+        m_cmd_interface_sp(cmd_interface_sp), m_synchro(synch),
+        m_options(interpreter, cmd_interface_sp), m_fetched_help_short(false),
         m_fetched_help_long(false) {
     StreamString stream;
-    ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
-    if (!scripter) {
+    if (!m_cmd_interface_sp) {
       m_options_error = Status::FromErrorString("No script interpreter");
       return;
     }
 
     // Set the flags:
-    GetFlags().Set(scripter->GetFlagsForCommandObject(cmd_obj_sp));
+    GetFlags().Set(m_cmd_interface_sp->GetFlags());
 
     // Now set up the options definitions from the options:
-    StructuredData::ObjectSP options_object_sp 
-        = scripter->GetOptionsForCommandObject(cmd_obj_sp);
+    StructuredData::ObjectSP options_object_sp =
+        m_cmd_interface_sp->GetOptionsDefinition();
     // It's okay not to have an options dict.
     if (options_object_sp) {
       // The options come as a dictionary of dictionaries.  The key of the
       // outer dict is the long option name (since that's required).  The
       // value holds all the other option specification bits.
-      StructuredData::Dictionary *options_dict 
-          = options_object_sp->GetAsDictionary();
+      StructuredData::Dictionary *options_dict =
+          options_object_sp->GetAsDictionary();
       // but if it exists, it has to be an array.
       if (options_dict) {
         m_options_error = m_options.SetOptionsFromArray(*(options_dict));
@@ -1884,8 +1862,8 @@ class CommandObjectScriptingObjectParsed : public 
CommandObjectParsed {
     }
     // Then fetch the args.  Since the arguments can have usage masks you need
     // an array of arrays.
-    StructuredData::ObjectSP args_object_sp 
-      = scripter->GetArgumentsForCommandObject(cmd_obj_sp);
+    StructuredData::ObjectSP args_object_sp =
+        m_cmd_interface_sp->GetArgumentsDefinition();
     if (args_object_sp) {
       StructuredData::Array *args_array = args_object_sp->GetAsArray();        
       if (!args_array) {
@@ -2017,9 +1995,7 @@ class CommandObjectScriptingObjectParsed : public 
CommandObjectParsed {
 public:
   void HandleArgumentCompletion(CompletionRequest &request,
                                 OptionElementVector &option_vec) override {
-    ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
-
-    if (!scripter)
+    if (!m_cmd_interface_sp)
       return;
 
     // Set up the options values on the scripted side:
@@ -2057,8 +2033,8 @@ class CommandObjectScriptingObjectParsed : public 
CommandObjectParsed {
         args_elem_pos--;
     }
     StructuredData::DictionarySP completion_dict_sp =
-        scripter->HandleArgumentCompletionForScriptedCommand(
-            m_cmd_obj_sp, args_vec, args_elem_pos, request.GetCursorCharPos());
+        m_cmd_interface_sp->HandleArgumentCompletion(
+            args_vec, args_elem_pos, request.GetCursorCharPos());
 
     if (!completion_dict_sp) {
       CommandObject::HandleArgumentCompletion(request, option_vec);
@@ -2074,22 +2050,19 @@ class CommandObjectScriptingObjectParsed : public 
CommandObjectParsed {
 
   std::optional<std::string> GetRepeatCommand(Args &args,
                                               uint32_t index) override {
-    ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
-    if (!scripter)
+    if (!m_cmd_interface_sp)
       return std::nullopt;
 
-    return scripter->GetRepeatCommandForScriptedCommand(m_cmd_obj_sp, args);
+    return m_cmd_interface_sp->GetRepeatCommand(args);
   }
 
   llvm::StringRef GetHelp() override {
     if (m_fetched_help_short)
       return CommandObjectParsed::GetHelp();
-    ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
-    if (!scripter)
+    if (!m_cmd_interface_sp)
       return CommandObjectParsed::GetHelp();
     std::string docstring;
-    m_fetched_help_short =
-        scripter->GetShortHelpForCommandObject(m_cmd_obj_sp, docstring);
+    m_fetched_help_short = m_cmd_interface_sp->GetShortHelp(docstring);
     if (!docstring.empty())
       SetHelp(docstring);
 
@@ -2100,13 +2073,11 @@ class CommandObjectScriptingObjectParsed : public 
CommandObjectParsed {
     if (m_fetched_help_long)
       return CommandObjectParsed::GetHelpLong();
 
-    ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
-    if (!scripter)
+    if (!m_cmd_interface_sp)
       return CommandObjectParsed::GetHelpLong();
 
     std::string docstring;
-    m_fetched_help_long =
-        scripter->GetLongHelpForCommandObject(m_cmd_obj_sp, docstring);
+    m_fetched_help_long = m_cmd_interface_sp->GetLongHelp(docstring);
     if (!docstring.empty())
       SetHelpLong(docstring);
     return CommandObjectParsed::GetHelpLong();
@@ -2121,17 +2092,13 @@ class CommandObjectScriptingObjectParsed : public 
CommandObjectParsed {
   }
 
 protected:
-  void DoExecute(Args &args,
-                 CommandReturnObject &result) override {
-    ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
-
+  void DoExecute(Args &args, CommandReturnObject &result) override {
     Status error;
 
     result.SetStatus(eReturnStatusInvalid);
-    
-    if (!scripter ||
-        !scripter->RunScriptBasedParsedCommand(m_cmd_obj_sp, args,
-                                         m_synchro, result, error, m_exe_ctx)) 
{
+
+    if (!m_cmd_interface_sp || !m_cmd_interface_sp->RunParsedCommand(
+                                   args, m_synchro, result, error, m_exe_ctx)) 
{
       result.AppendError(error.AsCString());
     } else {
       // Don't change the status if the command already set it...
@@ -2145,7 +2112,7 @@ class CommandObjectScriptingObjectParsed : public 
CommandObjectParsed {
   }
 
 private:
-  StructuredData::GenericSP m_cmd_obj_sp;
+  lldb::ScriptedCommandInterfaceSP m_cmd_interface_sp;
   ScriptedCommandSynchronicity m_synchro;
   CommandOptions m_options;
   Status m_options_error;
@@ -2513,22 +2480,32 @@ class CommandObjectCommandsScriptAdd : public 
CommandObjectParsed,
         return;
       }
 
-      auto cmd_obj_sp = interpreter->CreateScriptCommandObject(
-          m_options.m_class_name.c_str());
-      if (!cmd_obj_sp) {
+      lldb::ScriptedCommandInterfaceSP cmd_interface_sp =
+          interpreter->CreateScriptedCommandInterface();
+      if (!cmd_interface_sp) {
+        result.AppendError("cannot create ScriptedCommandInterface");
+        return;
+      }
+
+      auto obj_or_err = cmd_interface_sp->CreatePluginObject(
+          m_options.m_class_name, GetDebugger().shared_from_this());
+      if (!obj_or_err) {
         result.AppendErrorWithFormatv("cannot create helper object for: "
-                                      "'{0}'", m_options.m_class_name);
+                                      "'{0}': {1}",
+                                      m_options.m_class_name,
+                                      llvm::toString(obj_or_err.takeError()));
         return;
       }
-      
+
       if (m_options.m_parsed_command) {
-        new_cmd_sp = CommandObjectScriptingObjectParsed::Create(m_interpreter, 
-            m_cmd_name, cmd_obj_sp, m_synchronicity, result);
+        new_cmd_sp = CommandObjectScriptingObjectParsed::Create(
+            m_interpreter, m_cmd_name, cmd_interface_sp, m_synchronicity,
+            result);
         if (!result.Succeeded())
           return;
       } else
         new_cmd_sp = std::make_shared<CommandObjectScriptingObjectRaw>(
-            m_interpreter, m_cmd_name, cmd_obj_sp, m_synchronicity,
+            m_interpreter, m_cmd_name, cmd_interface_sp, m_synchronicity,
             m_completion_type);
     }
     
diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp 
b/lldb/source/Interpreter/ScriptInterpreter.cpp
index 4f6095d097d10..44cae7a76ff10 100644
--- a/lldb/source/Interpreter/ScriptInterpreter.cpp
+++ b/lldb/source/Interpreter/ScriptInterpreter.cpp
@@ -7,6 +7,8 @@
 
//===----------------------------------------------------------------------===//
 
 #include "lldb/Interpreter/ScriptInterpreter.h"
+#include "lldb/API/SBCommandReturnObject.h"
+#include "lldb/API/SBDebugger.h"
 #include "lldb/Core/Debugger.h"
 #include "lldb/Host/ConnectionFileDescriptor.h"
 #include "lldb/Host/Pipe.h"
@@ -96,6 +98,16 @@ ScriptInterpreter::GetOpaqueTypeFromSBBreakpointLocation(
   return break_loc.m_opaque_wp.lock();
 }
 
+CommandReturnObject *ScriptInterpreter::GetOpaqueTypeFromSBCommandReturnObject(
+    const lldb::SBCommandReturnObject &cmd_retobj) const {
+  return cmd_retobj.m_opaque_up->get();
+}
+
+lldb::DebuggerSP ScriptInterpreter::GetOpaqueTypeFromSBDebugger(
+    const lldb::SBDebugger &debugger) const {
+  return debugger.m_opaque_sp;
+}
+
 lldb::ProcessAttachInfoSP ScriptInterpreter::GetOpaqueTypeFromSBAttachInfo(
     const lldb::SBAttachInfo &attach_info) const {
   return attach_info.m_opaque_sp;
@@ -221,6 +233,10 @@ 
ScriptInterpreter::ExtensionToString(lldb::ScriptedExtension extension) {
     return "ScriptedFrame";
   case eScriptedExtensionScriptedStackFrameRecognizer:
     return "ScriptedStackFrameRecognizer";
+  case eScriptedExtensionScriptedCommand:
+    return "ScriptedCommand";
+  case eScriptedExtensionParsedCommand:
+    return "ParsedCommand";
   }
   llvm_unreachable("unhandled ScriptedExtension");
 }
@@ -241,6 +257,8 @@ ScriptInterpreter::StringToExtension(llvm::StringRef 
string) {
       .CaseLower("ScriptedFrame", eScriptedExtensionScriptedFrame)
       .CaseLower("ScriptedStackFrameRecognizer",
                  eScriptedExtensionScriptedStackFrameRecognizer)
+      .CaseLower("ScriptedCommand", eScriptedExtensionScriptedCommand)
+      .CaseLower("ParsedCommand", eScriptedExtensionParsedCommand)
       .Default(eScriptedExtensionInvalid);
 }
 
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt 
b/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
index 201574da72a19..d3dc5773eb0af 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
@@ -32,6 +32,7 @@ set(python_plugin_sources
   Interfaces/ScriptedPythonInterface.cpp
   Interfaces/ScriptedHookPythonInterface.cpp
   Interfaces/ScriptedBreakpointPythonInterface.cpp
+  Interfaces/ScriptedCommandPythonInterface.cpp
   Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp
   Interfaces/ScriptedThreadPlanPythonInterface.cpp
   Interfaces/ScriptedThreadPythonInterface.cpp
diff --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp
 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp
index 8914f6b239023..2c8e0ef8bbeb0 100644
--- 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp
+++ 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp
@@ -30,6 +30,7 @@ void ScriptInterpreterPythonInterfaces::Initialize() {
   ScriptedThreadPythonInterface::Initialize();
   ScriptedFramePythonInterface::Initialize();
   ScriptedStackFrameRecognizerPythonInterface::Initialize();
+  ScriptedCommandPythonInterface::Initialize();
 }
 
 void ScriptInterpreterPythonInterfaces::Terminate() {
@@ -43,4 +44,5 @@ void ScriptInterpreterPythonInterfaces::Terminate() {
   ScriptedThreadPythonInterface::Terminate();
   ScriptedFramePythonInterface::Terminate();
   ScriptedStackFrameRecognizerPythonInterface::Terminate();
+  ScriptedCommandPythonInterface::Terminate();
 }
diff --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
index 03d747e63a592..7ddf7ec0e5ae5 100644
--- 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
+++ 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h
@@ -14,6 +14,7 @@
 
 #include "OperatingSystemPythonInterface.h"
 #include "ScriptedBreakpointPythonInterface.h"
+#include "ScriptedCommandPythonInterface.h"
 #include "ScriptedFrameProviderPythonInterface.h"
 #include "ScriptedFramePythonInterface.h"
 #include "ScriptedHookPythonInterface.h"
diff --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedCommandPythonInterface.cpp
 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedCommandPythonInterface.cpp
new file mode 100644
index 0000000000000..d73ddb9f39327
--- /dev/null
+++ 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedCommandPythonInterface.cpp
@@ -0,0 +1,247 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "../lldb-python.h"
+
+#include "lldb/API/SBCommandReturnObject.h"
+#include "lldb/Core/PluginManager.h"
+#include "lldb/Interpreter/CommandReturnObject.h"
+#include "lldb/Target/ExecutionContext.h"
+#include "lldb/Utility/Args.h"
+#include "lldb/Utility/ScriptedMetadata.h"
+#include "lldb/lldb-enumerations.h"
+
+#include "../SWIGPythonBridge.h"
+#include "../ScriptInterpreterPythonImpl.h"
+#include "ScriptedCommandPythonInterface.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::python;
+using Locker = ScriptInterpreterPythonImpl::Locker;
+
+ScriptedCommandPythonInterface::ScriptedCommandPythonInterface(
+    ScriptInterpreterPythonImpl &interpreter)
+    : ScriptedCommandInterface(), ScriptedPythonInterface(interpreter) {}
+
+llvm::Expected<StructuredData::GenericSP>
+ScriptedCommandPythonInterface::CreatePluginObject(
+    llvm::StringRef class_name, lldb::DebuggerSP debugger_sp) {
+  if (class_name.empty())
+    return llvm::createStringError("empty class name");
+
+  if (!debugger_sp)
+    return llvm::createStringError("invalid Debugger pointer");
+
+  m_debugger_sp = debugger_sp;
+  ScriptedMetadata scripted_metadata(class_name,
+                                     StructuredData::DictionarySP());
+  return ScriptedPythonInterface::CreatePluginObject(
+      scripted_metadata, /*script_obj=*/nullptr, debugger_sp);
+}
+
+bool ScriptedCommandPythonInterface::RunRawCommand(
+    llvm::StringRef args, ScriptedCommandSynchronicity synchronicity,
+    CommandReturnObject &cmd_retobj, Status &error,
+    const ExecutionContext &exe_ctx) {
+  lldb::DebuggerSP debugger_sp = m_debugger_sp;
+  if (!debugger_sp) {
+    error = Status::FromErrorString("invalid Debugger pointer");
+    return false;
+  }
+  lldb::ExecutionContextRefSP exe_ctx_ref_sp(new ExecutionContextRef(exe_ctx));
+
+  // Outer Locker sets up the session (with conditional NoSTDIN for
+  // interactive commands and TearDownSession on exit) that Dispatch's inner
+  // Locker doesn't provide. Nested locks are safe.
+  Locker py_lock(&m_interpreter,
+                 Locker::AcquireLock | Locker::InitSession |
+                     (cmd_retobj.GetInteractive() ? 0 : Locker::NoSTDIN),
+                 Locker::FreeLock | Locker::TearDownSession);
+  ScriptInterpreterPythonImpl::SynchronicityHandler synch_handler(
+      debugger_sp, synchronicity);
+
+  std::string args_str = args.str();
+  Dispatch("__call__", error, debugger_sp, args_str.c_str(), exe_ctx_ref_sp,
+           &cmd_retobj);
+
+  if (!error.Success() || cmd_retobj.GetStatus() == eReturnStatusFailed)
+    return false;
+
+  return true;
+}
+
+bool ScriptedCommandPythonInterface::RunParsedCommand(
+    Args &args, ScriptedCommandSynchronicity synchronicity,
+    CommandReturnObject &cmd_retobj, Status &error,
+    const ExecutionContext &exe_ctx) {
+  lldb::DebuggerSP debugger_sp = m_debugger_sp;
+  if (!debugger_sp) {
+    error = Status::FromErrorString("invalid Debugger pointer");
+    return false;
+  }
+  lldb::ExecutionContextRefSP exe_ctx_ref_sp(new ExecutionContextRef(exe_ctx));
+
+  Locker py_lock(&m_interpreter,
+                 Locker::AcquireLock | Locker::InitSession |
+                     (cmd_retobj.GetInteractive() ? 0 : Locker::NoSTDIN),
+                 Locker::FreeLock | Locker::TearDownSession);
+  ScriptInterpreterPythonImpl::SynchronicityHandler synch_handler(
+      debugger_sp, synchronicity);
+
+  StructuredData::ArraySP args_arr_sp(new StructuredData::Array());
+  for (const Args::ArgEntry &entry : args)
+    args_arr_sp->AddStringItem(entry.ref());
+  StructuredDataImpl args_impl(args_arr_sp);
+
+  Dispatch("__call__", error, debugger_sp, args_impl, exe_ctx_ref_sp,
+           &cmd_retobj);
+
+  if (!error.Success() || cmd_retobj.GetStatus() == eReturnStatusFailed)
+    return false;
+
+  return true;
+}
+
+std::optional<std::string>
+ScriptedCommandPythonInterface::GetRepeatCommand(Args &args) {
+  std::string command;
+  args.GetQuotedCommandString(command);
+  Status error;
+  StructuredData::ObjectSP obj =
+      Dispatch("get_repeat_command", error, command.c_str());
+  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
+                                                    error))
+    return {};
+  return obj->GetStringValue().str();
+}
+
+StructuredData::DictionarySP
+ScriptedCommandPythonInterface::HandleArgumentCompletion(
+    std::vector<llvm::StringRef> &args, size_t args_pos, size_t char_in_arg) {
+  Status error;
+  StructuredData::ObjectSP obj = Dispatch("handle_argument_completion", error,
+                                          args, args_pos, char_in_arg);
+  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
+                                                    error))
+    return {};
+  StructuredData::DictionarySP dict_sp(new StructuredData::Dictionary(obj));
+  if (dict_sp->GetType() == lldb::eStructuredDataTypeInvalid)
+    return {};
+  return dict_sp;
+}
+
+StructuredData::DictionarySP
+ScriptedCommandPythonInterface::HandleOptionArgumentCompletion(
+    llvm::StringRef &long_option, size_t char_in_arg) {
+  Status error;
+  std::string long_option_str = long_option.str();
+  StructuredData::ObjectSP obj =
+      Dispatch("handle_option_argument_completion", error,
+               long_option_str.c_str(), char_in_arg);
+  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
+                                                    error))
+    return {};
+
+  // A boolean return means: True means completion handled but no
+  // completions; False means completion not handled (fall back to default).
+  if (obj->GetType() == lldb::eStructuredDataTypeBoolean) {
+    if (!obj->GetBooleanValue())
+      return {};
+    StructuredData::DictionarySP dict_sp(new StructuredData::Dictionary());
+    dict_sp->AddBooleanItem("no-completion", true);
+    return dict_sp;
+  }
+
+  StructuredData::DictionarySP dict_sp(new StructuredData::Dictionary(obj));
+  if (dict_sp->GetType() == lldb::eStructuredDataTypeInvalid)
+    return {};
+  return dict_sp;
+}
+
+bool ScriptedCommandPythonInterface::GetShortHelp(std::string &dest) {
+  dest.clear();
+  Status error;
+  StructuredData::ObjectSP obj = Dispatch("get_short_help", error);
+  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
+                                                    error))
+    return false;
+  dest = obj->GetStringValue().str();
+  return !dest.empty();
+}
+
+bool ScriptedCommandPythonInterface::GetLongHelp(std::string &dest) {
+  dest.clear();
+  Status error;
+  StructuredData::ObjectSP obj = Dispatch("get_long_help", error);
+  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
+                                                    error))
+    return false;
+  dest = obj->GetStringValue().str();
+  return !dest.empty();
+}
+
+uint32_t ScriptedCommandPythonInterface::GetFlags() {
+  Status error;
+  StructuredData::ObjectSP obj = Dispatch("get_flags", error);
+  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
+                                                    error))
+    return 0;
+  return static_cast<uint32_t>(obj->GetUnsignedIntegerValue());
+}
+
+StructuredData::ObjectSP
+ScriptedCommandPythonInterface::GetOptionsDefinition() {
+  Status error;
+  return Dispatch("get_options_definition", error);
+}
+
+StructuredData::ObjectSP
+ScriptedCommandPythonInterface::GetArgumentsDefinition() {
+  Status error;
+  return Dispatch("get_args_definition", error);
+}
+
+void ScriptedCommandPythonInterface::OptionParsingStarted() {
+  Status error;
+  Dispatch("option_parsing_started", error);
+}
+
+bool ScriptedCommandPythonInterface::SetOptionValue(ExecutionContext *exe_ctx,
+                                                    llvm::StringRef 
long_option,
+                                                    llvm::StringRef value) {
+  lldb::ExecutionContextRefSP exe_ctx_ref_sp;
+  if (exe_ctx)
+    exe_ctx_ref_sp = std::make_shared<ExecutionContextRef>(exe_ctx);
+  Status error;
+  std::string long_option_str = long_option.str();
+  std::string value_str = value.str();
+  StructuredData::ObjectSP obj =
+      Dispatch("set_option_value", error, exe_ctx_ref_sp,
+               long_option_str.c_str(), value_str.c_str());
+  if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
+                                                    error))
+    return false;
+  return obj->GetBooleanValue();
+}
+
+void ScriptedCommandPythonInterface::Initialize() {
+  const std::vector<llvm::StringRef> ci_usages = {
+      "command script add -c <ClassName> <cmd>",
+      "command script add -p <ClassName> <cmd>"};
+  const std::vector<llvm::StringRef> api_usages = {};
+  PluginManager::RegisterPlugin(
+      GetPluginNameStatic(),
+      "Implement a raw or parsed custom command backed by a Python class.",
+      CreateInstance, eScriptedExtensionScriptedCommand, eScriptLanguagePython,
+      ScriptedInterfaceUsages(ci_usages, api_usages));
+}
+
+void ScriptedCommandPythonInterface::Terminate() {
+  PluginManager::UnregisterPlugin(CreateInstance);
+}
diff --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedCommandPythonInterface.h
 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedCommandPythonInterface.h
new file mode 100644
index 0000000000000..d2bb5386a69ab
--- /dev/null
+++ 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedCommandPythonInterface.h
@@ -0,0 +1,81 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef 
LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDCOMMANDPYTHONINTERFACE_H
+#define 
LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDCOMMANDPYTHONINTERFACE_H
+
+#include "lldb/Interpreter/Interfaces/ScriptedCommandInterface.h"
+
+#include "ScriptedPythonInterface.h"
+namespace lldb_private {
+
+class ScriptedCommandPythonInterface : public ScriptedCommandInterface,
+                                       public ScriptedPythonInterface,
+                                       public PluginInterface {
+public:
+  ScriptedCommandPythonInterface(ScriptInterpreterPythonImpl &interpreter);
+
+  llvm::Expected<StructuredData::GenericSP>
+  CreatePluginObject(llvm::StringRef class_name,
+                     lldb::DebuggerSP debugger_sp) override;
+
+  llvm::SmallVector<AbstractMethodRequirement>
+  GetAbstractMethodRequirements() const override {
+    return llvm::SmallVector<AbstractMethodRequirement>();
+  }
+
+  bool RunRawCommand(llvm::StringRef args,
+                     ScriptedCommandSynchronicity synchronicity,
+                     CommandReturnObject &cmd_retobj, Status &error,
+                     const ExecutionContext &exe_ctx) override;
+
+  bool RunParsedCommand(Args &args, ScriptedCommandSynchronicity synchronicity,
+                        CommandReturnObject &cmd_retobj, Status &error,
+                        const ExecutionContext &exe_ctx) override;
+
+  std::optional<std::string> GetRepeatCommand(Args &args) override;
+
+  StructuredData::DictionarySP
+  HandleArgumentCompletion(std::vector<llvm::StringRef> &args, size_t args_pos,
+                           size_t char_in_arg) override;
+
+  StructuredData::DictionarySP
+  HandleOptionArgumentCompletion(llvm::StringRef &long_option,
+                                 size_t char_in_arg) override;
+
+  bool GetShortHelp(std::string &dest) override;
+
+  bool GetLongHelp(std::string &dest) override;
+
+  uint32_t GetFlags() override;
+
+  StructuredData::ObjectSP GetOptionsDefinition() override;
+
+  StructuredData::ObjectSP GetArgumentsDefinition() override;
+
+  void OptionParsingStarted() override;
+
+  bool SetOptionValue(ExecutionContext *exe_ctx, llvm::StringRef long_option,
+                      llvm::StringRef value) override;
+
+  static void Initialize();
+
+  static void Terminate();
+
+  static llvm::StringRef GetPluginNameStatic() {
+    return "ScriptedCommandPythonInterface";
+  }
+
+  llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
+
+private:
+  lldb::DebuggerSP m_debugger_sp;
+};
+} // namespace lldb_private
+
+#endif // 
LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDCOMMANDPYTHONINTERFACE_H
diff --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
index 61ceb40dd9d32..4658fc948cb1a 100644
--- 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
+++ 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
@@ -8,7 +8,9 @@
 
 #include "../lldb-python.h"
 
+#include "lldb/API/SBDebugger.h"
 #include "lldb/Host/Config.h"
+#include "lldb/Utility/ConstString.h"
 #include "lldb/Utility/Log.h"
 #include "lldb/lldb-enumerations.h"
 
@@ -65,6 +67,21 @@ Event 
*ScriptedPythonInterface::ExtractValueFromPythonObject<Event *>(
   return nullptr;
 }
 
+template <>
+CommandReturnObject *
+ScriptedPythonInterface::ExtractValueFromPythonObject<CommandReturnObject *>(
+    python::PythonObject &p, Status &error) {
+  if (lldb::SBCommandReturnObject *sb_cmd_retobj =
+          reinterpret_cast<lldb::SBCommandReturnObject *>(
+              python::LLDBSWIGPython_CastPyObjectToSBCommandReturnObject(
+                  p.get())))
+    return 
m_interpreter.GetOpaqueTypeFromSBCommandReturnObject(*sb_cmd_retobj);
+  error =
+      Status::FromErrorString("Couldn't cast lldb::SBCommandReturnObject to "
+                              "lldb_private::CommandReturnObject.");
+  return nullptr;
+}
+
 template <>
 lldb::StreamSP
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StreamSP>(
@@ -380,3 +397,41 @@ ScriptedPythonInterface::ExtractValueFromPythonObject<
 
   return static_cast<ValueType>(unmasked | flags);
 }
+
+template <>
+lldb::DebuggerSP
+ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DebuggerSP>(
+    python::PythonObject &p, Status &error) {
+  if (lldb::SBDebugger *sb_dbg = reinterpret_cast<lldb::SBDebugger *>(
+          python::LLDBSWIGPython_CastPyObjectToSBDebugger(p.get())))
+    return m_interpreter.GetOpaqueTypeFromSBDebugger(*sb_dbg);
+  error = Status::FromErrorString(
+      "Couldn't cast lldb::SBDebugger to lldb::DebuggerSP.");
+  return {};
+}
+
+template <>
+std::vector<llvm::StringRef>
+ScriptedPythonInterface::ExtractValueFromPythonObject<
+    std::vector<llvm::StringRef>>(python::PythonObject &p, Status &error) {
+  std::vector<llvm::StringRef> result;
+  python::PythonList list(python::PyRefType::Borrowed, p.get());
+  if (!list.IsValid()) {
+    error = Status::FromErrorString(
+        "Couldn't extract std::vector<llvm::StringRef>: not a Python list.");
+    return result;
+  }
+
+  const uint32_t size = list.GetSize();
+  result.reserve(size);
+  for (uint32_t i = 0; i < size; ++i) {
+    python::PythonString item(python::PyRefType::Borrowed,
+                              list.GetItemAtIndex(i).get());
+    if (!item.IsValid())
+      continue;
+    // Persist the backing storage in ConstString's static pool so the
+    // returned StringRefs outlive the Python list.
+    result.push_back(ConstString(item.GetString()).GetStringRef());
+  }
+  return result;
+}
diff --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
index aaa0b6a0f7a59..eee13783c74bf 100644
--- 
a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
+++ 
b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
@@ -15,6 +15,7 @@
 #include <type_traits>
 #include <utility>
 
+#include "lldb/API/SBCommandReturnObject.h"
 #include "lldb/Interpreter/Interfaces/ScriptedInterface.h"
 #include "lldb/Utility/DataBufferHeap.h"
 
@@ -668,6 +669,22 @@ class ScriptedPythonInterface : virtual public 
ScriptedInterface {
     return python::SWIGBridge::ToSWIGWrapper(arg);
   }
 
+  python::PythonObject Transform(lldb::DebuggerSP arg) {
+    return python::SWIGBridge::ToSWIGWrapper(arg);
+  }
+
+  python::PythonObject Transform(const std::vector<llvm::StringRef> &arg) {
+    python::PythonList list(python::PyInitialValue::Empty);
+    for (llvm::StringRef s : arg)
+      list.AppendItem(python::PythonString(s));
+    return list;
+  }
+
+  python::ScopedPythonObject<lldb::SBCommandReturnObject>
+  Transform(CommandReturnObject *arg) {
+    return python::SWIGBridge::ToSWIGWrapper(*arg);
+  }
+
   template <typename T, typename U>
   void ReverseTransform(T &original_arg, U transformed_arg, Status &error) {
     // If U is not a PythonObject, don't touch it!
@@ -708,6 +725,15 @@ class ScriptedPythonInterface : virtual public 
ScriptedInterface {
     ReverseTransform(original_arg, transformed_arg, error);
   }
 
+  // ScopedPythonObject is non-copyable — passing it through the generic
+  // TransformBack would trigger the deleted copy ctor. It manages its own
+  // cleanup via the destructor when the transformed-args tuple destructs, so
+  // there is nothing to reverse-transform back into the original arg.
+  template <typename T, typename SB>
+  void TransformBack(T &original_arg,
+                     python::ScopedPythonObject<SB> &transformed_arg,
+                     Status &error) {}
+
   template <std::size_t... I, typename... Ts, typename... Us>
   bool ReassignPtrsOrRefsArgs(std::tuple<Ts...> &original_args,
                               std::tuple<Us...> &transformed_args,
@@ -846,6 +872,16 @@ std::optional<lldb::ValueType>
 ScriptedPythonInterface::ExtractValueFromPythonObject<
     std::optional<lldb::ValueType>>(python::PythonObject &p, Status &error);
 
+template <>
+lldb::DebuggerSP
+ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DebuggerSP>(
+    python::PythonObject &p, Status &error);
+
+template <>
+std::vector<llvm::StringRef>
+ScriptedPythonInterface::ExtractValueFromPythonObject<
+    std::vector<llvm::StringRef>>(python::PythonObject &p, Status &error);
+
 } // namespace lldb_private
 
 #endif // 
LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDPYTHONINTERFACE_H
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h 
b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
index 07e0da1dcf70d..9c5391e754396 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
@@ -39,7 +39,7 @@ python::PythonObject ToSWIGHelper(void *obj, swig_type_info 
*info);
 
 /// A class that automatically clears an SB object when it goes out of scope.
 /// Use for cases where the SB object points to a temporary/unowned entity.
-template <typename T> class ScopedPythonObject : PythonObject {
+template <typename T> class ScopedPythonObject : public PythonObject {
 public:
   ScopedPythonObject(T *sb, swig_type_info *info)
       : PythonObject(ToSWIGHelper(sb, info)), m_sb(sb) {}
@@ -143,11 +143,6 @@ class SWIGBridge {
                                         const char *session_dictionary_name,
                                         const lldb::ValueObjectSP &valobj_sp);
 
-  static python::PythonObject
-  LLDBSwigPythonCreateCommandObject(const char *python_class_name,
-                                    const char *session_dictionary_name,
-                                    lldb::DebuggerSP debugger_sp);
-
   static size_t LLDBSwigPython_CalculateNumChildren(PyObject *implementor,
                                                     uint32_t max);
 
@@ -176,30 +171,6 @@ class SWIGBridge {
                             lldb_private::CommandReturnObject &cmd_retobj,
                             lldb::ExecutionContextRefSP exe_ctx_ref_sp);
 
-  static bool
-  LLDBSwigPythonCallCommandObject(PyObject *implementor,
-                                  lldb::DebuggerSP debugger, const char *args,
-                                  lldb_private::CommandReturnObject 
&cmd_retobj,
-                                  lldb::ExecutionContextRefSP exe_ctx_ref_sp);
-  static bool LLDBSwigPythonCallParsedCommandObject(
-      PyObject *implementor, lldb::DebuggerSP debugger,
-      StructuredDataImpl &args_impl,
-      lldb_private::CommandReturnObject &cmd_retobj,
-      lldb::ExecutionContextRefSP exe_ctx_ref_sp);
-
-  static std::optional<std::string>
-  LLDBSwigPythonGetRepeatCommandForScriptedCommand(PyObject *implementor,
-                                                   std::string &command);
-
-  static StructuredData::DictionarySP
-  LLDBSwigPythonHandleArgumentCompletionForScriptedCommand(
-      PyObject *implementor, std::vector<llvm::StringRef> &args_impl,
-      size_t args_pos, size_t pos_in_arg);
-
-  static StructuredData::DictionarySP
-  LLDBSwigPythonHandleOptionArgumentCompletionForScriptedCommand(
-      PyObject *implementor, llvm::StringRef &long_option, size_t pos_in_arg);
-
   static bool LLDBSwigPythonCallModuleInit(const char *python_module_name,
                                            const char *session_dictionary_name,
                                            lldb::DebuggerSP debugger);
@@ -247,6 +218,8 @@ void 
*LLDBSWIGPython_CastPyObjectToSBBreakpointLocation(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBAttachInfo(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBLaunchInfo(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBError(PyObject *data);
+void *LLDBSWIGPython_CastPyObjectToSBCommandReturnObject(PyObject *data);
+void *LLDBSWIGPython_CastPyObjectToSBDebugger(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBEvent(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBStream(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBThread(PyObject *data);
diff --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp 
b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
index 7b1bd9d8411c5..bb8871e234bc1 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -289,6 +289,9 @@ llvm::Expected<std::string> 
ScriptInterpreterPython::ExtensionToImportPath(
     return "lldb.plugins.scripted_process";
   case eScriptedExtensionScriptedStackFrameRecognizer:
     return "lldb.plugins.scripted_stackframe_recognizer";
+  case eScriptedExtensionScriptedCommand:
+  case eScriptedExtensionParsedCommand:
+    return "lldb.plugins.scripted_command";
   case eScriptedExtensionInvalid:
     return llvm::createStringError("invalid extension name");
   }
@@ -2003,6 +2006,11 @@ 
ScriptInterpreterPythonImpl::CreateScriptedStackFrameRecognizerInterface() {
   return std::make_shared<ScriptedStackFrameRecognizerPythonInterface>(*this);
 }
 
+ScriptedCommandInterfaceSP
+ScriptInterpreterPythonImpl::CreateScriptedCommandInterface() {
+  return std::make_shared<ScriptedCommandPythonInterface>(*this);
+}
+
 ScriptedThreadInterfaceSP
 ScriptInterpreterPythonImpl::CreateScriptedThreadInterface() {
   return std::make_shared<ScriptedThreadPythonInterface>(*this);
@@ -2117,28 +2125,6 @@ 
ScriptInterpreterPythonImpl::CreateSyntheticScriptedProvider(
       new StructuredPythonObject(std::move(ret_val)));
 }
 
-StructuredData::GenericSP
-ScriptInterpreterPythonImpl::CreateScriptCommandObject(const char *class_name) 
{
-  DebuggerSP debugger_sp(m_debugger.shared_from_this());
-
-  if (class_name == nullptr || class_name[0] == '\0')
-    return StructuredData::GenericSP();
-
-  if (!debugger_sp.get())
-    return StructuredData::GenericSP();
-
-  Locker py_lock(this,
-                 Locker::AcquireLock | Locker::InitSession | Locker::NoSTDIN);
-  PythonObject ret_val = SWIGBridge::LLDBSwigPythonCreateCommandObject(
-      class_name, m_dictionary_name.c_str(), debugger_sp);
-
-  if (ret_val.IsValid())
-    return StructuredData::GenericSP(
-        new StructuredPythonObject(std::move(ret_val)));
-  else
-    return {};
-}
-
 bool ScriptInterpreterPythonImpl::GenerateTypeScriptFunction(
     const char *oneliner, std::string &output, const void *name_token) {
   StringList input;
@@ -2987,166 +2973,6 @@ bool ScriptInterpreterPythonImpl::RunScriptBasedCommand(
   return ret_val;
 }
 
-bool ScriptInterpreterPythonImpl::RunScriptBasedCommand(
-    StructuredData::GenericSP impl_obj_sp, llvm::StringRef args,
-    ScriptedCommandSynchronicity synchronicity,
-    lldb_private::CommandReturnObject &cmd_retobj, Status &error,
-    const lldb_private::ExecutionContext &exe_ctx) {
-  if (!impl_obj_sp || !impl_obj_sp->IsValid()) {
-    error = Status::FromErrorString("no function to execute");
-    return false;
-  }
-
-  lldb::DebuggerSP debugger_sp = m_debugger.shared_from_this();
-  lldb::ExecutionContextRefSP exe_ctx_ref_sp(new ExecutionContextRef(exe_ctx));
-
-  if (!debugger_sp.get()) {
-    error = Status::FromErrorString("invalid Debugger pointer");
-    return false;
-  }
-
-  bool ret_val = false;
-
-  {
-    Locker py_lock(this,
-                   Locker::AcquireLock | Locker::InitSession |
-                       (cmd_retobj.GetInteractive() ? 0 : Locker::NoSTDIN),
-                   Locker::FreeLock | Locker::TearDownSession);
-
-    SynchronicityHandler synch_handler(debugger_sp, synchronicity);
-
-    std::string args_str = args.str();
-    ret_val = SWIGBridge::LLDBSwigPythonCallCommandObject(
-        static_cast<PyObject *>(impl_obj_sp->GetValue()), debugger_sp,
-        args_str.c_str(), cmd_retobj, exe_ctx_ref_sp);
-  }
-
-  if (!ret_val)
-    error = Status::FromErrorString("unable to execute script function");
-  else if (cmd_retobj.GetStatus() == eReturnStatusFailed)
-    return false;
-
-  error.Clear();
-  return ret_val;
-}
-
-bool ScriptInterpreterPythonImpl::RunScriptBasedParsedCommand(
-    StructuredData::GenericSP impl_obj_sp, Args &args,
-    ScriptedCommandSynchronicity synchronicity,
-    lldb_private::CommandReturnObject &cmd_retobj, Status &error,
-    const lldb_private::ExecutionContext &exe_ctx) {
-  if (!impl_obj_sp || !impl_obj_sp->IsValid()) {
-    error = Status::FromErrorString("no function to execute");
-    return false;
-  }
-
-  lldb::DebuggerSP debugger_sp = m_debugger.shared_from_this();
-  lldb::ExecutionContextRefSP exe_ctx_ref_sp(new ExecutionContextRef(exe_ctx));
-
-  if (!debugger_sp.get()) {
-    error = Status::FromErrorString("invalid Debugger pointer");
-    return false;
-  }
-
-  bool ret_val = false;
-
-  {
-    Locker py_lock(this,
-                   Locker::AcquireLock | Locker::InitSession |
-                       (cmd_retobj.GetInteractive() ? 0 : Locker::NoSTDIN),
-                   Locker::FreeLock | Locker::TearDownSession);
-
-    SynchronicityHandler synch_handler(debugger_sp, synchronicity);
-
-    StructuredData::ArraySP args_arr_sp(new StructuredData::Array());
-
-    for (const Args::ArgEntry &entry : args) {
-      args_arr_sp->AddStringItem(entry.ref());
-    }
-    StructuredDataImpl args_impl(args_arr_sp);
-
-    ret_val = SWIGBridge::LLDBSwigPythonCallParsedCommandObject(
-        static_cast<PyObject *>(impl_obj_sp->GetValue()), debugger_sp,
-        args_impl, cmd_retobj, exe_ctx_ref_sp);
-  }
-
-  if (!ret_val)
-    error = Status::FromErrorString("unable to execute script function");
-  else if (cmd_retobj.GetStatus() == eReturnStatusFailed)
-    return false;
-
-  error.Clear();
-  return ret_val;
-}
-
-std::optional<std::string>
-ScriptInterpreterPythonImpl::GetRepeatCommandForScriptedCommand(
-    StructuredData::GenericSP impl_obj_sp, Args &args) {
-  if (!impl_obj_sp || !impl_obj_sp->IsValid())
-    return std::nullopt;
-
-  lldb::DebuggerSP debugger_sp = m_debugger.shared_from_this();
-
-  if (!debugger_sp.get())
-    return std::nullopt;
-
-  std::optional<std::string> ret_val;
-
-  {
-    Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN,
-                   Locker::FreeLock);
-
-    StructuredData::ArraySP args_arr_sp(new StructuredData::Array());
-
-    // For scripting commands, we send the command string:
-    std::string command;
-    args.GetQuotedCommandString(command);
-    ret_val = SWIGBridge::LLDBSwigPythonGetRepeatCommandForScriptedCommand(
-        static_cast<PyObject *>(impl_obj_sp->GetValue()), command);
-  }
-  return ret_val;
-}
-
-StructuredData::DictionarySP
-ScriptInterpreterPythonImpl::HandleArgumentCompletionForScriptedCommand(
-    StructuredData::GenericSP impl_obj_sp, std::vector<llvm::StringRef> &args,
-    size_t args_pos, size_t char_in_arg) {
-  StructuredData::DictionarySP completion_dict_sp;
-  if (!impl_obj_sp || !impl_obj_sp->IsValid())
-    return completion_dict_sp;
-
-  {
-    Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN,
-                   Locker::FreeLock);
-
-    completion_dict_sp =
-        SWIGBridge::LLDBSwigPythonHandleArgumentCompletionForScriptedCommand(
-            static_cast<PyObject *>(impl_obj_sp->GetValue()), args, args_pos,
-            char_in_arg);
-  }
-  return completion_dict_sp;
-}
-
-StructuredData::DictionarySP
-ScriptInterpreterPythonImpl::HandleOptionArgumentCompletionForScriptedCommand(
-    StructuredData::GenericSP impl_obj_sp, llvm::StringRef &long_option,
-    size_t char_in_arg) {
-  StructuredData::DictionarySP completion_dict_sp;
-  if (!impl_obj_sp || !impl_obj_sp->IsValid())
-    return completion_dict_sp;
-
-  {
-    Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN,
-                   Locker::FreeLock);
-
-    completion_dict_sp = SWIGBridge::
-        LLDBSwigPythonHandleOptionArgumentCompletionForScriptedCommand(
-            static_cast<PyObject *>(impl_obj_sp->GetValue()), long_option,
-            char_in_arg);
-  }
-  return completion_dict_sp;
-}
-
 /// In Python, a special attribute __doc__ contains the docstring for an object
 /// (function, method, class, ...) if any is defined Otherwise, the attribute's
 /// value is None.
@@ -3180,322 +3006,6 @@ bool 
ScriptInterpreterPythonImpl::GetDocumentationForItem(const char *item,
   return false;
 }
 
-bool ScriptInterpreterPythonImpl::GetShortHelpForCommandObject(
-    StructuredData::GenericSP cmd_obj_sp, std::string &dest) {
-  dest.clear();
-
-  Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, 
Locker::FreeLock);
-
-  if (!cmd_obj_sp)
-    return false;
-
-  PythonObject implementor(PyRefType::Borrowed,
-                           (PyObject *)cmd_obj_sp->GetValue());
-
-  if (!implementor.IsAllocated())
-    return false;
-
-  llvm::Expected<PythonObject> expected_py_return =
-      implementor.CallMethod("get_short_help");
-
-  if (!expected_py_return) {
-    llvm::consumeError(expected_py_return.takeError());
-    return false;
-  }
-
-  PythonObject py_return = std::move(expected_py_return.get());
-
-  if (py_return.IsAllocated() && PythonString::Check(py_return.get())) {
-    PythonString py_string(PyRefType::Borrowed, py_return.get());
-    llvm::StringRef return_data(py_string.GetString());
-    dest.assign(return_data.data(), return_data.size());
-    return true;
-  }
-
-  return false;
-}
-
-uint32_t ScriptInterpreterPythonImpl::GetFlagsForCommandObject(
-    StructuredData::GenericSP cmd_obj_sp) {
-  uint32_t result = 0;
-
-  Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, 
Locker::FreeLock);
-
-  static char callee_name[] = "get_flags";
-
-  if (!cmd_obj_sp)
-    return result;
-
-  PythonObject implementor(PyRefType::Borrowed,
-                           (PyObject *)cmd_obj_sp->GetValue());
-
-  if (!implementor.IsAllocated())
-    return result;
-
-  PythonObject pmeth(PyRefType::Owned,
-                     PyObject_GetAttrString(implementor.get(), callee_name));
-
-  if (PyErr_Occurred())
-    PyErr_Clear();
-
-  if (!pmeth.IsAllocated())
-    return result;
-
-  if (PyCallable_Check(pmeth.get()) == 0) {
-    if (PyErr_Occurred())
-      PyErr_Clear();
-    return result;
-  }
-
-  if (PyErr_Occurred())
-    PyErr_Clear();
-
-  long long py_return = unwrapOrSetPythonException(
-      As<long long>(implementor.CallMethod(callee_name)));
-
-  // if it fails, print the error but otherwise go on
-  if (PyErr_Occurred()) {
-    PyErr_Print();
-    PyErr_Clear();
-  } else {
-    result = py_return;
-  }
-
-  return result;
-}
-
-StructuredData::ObjectSP
-ScriptInterpreterPythonImpl::GetOptionsForCommandObject(
-    StructuredData::GenericSP cmd_obj_sp) {
-  StructuredData::ObjectSP result = {};
-
-  Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, 
Locker::FreeLock);
-
-  static char callee_name[] = "get_options_definition";
-
-  if (!cmd_obj_sp)
-    return result;
-
-  PythonObject implementor(PyRefType::Borrowed,
-                           (PyObject *)cmd_obj_sp->GetValue());
-
-  if (!implementor.IsAllocated())
-    return result;
-
-  PythonObject pmeth(PyRefType::Owned,
-                     PyObject_GetAttrString(implementor.get(), callee_name));
-
-  if (PyErr_Occurred())
-    PyErr_Clear();
-
-  if (!pmeth.IsAllocated())
-    return result;
-
-  if (PyCallable_Check(pmeth.get()) == 0) {
-    if (PyErr_Occurred())
-      PyErr_Clear();
-    return result;
-  }
-
-  if (PyErr_Occurred())
-    PyErr_Clear();
-
-  PythonDictionary py_return = unwrapOrSetPythonException(
-      As<PythonDictionary>(implementor.CallMethod(callee_name)));
-
-  // if it fails, print the error but otherwise go on
-  if (PyErr_Occurred()) {
-    PyErr_Print();
-    PyErr_Clear();
-    return {};
-  }
-  return py_return.CreateStructuredObject();
-}
-
-StructuredData::ObjectSP
-ScriptInterpreterPythonImpl::GetArgumentsForCommandObject(
-    StructuredData::GenericSP cmd_obj_sp) {
-  StructuredData::ObjectSP result = {};
-
-  Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, 
Locker::FreeLock);
-
-  static char callee_name[] = "get_args_definition";
-
-  if (!cmd_obj_sp)
-    return result;
-
-  PythonObject implementor(PyRefType::Borrowed,
-                           (PyObject *)cmd_obj_sp->GetValue());
-
-  if (!implementor.IsAllocated())
-    return result;
-
-  PythonObject pmeth(PyRefType::Owned,
-                     PyObject_GetAttrString(implementor.get(), callee_name));
-
-  if (PyErr_Occurred())
-    PyErr_Clear();
-
-  if (!pmeth.IsAllocated())
-    return result;
-
-  if (PyCallable_Check(pmeth.get()) == 0) {
-    if (PyErr_Occurred())
-      PyErr_Clear();
-    return result;
-  }
-
-  if (PyErr_Occurred())
-    PyErr_Clear();
-
-  PythonList py_return = unwrapOrSetPythonException(
-      As<PythonList>(implementor.CallMethod(callee_name)));
-
-  // if it fails, print the error but otherwise go on
-  if (PyErr_Occurred()) {
-    PyErr_Print();
-    PyErr_Clear();
-    return {};
-  }
-  return py_return.CreateStructuredObject();
-}
-
-void ScriptInterpreterPythonImpl::OptionParsingStartedForCommandObject(
-    StructuredData::GenericSP cmd_obj_sp) {
-
-  Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, 
Locker::FreeLock);
-
-  static char callee_name[] = "option_parsing_started";
-
-  if (!cmd_obj_sp)
-    return;
-
-  PythonObject implementor(PyRefType::Borrowed,
-                           (PyObject *)cmd_obj_sp->GetValue());
-
-  if (!implementor.IsAllocated())
-    return;
-
-  PythonObject pmeth(PyRefType::Owned,
-                     PyObject_GetAttrString(implementor.get(), callee_name));
-
-  if (PyErr_Occurred())
-    PyErr_Clear();
-
-  if (!pmeth.IsAllocated())
-    return;
-
-  if (PyCallable_Check(pmeth.get()) == 0) {
-    if (PyErr_Occurred())
-      PyErr_Clear();
-    return;
-  }
-
-  if (PyErr_Occurred())
-    PyErr_Clear();
-
-  // option_parsing_starting doesn't return anything, ignore anything but
-  // python errors.
-  unwrapOrSetPythonException(As<bool>(implementor.CallMethod(callee_name)));
-
-  // if it fails, print the error but otherwise go on
-  if (PyErr_Occurred()) {
-    PyErr_Print();
-    PyErr_Clear();
-    return;
-  }
-}
-
-bool ScriptInterpreterPythonImpl::SetOptionValueForCommandObject(
-    StructuredData::GenericSP cmd_obj_sp, ExecutionContext *exe_ctx,
-    llvm::StringRef long_option, llvm::StringRef value) {
-  StructuredData::ObjectSP result = {};
-
-  Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, 
Locker::FreeLock);
-
-  static char callee_name[] = "set_option_value";
-
-  if (!cmd_obj_sp)
-    return false;
-
-  PythonObject implementor(PyRefType::Borrowed,
-                           (PyObject *)cmd_obj_sp->GetValue());
-
-  if (!implementor.IsAllocated())
-    return false;
-
-  PythonObject pmeth(PyRefType::Owned,
-                     PyObject_GetAttrString(implementor.get(), callee_name));
-
-  if (PyErr_Occurred())
-    PyErr_Clear();
-
-  if (!pmeth.IsAllocated())
-    return false;
-
-  if (PyCallable_Check(pmeth.get()) == 0) {
-    if (PyErr_Occurred())
-      PyErr_Clear();
-    return false;
-  }
-
-  if (PyErr_Occurred())
-    PyErr_Clear();
-
-  lldb::ExecutionContextRefSP exe_ctx_ref_sp;
-  if (exe_ctx)
-    exe_ctx_ref_sp = std::make_shared<ExecutionContextRef>(exe_ctx);
-  PythonObject ctx_ref_obj = SWIGBridge::ToSWIGWrapper(exe_ctx_ref_sp);
-
-  bool py_return = unwrapOrSetPythonException(As<bool>(
-      implementor.CallMethod(callee_name, ctx_ref_obj,
-                             long_option.str().c_str(), value.str().c_str())));
-
-  // if it fails, print the error but otherwise go on
-  if (PyErr_Occurred()) {
-    PyErr_Print();
-    PyErr_Clear();
-    return false;
-  }
-  return py_return;
-}
-
-bool ScriptInterpreterPythonImpl::GetLongHelpForCommandObject(
-    StructuredData::GenericSP cmd_obj_sp, std::string &dest) {
-  dest.clear();
-
-  Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, 
Locker::FreeLock);
-
-  if (!cmd_obj_sp)
-    return false;
-
-  PythonObject implementor(PyRefType::Borrowed,
-                           (PyObject *)cmd_obj_sp->GetValue());
-
-  if (!implementor.IsAllocated())
-    return false;
-
-  llvm::Expected<PythonObject> expected_py_return =
-      implementor.CallMethod("get_long_help");
-
-  if (!expected_py_return) {
-    llvm::consumeError(expected_py_return.takeError());
-    return false;
-  }
-
-  PythonObject py_return = std::move(expected_py_return.get());
-
-  bool got_string = false;
-  if (py_return.IsAllocated() && PythonString::Check(py_return.get())) {
-    PythonString str(PyRefType::Borrowed, py_return.get());
-    llvm::StringRef str_data(str.GetString());
-    dest.assign(str_data.data(), str_data.size());
-    got_string = true;
-  }
-
-  return got_string;
-}
-
 std::unique_ptr<ScriptInterpreterLocker>
 ScriptInterpreterPythonImpl::AcquireInterpreterLock() {
   std::unique_ptr<ScriptInterpreterLocker> py_lock(new Locker(
diff --git 
a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h 
b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
index d8a817198253f..51b373afbb9f4 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
@@ -70,9 +70,6 @@ class ScriptInterpreterPythonImpl : public 
ScriptInterpreterPython {
   CreateSyntheticScriptedProvider(const char *class_name,
                                   lldb::ValueObjectSP valobj) override;
 
-  StructuredData::GenericSP
-  CreateScriptCommandObject(const char *class_name) override;
-
   StructuredData::ObjectSP
   CreateStructuredDataFromScriptObject(ScriptObject obj) override;
 
@@ -86,6 +83,8 @@ class ScriptInterpreterPythonImpl : public 
ScriptInterpreterPython {
   lldb::ScriptedStackFrameRecognizerInterfaceSP
   CreateScriptedStackFrameRecognizerInterface() override;
 
+  lldb::ScriptedCommandInterfaceSP CreateScriptedCommandInterface() override;
+
   lldb::ScriptedThreadInterfaceSP CreateScriptedThreadInterface() override;
 
   lldb::ScriptedFrameInterfaceSP CreateScriptedFrameInterface() override;
@@ -137,30 +136,6 @@ class ScriptInterpreterPythonImpl : public 
ScriptInterpreterPython {
                         Status &error,
                         const lldb_private::ExecutionContext &exe_ctx) 
override;
 
-  bool RunScriptBasedCommand(
-      StructuredData::GenericSP impl_obj_sp, llvm::StringRef args,
-      ScriptedCommandSynchronicity synchronicity,
-      lldb_private::CommandReturnObject &cmd_retobj, Status &error,
-      const lldb_private::ExecutionContext &exe_ctx) override;
-
-  bool RunScriptBasedParsedCommand(
-      StructuredData::GenericSP impl_obj_sp, Args &args,
-      ScriptedCommandSynchronicity synchronicity,
-      lldb_private::CommandReturnObject &cmd_retobj, Status &error,
-      const lldb_private::ExecutionContext &exe_ctx) override;
-
-  std::optional<std::string>
-  GetRepeatCommandForScriptedCommand(StructuredData::GenericSP impl_obj_sp,
-                                     Args &args) override;
-
-  StructuredData::DictionarySP HandleArgumentCompletionForScriptedCommand(
-      StructuredData::GenericSP impl_obj_sp, std::vector<llvm::StringRef> 
&args,
-      size_t args_pos, size_t char_in_arg) override;
-
-  StructuredData::DictionarySP 
HandleOptionArgumentCompletionForScriptedCommand(
-      StructuredData::GenericSP impl_obj_sp, llvm::StringRef &long_options,
-      size_t char_in_arg) override;
-
   Status GenerateFunction(const char *signature, const StringList &input,
                           bool is_callback) override;
 
@@ -183,29 +158,6 @@ class ScriptInterpreterPythonImpl : public 
ScriptInterpreterPython {
 
   bool GetDocumentationForItem(const char *item, std::string &dest) override;
 
-  bool GetShortHelpForCommandObject(StructuredData::GenericSP cmd_obj_sp,
-                                    std::string &dest) override;
-
-  uint32_t
-  GetFlagsForCommandObject(StructuredData::GenericSP cmd_obj_sp) override;
-
-  bool GetLongHelpForCommandObject(StructuredData::GenericSP cmd_obj_sp,
-                                   std::string &dest) override;
-
-  StructuredData::ObjectSP
-  GetOptionsForCommandObject(StructuredData::GenericSP cmd_obj_sp) override;
-
-  StructuredData::ObjectSP
-  GetArgumentsForCommandObject(StructuredData::GenericSP cmd_obj_sp) override;
-
-  bool SetOptionValueForCommandObject(StructuredData::GenericSP cmd_obj_sp,
-                                      ExecutionContext *exe_ctx,
-                                      llvm::StringRef long_option,
-                                      llvm::StringRef value) override;
-
-  void OptionParsingStartedForCommandObject(
-      StructuredData::GenericSP cmd_obj_sp) override;
-
   bool CheckObjectExists(const char *name) override {
     if (!name || !name[0])
       return false;
diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp 
b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
index c9298191ec3c1..808bb6157e5b8 100644
--- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
+++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
@@ -73,13 +73,6 @@ 
lldb_private::python::SWIGBridge::LLDBSwigPythonCreateSyntheticProvider(
   return python::PythonObject();
 }
 
-python::PythonObject
-lldb_private::python::SWIGBridge::LLDBSwigPythonCreateCommandObject(
-    const char *python_class_name, const char *session_dictionary_name,
-    lldb::DebuggerSP debugger_sp) {
-  return python::PythonObject();
-}
-
 size_t lldb_private::python::SWIGBridge::LLDBSwigPython_CalculateNumChildren(
     PyObject *implementor, uint32_t max) {
   return 0;
@@ -126,6 +119,16 @@ 
lldb_private::python::LLDBSWIGPython_CastPyObjectToSBError(PyObject *data) {
   return nullptr;
 }
 
+void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBCommandReturnObject(
+    PyObject *data) {
+  return nullptr;
+}
+
+void *
+lldb_private::python::LLDBSWIGPython_CastPyObjectToSBDebugger(PyObject *data) {
+  return nullptr;
+}
+
 void *
 lldb_private::python::LLDBSWIGPython_CastPyObjectToSBEvent(PyObject *data) {
   return nullptr;
@@ -207,40 +210,6 @@ bool 
lldb_private::python::SWIGBridge::LLDBSwigPythonCallCommand(
   return false;
 }
 
-bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallCommandObject(
-    PyObject *implementor, lldb::DebuggerSP debugger, const char *args,
-    lldb_private::CommandReturnObject &cmd_retobj,
-    lldb::ExecutionContextRefSP exe_ctx_ref_sp) {
-  return false;
-}
-
-bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallParsedCommandObject(
-    PyObject *implementor, lldb::DebuggerSP debugger,
-    StructuredDataImpl &args_impl,
-    lldb_private::CommandReturnObject &cmd_retobj,
-    lldb::ExecutionContextRefSP exe_ctx_ref_sp) {
-  return false;
-}
-
-std::optional<std::string>
-LLDBSwigPythonGetRepeatCommandForScriptedCommand(PyObject *implementor,
-                                                 std::string &command) {
-  return std::nullopt;
-}
-
-StructuredData::DictionarySP
-LLDBSwigPythonHandleArgumentCompletionForScriptedCommand(
-    PyObject *implementor, std::vector<llvm::StringRef> &args, size_t args_pos,
-    size_t pos_in_arg) {
-  return {};
-}
-
-StructuredData::DictionarySP
-LLDBSwigPythonHandleOptionArgumentCompletionForScriptedCommand(
-    PyObject *implementor, llvm::StringRef &long_options, size_t char_in_arg) {
-  return {};
-}
-
 bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallModuleInit(
     const char *python_module_name, const char *session_dictionary_name,
     lldb::DebuggerSP debugger) {

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to