Plugins · Überblick & Referenz
Auf dieser Seite: was Plugins können, wie du sie installierst, aktualisierst und verwaltet
(auch die offiziellen Plugins), was Signaturen und Berechtigungen bedeuten – und zum Nachschlagen die Referenz:
plugin.toml, alle ctx-Aufrufe, Nodes, Hooks, Packen & Signieren.
Du willst selbst ein Plugin bauen? Dann fang mit Plugins · Entwickeln
an – dem Handbuch mit Schritt-für-Schritt-Beispielen – und schlag hier nach.
Überblick
Plugins erweitern Theron um das, was dir fehlt – ohne auf ein neues Release zu warten. Ein Plugin ist ein
Python-Paket mit einer plugin.toml, gepackt als .theronplugin-Datei.
- Neue Navigationspunkte mit eigener Oberfläche (z. B. ein Trello-Board in Theron).
- Zusätzliche Einstellungen – aus der
plugin.tomlerzeugt, auf Wunsch mit eigener Oberfläche. - Neue Nodes für Prozesse – im Editor wie die eingebauten, mit Feldern, ✔/✖- oder benannten Ausgängen (z. B. ein Switch), mehreren Eingängen und Unterstützung durch den KI-Designer.
- Anbindungen – z. B. Task-Listen mit Trello, Jira oder GitHub Issues synchronisieren: Zugriff auf Task-Listen, Ereignisse, Zeitpläne und HTTP.
- Werkzeuge für Agents – zusätzliche Tools, die jeder Claude-Agent während seiner Arbeit nutzen kann.
- Hooks – in Therons Abläufe eingreifen und das Ergebnis verändern: Prompts und Ergebnisse von Agents, Rückfragen (oder ihre Antwort), KI-Entwürfe, Task-Listen, das Team des THERON-Assistenten.
- Prozesse & Agents lesen, anlegen, ändern und löschen – und selbst mit Claude sprechen: einzelne Antworten, Gespräche, eigene Claude-Code-Agents.
Eine Vorlage mit Beispielen für alles davon – inklusive einer Trello-Synchronisierung – liegt öffentlich auf GitHub: github.com/mibexx/theron-plugin-sekeleton.
Fertige Plugins zum Herunterladen – mit Suche und Beschreibung – findest du im Plugin-Katalog. Schritt für Schritt erklärt das Plugin-Handbuch, wie du Seiten, Nodes, Fenster, Buttons und Formularfelder hinzufügst, mit Task-Listen arbeitest, auf Events und Transaktionen reagierst und laufende Agents liest.
Installieren & verwalten
- Offizielle Plugins (ab 0.18): Einstellungen → Plugins → „Offizielle Plugins …“ zeigt alle Plugins von
mibexx – direkt vom Plugin-Server
plugins.theron.mibexx.de. Ein Klick installiert oder aktualisiert; jede ältere Version lässt sich ebenfalls wählen. Theron nimmt die neueste Version, die zu deiner Theron-Version passt, prüft die Prüfsumme und die offizielle Signatur und zeigt Updates in der Plugin-Liste an (⬆). - In der App: Einstellungen → Plugins → „Plugin hochladen …“ und die
.theronplugin-Datei wählen. Theron zeigt Name, Version, Signatur und was das Plugin nutzt, bevor du es installierst. Dort schaltest du Plugins aus und ein, öffnest ihre Einstellungen und ihr Protokoll oder entfernst sie. - In der Konsole (auch auf einem Slave):
theron plugin install DATEI– oder ein offizielles Plugin per ID:theron plugin install elevenlabs,theron plugin update. - Im Headless-Worker (Docker):
.theronplugin-Dateien nach/data/plugin-packageslegen – sie werden bei jedem Start installiert oder aktualisiert. Oderdocker exec theron-worker theron plugin install /data/x.theronplugin --yes. - Neustart: Neue, geänderte und entfernte Plugins gelten nach einem Neustart von Theron.
- Wo es läuft: Ein Plugin-Node läuft dort, wo der Prozess läuft. Steuert deine App einen Slave, muss das Plugin auf dem Slave installiert sein. Fehlt es, bleibt der Node im Prozess und Theron fragt nach, wenn der Ablauf dort ankommt.
| Befehl | Wirkung |
|---|---|
theron plugin list | Installierte Plugins mit Zustand und Signatur. |
theron plugin info DATEI|ID | Manifest, Berechtigungen, Einstellungen, Signatur. |
theron plugin install DATEI [--yes] | Installieren oder aktualisieren (.theronplugin, .zip oder Ordner). |
theron plugin official | Die offiziellen Plugins: was installiert ist, was ein Update hat. |
theron plugin install ID[@VERSION] | Ein offizielles Plugin vom Plugin-Server – ohne Version die neueste, die passt. |
theron plugin update [ID …] | Offizielle Plugins aktualisieren (ohne IDs: alle mit Update). |
theron plugin enable|disable ID | Ein- bzw. ausschalten. |
theron plugin remove ID [--purge] | Entfernen; --purge löscht auch Einstellungen und Daten. |
theron plugin new ORDNER | Ein minimales Plugin zum Loslegen. |
theron plugin pack ORDNER [--sign SCHLÜSSEL] [--out ZIEL] | ID-VERSION.theronplugin bauen, optional signiert. |
theron plugin keygen DATEI | Einen Signaturschlüssel erzeugen. |
theron plugin trust ÖFFENTLICHER_SCHLÜSSEL [--name NAME] | Plugins mit diesem Schlüssel gelten als verifiziert. |
Sicherheit & Signaturen
Plugins laufen innerhalb von Theron und haben dieselben Rechte wie Theron selbst – Python lässt sich nicht
abschotten. Die Berechtigungen in der plugin.toml (network, tasklists …) zeigen
dir, was ein Plugin nutzt; sie sind ein Hinweis, keine Sperre. Installiere nur Plugins, deren Herkunft du vertraust.
- ✔ verifiziert: signiert mit einem Schlüssel, dem Theron vertraut – dem Theron-Schlüssel von mibexx oder einem,
den du mit
theron plugin trusthinzugefügt hast. - signiert, Schlüssel unbekannt: unverändert seit dem Signieren, aber von jemandem, den Theron nicht kennt. Der Fingerprint wird angezeigt.
- nicht signiert: Theron fragt ausdrücklich nach, ob du dem Plugin vertraust.
- ✖ ungültig: nach dem Signieren verändert – wird nie installiert.
Bricht ein Plugin beim Laden ab, schaltet Theron es mit seiner Fehlermeldung aus; alle anderen laufen weiter. Hat ein
Plugin Theron beim Laden zum Absturz gebracht, ist es beim nächsten Start ausgeschaltet. Mit
THERON_NO_PLUGINS=1 oder --no-plugins startet Theron ganz ohne Plugins.
Ein Plugin bauen
Am schnellsten mit der Vorlage: git clone https://github.com/mibexx/theron-plugin-sekeleton my-plugin – oder ganz
klein mit theron plugin new my-plugin. Aufbau:
my-plugin/ ├── plugin.toml Manifest: ID, Version, Berechtigungen, Einstellungen ├── my_plugin/ dein Python-Paket │ └── __init__.py def register(ctx): … ├── lib/ optional: reine Python-Abhängigkeiten └── i18n/de.json optional: Übersetzungen deiner Texte
Theron ruft beim Start register(ctx) auf. Über ctx meldet das Plugin an, was es mitbringt –
das ist die einzige Schnittstelle zu Theron und bleibt innerhalb einer API-Version stabil.
def register(ctx):
async def hello(node):
node.progress("greeting …")
return node.success(f"Hello – the input had {len(node.input)} characters.")
ctx.nodes.register("hello", label=ctx.tr("Say hello"), run=hello, outputs="success")
- Python: Plugins liegen als Quelltext vor und laufen mit dem Python, das in Theron steckt (3.12 oder neuer). Die ganze Standardbibliothek steht zur Verfügung.
- Abhängigkeiten: In Theron gibt es kein
pip. Reine Python-Bibliotheken legst du nachlib/(pip install --target lib PAKET); Bibliotheken mit nativem Code gehen nicht. Für HTTP gibt esctx.http. - Importe im Plugin relativ schreiben (
from .trello import Client). - Texte sind englisch;
ctx.tr("…")übersetzt sie miti18n/<sprache>.json.
plugin.toml
id = "trello-sync" # Kleinbuchstaben, Ziffern, "-" – eindeutig name = "Trello Sync" version = "1.0.0" description = "Synchronisiert Task-Listen mit einem Trello-Board." author = "Dein Name" license = "MIT" theron_api = 1 # Plugin-API, für die es geschrieben ist min_theron = "0.14.0" # optional entry = "trello_sync" # das Python-Paket mit register(ctx) permissions = ["network", "tasklists"] [[settings]] key = "api_key" type = "secret" # text, secret, textarea, number, bool, choice, tasklist, project label = "API key" required = true
Berechtigungen: network, tasklists, processes, agents, runs, agent-tools, hooks,
ui, files, ai. Jede Einstellung erscheint automatisch im Einstellungsdialog des
Plugins; tasklist und project werden zur Auswahlliste.
Die Plugin-API (ctx)
| ctx.… | Wofür |
|---|---|
settings.get(key) · settings[key] · settings.set(key, value) · settings.on_change(fn) · settings.add_button(label, run) · settings.on_edit(run, keys) | Die Einstellungen des Plugins; Buttons im Einstellungsdialog, z. B. „Testen“ (run(values) → Text oder (ok, text)); on_edit reagiert, während Felder im Dialog geändert werden – prüfen, nachfragen, andere Felder füllen ({"values": {…}, "ok", "text"}, ab 0.20). Auswahl-Optionen dürfen {value, label} sein. |
storage.get/set/delete/keys/items | Kleiner dauerhafter Speicher (Schlüssel → JSON), z. B. Trello-Karte ↔ Requirement. |
data_dir | Eigener Ordner für Dateien; bleibt bei Updates erhalten. |
nodes.register(kind, label=, run=, fields=, outputs=, inputs=, wait=, description=, ai_hint=, icon=) | Ein Prozess-Node – ✔/✖ oder benannte Aus-/Eingänge (fest oder aus den Einstellungen); Auswahlfelder mit Optionen aus einer Funktion, Felder mit show_if. |
{"id", "label", "color"} an Aus-/Eingängen | Farbe des Ports und seiner Kanten, frei vom Plugin gewählt: success (grün), error (rot, gestrichelt), neutral, info oder #rrggbb – auch abhängig von den Einstellungen (ab 0.18). |
"buttons": [{"label", "run"}] an einem Feld | Buttons neben einem Node-Feld, z. B. ▶ Hörprobe: run(config) läuft mit den aktuellen Einstellungen, das Label darf eine Funktion sein (▶ / ■) (ab 0.18). |
tools.register(name, description=, params=, run=) | Ein Werkzeug für Claude-Agents. |
ui.add_page(title, factory, icon=) · ui.settings_page(factory) · ui.call(fn) · ui.open_url(url) | Oberfläche (nur Desktop – ui.available). |
ui.add_panel(page, title, factory, area=) · ui.open_window(title, factory, id=) | Fenster neben/unter jeder Seite (folgen dem, was die Seite zeigt) und eigene Fenster – Handbuch. |
ui.add_action(page, label, run, place=) · ui.add_form_button(form, label, run) · fields.add(form, key, …) | Buttons in Werkzeugleisten, neben „Senden“, neben „+ Neu …“, auf der Task-Listen-Seite, in der Seitenleiste; Felder und Buttons in den Formularen von Agents, Prozessen, Projekten – Handbuch. |
ui.ask_text · ui.ask_choice · ui.message · ui.chat_input · ui.set_chat_input · ui.ask_files · ui.ask_folder · await ui.run(fn) | Kleine Dialoge im Theron-Look (ask_text auch mit Vorschlagsliste); Datei- und Ordnerwahl; mit ui.run aus Nodes und Buttons heraus etwas auf dem UI-Thread ausführen und das Ergebnis bekommen (ab 0.20); die Chat-Eingabe lesen und füllen. |
chat.add_format(pattern, render) · chat.add_renderer(kind, render) · chat.link(name, text, …) · chat.on_link(name, fn) · chat.refresh() | Den Chat erweitern: Text in jeder Nachricht anders darstellen, eigene Blöcke (Player, Dokumente, Diagramme …) mit klickbaren Buttons – das Plugin rendert, Theron bietet die Haken – Handbuch. |
await node.ask(question, options) · node.say(text, kind=, data=) | Ein Node fragt den Nutzer im Chat und wartet auf die Antwort; ein Node postet in den Chat (Text oder eigener Block). |
events.on(name, fn) | Auf Ereignisse reagieren (siehe unten). |
schedule(seconds, fn, first=) · on_start(fn) · on_stop(fn) | Regelmäßige Aufgaben, Start und Ende. |
spawn(fn, *args) | Etwas im Hintergrund ausführen, z. B. nach einem Klick (gibt ein Future zurück). |
tasklists.projects/list/get/find/create/update/delete, add_epic/update_epic/delete_epic, add_requirement/update_requirement/delete_requirement | Task-Listen, Epics und Requirements. |
processes.list/get/find/create/create_from_draft/to_draft/update/delete · agents.list/get/find/create/update/delete | Prozesse und Agents lesen, anlegen, ändern, löschen. |
hooks.on(name, fn, timeout=) · await hooks.call(name, data) · events.emit(name, payload) | In Abläufe eingreifen; eigene Hooks und Ereignisse anbieten. |
runs.start(project_id, text, title=) · runs.get/list · runs.message | Tasks starten und lesen. |
runs.active/state/workers/events/log · runs.watch(fn) · await runs.ask_worker(run_id, key, question) · runs.answer/stop/reset | Laufende Tasks, arbeitende Agents und ihre Logs lesen – live; einen Agent nebenbei fragen; antworten, stoppen, neu starten – Handbuch. |
await ai.ask(prompt, system=, model="haiku") | Eine Antwort des KI-Modells (dein Claude-Abo oder das lokale Kern-Modell). |
ai.session(system=, model=) · await ai.agent(prompt, cwd=, tools=, model=) | Ein Gespräch über mehrere Runden; ein eigener Claude-Code-Agent mit Werkzeugen (Standard: nur lesend). |
await http.get/post/put/patch/delete(url, params=, headers=, json=, data=) | HTTP-Client (.status, .json(), .text, .raise_for_status()). |
log(text) · log.error · log.exception · notify(text, level) | Protokoll (Einstellungen → Plugins → Protokoll) und Hinweise in der App. |
tr(text) · id · version · host · language · theron_version · dir | Übersetzung und Umgebung; host ist desktop, slave oder cli. |
Callbacks dürfen async oder normale Funktionen sein – normale laufen in einem eigenen Thread und dürfen
blockieren. Zeitpläne, on_start und Ereignisse laufen in der App und auf einem Slave, nicht bei einem
einzelnen theron execute.
Ereignisse: app.started, app.stopping, settings.changed,
run.event, run.status, run.finished, node.transition (Wechsel von Node zu
Node), agent.started, agent.finished, agent.status (Zwischenstand eines
arbeitenden Agents, alle paar Minuten), run.reset (Prozess ab einem Node neu gestartet),
hitl.asked, hitl.answered,
tasklist.created sowie …saved und
…deleted für project, process, agent, tasklist,
epic und requirement. Jedes Ereignis trägt source – Änderungen, die das Plugin
selbst gemacht hat, bekommt es nicht (außer mit include_own=True).
Prozess-Nodes
async def push(node):
board = node.config["board"] # Felder aus dem Editor
for h in node.handovers: # eingehende Übergaben: {"from", "text", "verdict"}
await create_card(board, h["text"])
if not board:
return node.failure("No board chosen") # ✖-Ausgang
return node.success(f"{len(node.handovers)} cards created")
ctx.nodes.register(
"push", label="Push to Trello", run=push, icon="▦",
description="Creates a Trello card per hand-over",
ai_hint="Use after results that should become Trello cards",
fields=[{"key": "board", "type": "text", "label": "Board"},
{"key": "list", "type": "choice", "options": ["To do", "Doing"]}],
outputs="success_failure") # oder "success" (nur ✔)
Der Node erscheint im Editor unter WERKZEUGE. node bringt außerdem input (Übergaben samt
aktuellem Requirement als Text), task, instructions, project,
run_id und tasklist_id (eine Task-Liste, die mit der Übergabe kam) mit, dazu
progress(text) für die Statuszeile. node.success(text, tasklist_id=…) gibt eine Task-Liste
weiter, z. B. an eine Schleife. Was hereinkommt, geht bei Erfolg mit weiter. Der KI-Designer kennt die Nodes aller
installierten Plugins und setzt sie ein, wenn sie passen.
Nodes wie ein Switch: benannte Aus- und Eingänge
def routes(config): # Ausgänge aus den Einstellungen des Nodes
names = [l.strip() for l in config["routes"].splitlines() if l.strip()]
return [{"id": f"r{k}", "label": n} for k, n in enumerate(names)] + [{"id": "other", "label": "Sonstiges"}]
async def route(node):
hit = next((o for o in node.outputs if o["label"].lower() in node.input.lower()), None)
return node.route(hit["id"] if hit else "other") # oder mehrere: node.route(["r0", "r1"])
ctx.nodes.register("route", label="Router", run=route, outputs=routes,
fields=[{"key": "routes", "type": "textarea", "default": "Bug\nFeature"}])
ctx.nodes.register("merge", label="Merge", run=merge, outputs="success",
inputs=[{"id": "code", "label": "Code"}, {"id": "docs", "label": "Doku"}], wait="all")
outputs nimmt neben "success_failure" und "success" eine Liste benannter
Ausgänge oder eine Funktion der Node-Einstellungen. Der Editor fragt sie bei jeder Änderung neu ab und zeichnet die
Ports neu, Kanten bestehender Ausgänge bleiben. node.route(id) wählt den Ausgang, mehrere Ausgänge laufen
wie eine UND-Verzweigung weiter. Benannte Eingänge (inputs=…) kommen je Eingang in
node.inputs an. Mit wait="all" startet der Node erst, wenn an jedem verbundenen Eingang
eine Übergabe liegt, mit "any" sofort. Der KI-Designer kennt die Ports und verbindet sie
(routes, input_map).
Seiten & Einstellungen
def board_page(ctx):
from PySide6.QtWidgets import QLabel, QVBoxLayout, QWidget
page = QWidget()
QVBoxLayout(page).addWidget(QLabel("My board"))
return page
ctx.ui.add_page("Trello", board_page, icon="▦") # eigener Navigationspunkt
Seiten sind PySide6-Widgets und entstehen erst, wenn das Fenster da ist. Widgets nur im UI-Thread anfassen – aus
Hintergrundaufgaben mit ctx.ui.call(fn). Für Einstellungen reicht meist [[settings]] in der
plugin.toml; mehr Oberfläche gibt ctx.ui.settings_page(factory) (ein Widget mit optionaler
save()-Methode).
Hooks: in Abläufe eingreifen
Ereignisse sagen, was passiert ist – Hooks verändern es. An festen Stellen wartet Theron auf die Plugins:
Jeder Handler bekommt den Kontext (hook.context: Lauf, Projekt, Prozess, Schritt, Agent, Übergaben …)
und die Daten (hook.data), die er ändern darf. Die Plugins sind nacheinander an der Reihe – in
der Reihenfolge, die du unter Einstellungen → Plugins mit ▲ ▼ festlegst (oder theron plugin order);
jeder sieht die Änderungen der vorigen. Ein Handler, der abbricht oder zu lange braucht, ändert nichts.
| Hook | Was sich ändern lässt |
|---|---|
agent.before_start · agent.after_end | Prompt und Briefing eines Agents – oder er wird übersprungen (result); sein Ergebnis und Urteil. |
hitl.question | Frage, Optionen, Empfehlung – oder gleich die Antwort (answer), z. B. von einer KI. |
message.user · orchestrator.prompt | Was Theron von einer Nachricht bekommt; Therons eigenes Briefing. |
process.design.before/after · agent.design.before/after | Anfrage, Prompt und Anweisungen der KI-Erstellung (z. B. ein Basis-Prompt); der fertige Entwurf. |
tasklist.before_create | Der Entwurf einer temporären oder befüllten Task-Liste. |
assistant.process.before_create/created | Auftrag und Temp-Prozess des THERON-Assistenten. |
@ctx.hooks.on("hitl.question", timeout=180) # Rückfragen von Claude beantworten lassen
async def answer(hook):
reply = await ctx.ai.ask(f"{hook.data['question']}\nOptionen: {hook.data['options']}\nNur die Option oder UNSURE.")
if reply.strip() in hook.data["options"]:
hook.data["answer"] = reply.strip()
@ctx.events.on("node.transition") # bei jedem Node-Wechsel eine WhatsApp
async def tell(e):
await whatsapp(f"{e['from']['name']} → {e['to']['name']}")
Plugins bieten auch eigene Hooks und Ereignisse an (await ctx.hooks.call("mein-plugin.vor_dem_senden", …),
ctx.events.emit("mein-plugin.gesendet", …)). Hooks laufen überall, wo Theron arbeitet – auch in
Konsolenläufen. Jeder Hook mit Kontext und Daten:
docs/hooks.md.
Anbindungen & Sync
@ctx.events.on("requirement.saved")
async def to_trello(event): # Theron → Trello
req = event["data"]
card = ctx.storage.get(f"req:{event['id']}")
...
async def from_trello(): # Trello → Theron
resp = await ctx.http.get("https://api.trello.com/1/boards/ID/cards",
params={"key": ctx.settings["api_key"], "token": ctx.settings["token"]})
for card in resp.raise_for_status().json():
... # ctx.tasklists.add_requirement / update_requirement
ctx.schedule(ctx.settings.get("interval", 5) * 60, from_trello, first=10)
Die Zuordnung (Karte ↔ Requirement) hältst du in ctx.storage. Was das Plugin über
ctx.tasklists ändert, löst bei ihm selbst keine Ereignisse aus – so entstehen keine Endlosschleifen. Die
Vorlage enthält eine fertige Grundlage für eine Zwei-Wege-Synchronisierung.
Werkzeuge für Agents
async def create_card(args):
...
return f"Card created: {url}"
ctx.tools.register("create_card", description="Create a Trello card",
params={"title": "string", "description": "string"}, run=create_card)
Jeder Claude-Agent kann das Werkzeug als mcp__theron_plugins__trello_sync__create_card aufrufen. OpenCode-Agents
bekommen keine Plugin-Werkzeuge.
Packen & signieren
theron plugin keygen ~/.theron-signing.key # einmal – geheim halten theron plugin pack my-plugin --sign ~/.theron-signing.key theron plugin trust "ÖFFENTLICHER_SCHLÜSSEL" --name "Ich" # auf jedem Rechner, der dir vertraut
Die Vorlage bringt mit python scripts/pack.py ein eigenes Pack-Skript mit, das ohne installiertes Theron
funktioniert. Die Signatur deckt jede Datei des Plugins ab; wer danach etwas ändert, macht sie ungültig.
Fehlersuche
- Protokoll: Einstellungen → Plugins → Protokoll, oder die Datei
plugin-data/<id>/plugin.logim Datenordner. Auf einem Slave stehen die Zeilen auch in der Konsole. - Fehler beim Laden stehen in der Plugin-Liste; Theron läuft ohne das Plugin weiter.
- „passt nicht zu dieser Theron-Version“:
theron_apiodermin_theronpasst nicht – Theron aktualisieren. - Tests ohne Theron: Die Vorlage enthält einen nachgebauten
ctx, mit dem du dein Plugin mitpytesttestest.