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 154 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 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 nicht möglich und auch nicht verhandelbar,da mjEdit, selbst auf GPL bzw. AGPL Lizenzen basiert. Bitte nehmen Sie über das Kontaktformular mit uns Kontakt auf, damit wir gemeinsam eine Lösung für Ihre Anforderungen finden können – sei es durch Beratung, Entwicklungshilfe oder die Möglichkeit, Ihr Plugin unter AGPL zu veröffentlichen.
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. Wenn Sie ein Plugin entwickeln, das auf der mjEdit-API aufbaut, empfehlen wir dringend, es ebenfalls unter AGPL-3.0 zu veröffentlichen – so profitieren alle von der Offenheit und Erweiterbarkeit der Plattform.
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.