# Plugins — die Bauanleitung Ein Tool wird zum Plugin, indem sein Ordner hier abgelegt wird. Beim Start liest die Suite jede `plugin.json`, lädt das Backend und hängt es unter `/plugins/` ein. Das Dashboard listet es danach automatisch auf. Es gibt keine zentrale Liste, in die man ein Tool eintragen müsste. ``` plugins/ └── mein-tool/ ├── plugin.json Pflicht — Metadaten ├── backend.py Pflicht — create_app(ctx) gibt eine ASGI-App zurück ├── static/ │ └── index.html die Oberfläche └── … der Rest des Tools, unverändert ``` --- ## 1. plugin.json ```json { "id": "mein-tool", "name": "Mein Tool", "summary": "Eine Zeile für den Tool-Umschalter", "description": "Zwei bis drei Zeilen für die Dashboard-Karte.", "icon": "🔧", "category": "Tandoor", "version": "1.0.0", "entrypoint": "backend:create_app", "order": 50, "requires": ["tandoor"], "features": ["Stichpunkt eins", "Stichpunkt zwei"], "docs": "TOOL-README.md", "enabled": true } ``` | Feld | Bedeutung | |---|---| | `id` | identisch zum Ordnernamen, bestimmt Mount-Pfad und Datenordner | | `category` | gruppiert die Karten im Dashboard | | `order` | Sortierung (kleiner = weiter oben) | | `requires` | `tandoor`, `openai` — erscheint als Kennzeichen auf der Karte | | `mount` | optional, Standard ist `/plugins/` | | `enabled` | auf `false` setzen, um ein Tool vorübergehend stillzulegen | ## 2. backend.py ```python from fastapi import FastAPI from fastapi.responses import FileResponse def create_app(ctx): app = FastAPI(title=ctx.meta.name) @app.get("/") def index(): return FileResponse(ctx.path("static", "index.html")) @app.get("/api/state") def state(): return {"tandoor": ctx.settings.status()["tandoor"]} return app ``` Der Kontext `ctx`: | Attribut | Inhalt | |---|---| | `ctx.id`, `ctx.meta` | ID und Metadaten aus der `plugin.json` | | `ctx.dir`, `ctx.path(*teile)` | Pfade **im** Plugin-Ordner (nur lesen) | | `ctx.data_dir` | eigener Datenordner `data//` (hier schreiben) | | `ctx.settings` | zentrale Zugänge: `.get("TANDOOR_URL")`, `.status()`, `.tool_env()` | | `ctx.jobs` | Job-Runner für Kommandozeilen-Tools | | `ctx.mount` | Mount-Präfix, z. B. `/plugins/mein-tool` | Zurückgeben lässt sich jede ASGI-App. Eine **Flask**-App wird eingepackt: ```python from a2wsgi import WSGIMiddleware return WSGIMiddleware(flask_app) ``` ### Namenskollisionen vermeiden Gewachsene Tools benutzen naheliegende Modulnamen (`app.py`, `storage.py`). Damit sich zwei Plugins nicht ins Gehege kommen, werden ihre Module über `core.loader` unter eindeutigem Namen geladen — statt mit `import app`: ```python from core.loader import load_module, load_package, load_submodule modul = load_module(ctx.path("app.py"), f"btp_{ctx.id}_app") # app.py paket = load_package(ctx.path("app"), f"btp_{ctx.id}") # app/ main = load_submodule(ctx.path("app"), f"btp_{ctx.id}", "main") # app/main.py ``` ## 3. Die Oberfläche Damit ein Tool aussieht wie der Rest der Suite, bindet seine `index.html` das gemeinsame Design-System ein und bringt selbst nur noch Layout mit: ```html
``` `boehmi.js` ergänzt die App-Bar mit Tool-Umschalter und Design-Wechsel und stellt bereit: | Aufruf | Zweck | |---|---| | `BT.url("/api/x")` | Plugin-Pfad → vollständige URL (Mount-Präfix) | | `BT.api("/api/x", {method:"POST", body:…})` | fetch mit JSON- und Fehlerbehandlung | | `BT.toast("Text", "ok"\|"err")` | Rückmeldung | | `BT.escape(text)` | HTML-sicher ausgeben | | `BT.plugins`, `BT.ready` | Liste aller Tools, Promise nach dem Aufbau | **Wichtig:** API-Pfade nie fest verdrahten (`fetch("/api/x")`), sondern immer über `BT.url()` bzw. `BT.api()` — sonst zeigt das Tool auf den Host statt auf sich selbst. ### Bausteine aus `/shared/boehmi.css` `bt-main` (`.wide`, `.narrow`) · `bt-pagehead` · `bt-card` · `bt-btnrow` · `bt-row` / `bt-grid2` / `bt-grid3` · `bt-tabs` + `bt-tab` · `bt-badge` (`ok warn err info accent`) · `bt-notice` (`ok warn err info`) · `bt-status` · `bt-item` · `bt-table` · `bt-log` · `bt-empty` · `bt-muted` · `bt-hidden` Buttons und Formularfelder sind direkt gestylt — `