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.pyals Enum + Dataclass-Events versioniert. Aufrufe wiefile_openedwerden über ein typisiertesFileOpenedEventzugestellt – alte Signaturen bleiben abwärtskompatibel. - Lifecycle-Trennung: Frühes
on_load()für Registrierungen, separateson_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_renamedlauschen 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_pluginals 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:
BasePluginliefert sicheres Cleanup, i18n viautils.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.mdist 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
- 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. - 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.
- 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.
- 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.
- 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.
- Headers und Lizenztext. Übernehmen Sie den AGPL-Header, den auch alle Core-Dateien tragen (siehe
plugins/base.py), und legen Sie eineLICENSE-Datei (oderCOPYING) 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
- Repository klonen.
plugins/example_plugin/als Vorlage kopieren inplugins/my_plugin/.config/config.jsonum"my_plugin"insys_active_pluginserweitern.- Plugin-Klasse füllen (
on_load,on_gui_ready, gewünschte Hooks). - mjEdit starten – Ihr Menüpunkt erscheint im Plugins-Menü.
- Entwicklerhandbuch lesen:
doc_dev/PLUGINS_DEV.md.
Best Practices
- GUI strikt vom Load trennen – Hook-Registrierung in
on_load(), GUI-Erzeugung erst inon_gui_ready(). - Globale Shortcuts nicht doppeln – Strg+S, Strg+W etc. werden zentral behandelt; nutzen Sie
save_active_plugin_tabstatt 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 Plugin –
doc_dev/unddoc_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.