mjEdit ist nicht nur ein OSCAL-Editor, sondern eine Plattform. Das gesamte Programm ist auf einem dokumentierten Plugin-System aufgebaut: Selbst zentrale Funktionen wie der OSCAL-Editor, die Netzwerk-Discovery oder der MCP-Server liegen als Plugins vor und nutzen genau die API, die auch Ihnen offensteht.

Wenn ein internes mjEdit-Feature damit umsetzbar war, ist Ihr Plugin es auch.

Das Besondere

  • Open by design: mjEdit ist als erweiterbare Anwendung konzipiert. Funktionen wie OSCAL-Tabs, Browser-Tab, Database-Tab, Netzwerk-Discovery, MCP-Server und JSON-Transform-Tools sind eigene Plugins – kein zugeklemmtes Closed-Source-Innenleben.
  • Stabile Hook-Verträge: Die Schnittstellen sind in plugins/hook_contracts.py als Enum + Dataclass-Events versioniert. Aufrufe wie file_opened werden über ein typisiertes FileOpenedEvent zugestellt – alte Signaturen bleiben abwärtskompatibel.
  • Lifecycle-Trennung: Frühes on_load() für Registrierungen, separates on_gui_ready() sobald die GUI vollständig steht. Das verhindert die typischen „MainGUI noch nicht da"-Crashes anderer Plugin-Systeme.
  • Robust isoliert: Fehler in einem Plugin-Hook blockieren weder den Core noch andere Plugins. Beim Entladen werden Menü-Items, Toolbar-Buttons, Editor-Funktionen und Hooks automatisch durch BasePlugin.on_unload() aufgeräumt.
  • Konfiguration statt Klick-Installation: Aktivierung über config/config.json → sys_active_plugins. Versionsfest, deploybar, Git-freundlich.

Wie es funktioniert

plugins/
├── __init__.py          # PluginManager: Laden, Aktivieren, Hook-Aufrufe, Entladen
├── base.py              # BasePlugin: Lifecycle + Menü-/Toolbar-Helfer + Cleanup
├── hook_contracts.py    # HookName-Enum + typisierte Events
└── my_plugin/
    ├── __init__.py      # exportiert Plugin
    └── plugin.py        # Ihre Plugin-Klasse

Jedes Plugin exportiert eine Klasse Plugin, die von BasePlugin erbt. Der PluginManager lädt nur Plugins, die in sys_active_plugins aufgelistet sind, ruft den Lifecycle in der richtigen Reihenfolge auf und verteilt Hook-Aufrufe an alle registrierten Callbacks.

Minimalbeispiel – ein Plugin in unter 30 Zeilen

from plugins.base import BasePlugin, PluginType
from utils.i18n import _


class Plugin(BasePlugin):
    name = "Mein Plugin"
    version = "1.0.0"
    description = "Beispiel für die mjEdit Plugin-API"
    author = "Ihr Name"

    def __init__(self):
        super().__init__()
        self.plugin_type = PluginType.EDITOR_PLUGIN

    def on_load(self):
        self.add_menu_item(_("Mein Menüpunkt"), self.show_message)
        self.register_hook("file_opened", self.on_file_opened)

    def on_gui_ready(self):
        main_gui = self.manager.main_gui
        main_gui.widgets.set_status(_("Mein Plugin ist bereit"), timeout=3000)

    def show_message(self, main_gui):
        self.show_info(_("Mein Plugin wurde aufgerufen."))

    def on_file_opened(self, file_path, content, is_large_file=False):
        self.log(f"Datei geöffnet: {file_path}")

Aktivieren mit einem Eintrag in config/config.json:

{ "sys_active_plugins": ["my_plugin"] }

Fertig. Beim nächsten Start steht Ihr Menüpunkt im Plugins-Menü, Ihr file_opened-Handler reagiert auf jede geöffnete Datei.

Plugin-Typen

Drei Grundtypen über PluginType:

Typ Wofür Beispiele aus dem Core
EDITOR_PLUGIN Editor erweitern: Menüs, Funktionen, Hook-Reaktionen transform_script_plugin, network_discovery_plugin
GUI_PLUGIN Eigene Tabs, Dialoge, Fenster oscal_plugin, browser_plugin, database_plugin
TOOL_PLUGIN Hintergrund-Werkzeuge ohne eigene UI mje_mcp_server_plugin, gui_auto_test_plugin

Was lässt sich konkret bauen?

Die im Lieferumfang enthaltenen Plugins zeigen die Bandbreite – jedes davon ist ein realistisches Vorbild für eigene Erweiterungen:

  • Eigene Editor-Tabs für Domänen-spezifische Dateiformate (analog zum OSCAL-Plugin mit 8 spezialisierten Editoren).
  • Externe Tools andocken – ein Plugin kann eigene Server starten (siehe mje_mcp_server_plugin, das einen kompletten MCP-Server mit 88 Tools registriert).
  • Datenbank-Workbenches als Tab (siehe database_plugin).
  • Netzwerk- und Inventar-Tools, die ihre Ergebnisse direkt in geöffnete OSCAL-Dokumente schreiben (siehe network_discovery_plugin).
  • Transformationen und Auto-Repair für JSON-Strukturen (siehe transform_script_plugin).
  • Webbrowser oder externe Viewer als integrierten Tab (siehe browser_plugin).
  • Test- und Automatisierungs-Plugins, die GUI-Aktionen scripten (siehe gui_auto_test_plugin).
  • File-Type-Reaktoren, die auf file_opened / file_saved / file_renamed lauschen und z. B. Validierung, Konvertierung oder externes Logging anstoßen.

Hook-Referenz (Auszug)

Hook Signatur Zweck
add_menu (menubar, main_gui) Menüleiste erweitern
add_toolbar (toolbar, main_gui) Toolbar erweitern
file_opened FileOpenedEvent auf geöffnete Dateien reagieren
file_saved (file_path, main_gui) nach dem Speichern reagieren
file_renamed (old_path, new_path) Umbenennungen verarbeiten
file_save_requested (file_path) Speichern selbst übernehmen (return True)
save_active_plugin_tab (tab_index) aktiven Plugin-Tab für Strg+S speichern
get_plugin_file_path (tab_index) Dateipfad eines Plugin-Tabs liefern
open_external_url (url) externe URL plugin-intern behandeln
on_tab_changed (tab_index, tab_name) auf Tabwechsel reagieren

Domänenspezifische Hooks (z. B. add_excel_resource_to_oscal, update_oscal_resource_base64) sind über das OSCAL-Plugin verfügbar und werden nur dort aktiv – Ihre Plugins können sich gezielt einklinken.

Vorteile für Entwickler

  • Schnell zum ersten Erfolg: Erstes lauffähiges Plugin in einer halben Stunde – example_plugin als kopierbarer Startpunkt liegt im Repository.
  • Echte API, keine Fassade: Sie nutzen exakt dieselben Hooks und Helfer, mit denen das Core-Team selbst Tabs, Menüs und Tools baut.
  • PySide6 + Python: Volle Qt-Power, vertrauter Python-Stack, keine eigene DSL.
  • Saubere Trennung: BasePlugin liefert sicheres Cleanup, i18n via utils.i18n._(), einheitliches Logging und Fehler-Dialoge ohne Boilerplate.
  • Stabile Verträge: HookName-Enum + Dataclass-Events bedeuten: Refactorings im Core brechen Ihr Plugin nicht stillschweigend.
  • Gute Beispiele: Acht im Repository enthaltene Plugins decken nahezu jeden Erweiterungsfall ab – von Tab-GUI bis Hintergrund-Server.
  • Dokumentation und Tests: doc_dev/PLUGINS_DEV.md ist die offizielle, gepflegte Referenz. Plugins sind testbar wie normale Python-Pakete.
  • Keine Marktplatz-Hürde: Sie liefern Ihr Plugin als Verzeichnis aus – kein Store, keine Signatur, keine Approval-Pipeline.

Lizenzfrage – AGPL und Ihr Plugin

mjEdit steht unter der GNU Affero General Public License v3 (AGPL-3.0). Das hat klare Konsequenzen, sobald Sie ein Plugin schreiben, das die mjEdit-API verwendet:

Was AGPL für Plugin-Entwickler bedeutet

  1. Plugins sind ein abgeleitetes Werk. Da Ihr Plugin direkt auf BasePlugin, den Hook-Verträgen und der internen API von mjEdit aufbaut (from plugins.base import BasePlugin), entsteht im urheberrechtlichen Sinn ein abgeleitetes Werk. Damit greift die Copyleft-Klausel der AGPL.
  2. Ihr Plugin muss ebenfalls AGPL-kompatibel sein. In der Praxis: AGPL-3.0 oder eine ausdrücklich kompatible Lizenz. Proprietär / Closed-Source ist nicht zulässig, sobald Sie Ihr Plugin Dritten zugänglich machen oder als Service betreiben.
  3. Quellcode-Bereitstellung ist Pflicht – sowohl bei Weitergabe der Binaries (klassische GPL-Pflicht) als auch bei Netzwerk-Bereitstellung (die AGPL-Besonderheit gegenüber GPL). Wer mjEdit + Ihr Plugin als Service anbietet, muss den Quellcode aller Teile zugänglich machen.
  4. Interne Nutzung im Unternehmen ist unkritisch. Solange das Plugin nur intern verwendet und nicht an Dritte verteilt oder als Netzwerkdienst angeboten wird, entstehen keine Veröffentlichungspflichten.
  5. Kommerzielle Nutzung ist erlaubt. AGPL ≠ „nicht kommerziell". Sie dürfen Plugins verkaufen, Support anbieten, Beratungsleistungen rund um Ihr Plugin anbieten – Sie müssen nur den Quellcode mitliefern bzw. zugänglich machen.
  6. Headers und Lizenztext. Übernehmen Sie den AGPL-Header, den auch alle Core-Dateien tragen (siehe plugins/base.py), und legen Sie eine LICENSE-Datei (oder COPYING) bei.

Auswirkungen in der Praxis

Szenario Konsequenz
Plugin nur firmenintern nutzen Keine Veröffentlichungspflicht – AGPL fordert nichts.
Plugin an Kunden weitergeben Quellcode des Plugins muss als AGPL-3.0 mitgeliefert werden.
mjEdit + Plugin als SaaS / Webdienst Quellcode aller Teile muss Nutzern des Dienstes zugänglich sein (Netzwerk-Klausel der AGPL).
Plugin auf GitHub / GitLab veröffentlichen Lizenzhinweis AGPL-3.0 + Header in allen Quelldateien.
Closed-Source-Plugin verkaufen Nicht möglich ohne separate, kommerzielle Lizenz vom mjEdit-Rechteinhaber.

Wenn Sie Closed-Source brauchen

Falls Sie ein Plugin schreiben möchten, das aus geschäftlichen Gründen nicht unter AGPL veröffentlicht werden kann – etwa weil es proprietäre Algorithmen oder Kundendaten-Schemas enthält – ist eine kommerzielle Dual-License für mjEdit grundsätzlich verhandelbar. Bitte über das Kontaktformular anfragen.

Empfehlung

Für die meisten Plugin-Entwickler ist AGPL ein Vorteil, kein Hindernis: Ihr Plugin profitiert von einem stabilen, offen gepflegten Editor-Kern; Anwender bekommen Vertrauen durch Quelloffenheit; Auditoren und Behörden bevorzugen AGPL-Software in Compliance-Umgebungen ausdrücklich.

Erste Schritte

  1. Repository klonen.
  2. plugins/example_plugin/ als Vorlage kopieren in plugins/my_plugin/.
  3. config/config.json um "my_plugin" in sys_active_plugins erweitern.
  4. Plugin-Klasse füllen (on_load, on_gui_ready, gewünschte Hooks).
  5. mjEdit starten – Ihr Menüpunkt erscheint im Plugins-Menü.
  6. Entwicklerhandbuch lesen: doc_dev/PLUGINS_DEV.md.

Best Practices

  • GUI strikt vom Load trennen – Hook-Registrierung in on_load(), GUI-Erzeugung erst in on_gui_ready().
  • Globale Shortcuts nicht doppeln – Strg+S, Strg+W etc. werden zentral behandelt; nutzen Sie save_active_plugin_tab statt eigener Shortcuts.
  • Fehler isolieren – Hook-Handler defensiv schreiben; benutzerrelevante Fehler über self.show_error(...).
  • i18n verwenden – sichtbare Texte über utils.i18n._() führen.
  • Lazy Imports – schwere GUI-Abhängigkeiten erst beim ersten Aufruf importieren.
  • Eigene Doku im Plugindoc_dev/ und doc_user/ direkt im Plugin-Verzeichnis pflegen.

Sie möchten ein Plugin entwickeln?

Wir unterstützen Plugin-Autoren mit API-Beratung, Code-Reviews, AGPL-Konformitätsprüfung und – bei Bedarf – kommerzieller Dual-License. Schreiben Sie uns.