Harness MaxxingDoku
📚 Alle Seiten

🧩 Erweitern · 7 Min. Lesezeit

🧩Dashboard-Widgets

Das Dashboard ist ein Raster aus Karten. Zieh die Karten, um sie neu anzuordnen, blende die aus, die du nicht brauchst (Layout-Menü), und aktualisiere eine Karte mit ihrer Aktualisieren-Schaltfläche.

Die Karten bauen sich selbst auf, sobald sie erscheinen: Zahlen zählen bis zu ihrem Wert hoch, Listen und Tabellen erscheinen Zeile für Zeile, Diagrammbalken wachsen und Linien werden gezeichnet, Fortschrittsbalken füllen sich, und Heutiger Zeitplan öffnet die Zeile jedes Jobs und setzt dessen Durchläufe von links nach rechts darauf. Eine Karte weiter unten auf der Seite wartet, bis du zu ihr scrollst, sodass du das ebenfalls siehst. Wenn sich das Benachrichtigungspanel öffnet oder schließt, gleiten die Karten an ihren neuen Platz. Alles bleibt ruhig stehen, wenn dein System reduzierte Bewegung verlangt.

Eine neue Installation zeigt Aktivität, Server, Heutiger Zeitplan, Aufgaben und Mach es zu deinem. Die anderen sind ausgeblendet, bis du sie brauchst (Layout bearbeiten zeigt sie an); Shopify erscheint von selbst, sobald ein Shop verbunden ist.

Mach es zu deinem ist die Karte am Ende: Klicke auf ein Beispiel („Heutige Shopify-Umsätze“, „Besucher auf meiner Website“ ...) und es öffnet sich eine neue Session in system/harness mit bereits eingetippter Anfrage - schick sie ab, und Harness schreibt das Widget in deine Erweiterungen. Jede Anfrage funktioniert auf die gleiche Weise aus jeder Session: „Füge ein Widget hinzu, das ... zeigt“. Blende die Karte mit Layout bearbeiten aus, sobald du dein eigenes hast.

Integrierte Widgets

Widget Zeigt
Aktivität letzte Sessions, laufende Durchgänge, ungelesene Antworten (vor der ersten Session: wo du anfangen kannst)
Aufgaben in Bearbeitung, heute fällig, überfällig
Heutiger Zeitplan der Tag deiner geplanten Jobs auf einer Linie von 00:00 - 24:00: eine Zeile pro Job, erledigte Durchläufe (grün, rot bei Fehlschlag), noch anstehende Durchläufe (hohl), der gerade laufende (pulsierend) und eine „Jetzt“-Linie; fahre über einen Punkt, um Uhrzeit und Dauer zu sehen
Geplante Jobs nächste Durchläufe und letzte Fehlschläge
Server CPU, Arbeitsspeicher, Festplatte, Betriebszeit, Gesundheitsprobleme
Memory-Backup letzter Lauf, Alter, Repository
Telegram Status von Bot und Verbindung
Shopify Umsatz heute und über 7 Tage (im Vergleich zur Vorwoche), Bestellungen heute, zu versendende Bestellungen, Umsatzdiagramm der letzten 14 Tage, neueste Bestellungen — sobald ein Shop verbunden ist (siehe unten)
Mach es zu deinem wie du an eigene Widgets kommst, mit Beispielen, die eine versandfertige Session öffnen
Claude-Nutzung standardmäßig aus (Einstellungen -> Funktionen): Nutzungsfenster deines Tarifs, wie die Balken unten in der Seitenleiste (gelesen aus der letzten Claude-Antwort; kam 10 Minuten lang keine, fragt es einen undokumentierten Anthropic-Endpunkt ab, der möglicherweise nicht mehr funktioniert; wenn dieser Endpunkt fehlschlägt oder um Wartezeit bittet, bleiben die letzten von Claude gelesenen Werte sichtbar).

Einen Shopify-Shop verbinden

Das Shopify-Widget zeigt eine Karte „Verbinde deinen Shop“, bis du zwei Schlüssel hinzufügst:

  1. Im Shopify-Admin: Einstellungen → Apps und Vertriebskanäle → Apps entwickeln → App erstellen, dann Admin-API-Bereiche konfigurieren → read_orders ankreuzen → App installieren → den Admin-API- Zugriffstoken anzeigen (shpat_…, wird nur einmal angezeigt).
  2. In Harness Maxxing: Einstellungen → Schlüssel → SHOPIFY_STORE = myshop.myshopify.com und SHOPIFY_ADMIN_TOKEN = der Token hinzufügen.

Das Widget aktualisiert sich alle 2 Minuten (oder mit seiner Aktualisieren-Schaltfläche) und liest nur Bestellungen. Ein anderer Shop (WooCommerce, Stripe, PrestaShop…)? Bitte Claude um ein Widget: Es baut auf die gleiche Weise eine Erweiterung (siehe unten).

Dein eigenes Widget schreiben

Ein Widget ist ein Ordner in <workspace>/extensions/<id>/ mit zwei Dateien. Du schreibst Daten, nicht HTML: Die Karte wird vom Designsystem der App gezeichnet, sodass sie im Dunkel- und im Hellmodus, auf dem Desktop und auf dem Handy immer gut aussieht. (Brauchst du freies Zeichnen oder Interaktion - ein Spiel, ein Canvas? Siehe HTML-Widgets weiter unten.)

Der schnellste Weg: Bitte Claude in einer Session: „Erstelle ein Dashboard-Widget, das ... zeigt“. Der Skill maxxing-extension kennt alles Folgende.

1. Den Ordner anlegen

/root/maxxing/extensions/disk-usage/
  manifest.json
  api.py

Die ID (Ordnername) ist ein kleingeschriebener Slug: Buchstaben, Ziffern, Bindestriche. Reservierte IDs: activity, tasks, day, crons, server, backup, telegram, shopify, usage, customize.

2. manifest.json

{"id": "disk-usage", "name": "Disk usage", "type": "widget", "icon": "hard-drive",
 "version": "1.0.0", "api": 1, "span": 1, "ttl": 60}
Schlüssel Bedeutung
type widget
api Version des Erweiterungsvertrags, für den es geschrieben ist (Standard 1); ein Release, das sie nicht mehr unterstützt, wird vor dem Update vom Update-Preflight angekündigt
icon ein Icon-Name in kebab-case (gauge, chart-line, shopping-cart, users, receipt...) aus dem Satz von lucide-Icons der App; ein Name, den die App nicht mitbringt, zeigt ein Puzzleteil
span Breite in Rasterspalten: 1, 2 oder 3
ttl Sekunden, für die das Ergebnis zwischengespeichert wird (Standard 300). Die Aktualisieren-Schaltfläche umgeht den Cache.

3. api.py

import shutil


def card(ctx):
    du = shutil.disk_usage("/")
    pct = round(100 * (du.total - du.free) / du.total)
    return {
        "title": "Disk usage",
        "status": "error" if pct > 95 else "warn" if pct > 85 else "ok",
        "blocks": [
            {"type": "stat", "label": "Used", "value": f"{pct}%", "hint": f"{du.free / 1e9:.1f} GB free"},
            {"type": "progress", "label": "/", "value": pct, "max": 100},
        ],
    }

card(ctx) läuft im Server: Verwende nur die Python-Standardbibliothek und halte Netzwerkaufrufe kurz (Timeout ≤ 10 s). Der Code von Erweiterungen läuft auf 4 eigenen Threads: Code, der nie zurückkehrt (ein Netzwerkaufruf ohne Timeout), blockiert nur andere Erweiterungen („Erweiterungen sind ausgelastet“, bis er zurückkehrt), nie den Rest von Harness. Eine api.py muss in weniger als 10 s laden (kein Netzwerkaufruf beim Import), sonst zeigt die Erweiterung einen Fehler. ctx enthält:

Schlüssel Wert
id die ID der Erweiterung
workspace Pfad des Workspace
state_dir ein für diese Erweiterung reservierter Ordner (Cache, Daten)
secret(name) Wert eines Schlüssels aus Einstellungen -> Schlüssel, oder None
config {"timezone", "language", "public_url"}

Schreibe niemals einen Token fest in den Code: Speichere ihn unter Einstellungen -> Schlüssel und lies ihn mit ctx["secret"]("MY_TOKEN").

4. Das Karten-Schema

Card  = {title?, status?: "ok"|"warn"|"error", link?, blocks: [Block], updated?}
Block =
  {type: "stat", label, value, hint?, delta?, tone?: "ok"|"warn"|"error"}
| {type: "stats", items: [stat, ...]}                      several stats in a row
| {type: "list", items: [{title, subtitle?, value?, href?, status?, icon?, emoji?}], empty?, grid?}
                                                            grid: true = tiles side by side (shortcuts)
                                                            icon: an icon name (below), drawn like the app's
| {type: "chart", kind: "bar"|"line", series: [{name, points: [[x, y], ...]}], unit?}
| {type: "table", columns: [str], rows: [[cell, ...]]}
| {type: "progress", label, value, max, hint?}
| {type: "markdown", text}

link macht den Kartentitel anklickbar (eine App-Route wie /tasks oder eine https://-URL). Unbekannte Blocktypen werden ignoriert.

Beispiel Liste + Diagramm:

def card(ctx):
    orders = [("#1042", "Alice", 89.0), ("#1041", "Bob", 34.5)]
    return {
        "title": "Shop",
        "link": "https://shop.example.com/admin",
        "blocks": [
            {"type": "stats", "items": [{"type": "stat", "label": "Today", "value": "123.50 €"},
                                        {"type": "stat", "label": "Orders", "value": "2"}]},
            {"type": "chart", "kind": "bar", "unit": "€",
             "series": [{"name": "Sales", "points": [["Mon", 80], ["Tue", 120], ["Wed", 123.5]]}]},
            {"type": "list", "empty": "No orders yet",
             "items": [{"title": o, "subtitle": c, "value": f"{v:.2f} €"} for o, c, v in orders]},
        ],
    }

5. Testen, dann laden

Teste die Funktion direkt auf dem Server:

cd /root/maxxing/extensions/disk-usage && python3 -c "
import json, api, tempfile
ctx = {'id': 'disk-usage', 'workspace': '/root/maxxing', 'state_dir': tempfile.mkdtemp(),
       'secret': lambda n: None, 'config': {'timezone': 'UTC', 'language': 'en', 'public_url': ''}}
print(json.dumps(api.card(ctx), indent=2))"

Dann Einstellungen -> Erweiterungen -> Neu laden. Das Widget erscheint auf dem Dashboard. Wenn etwas nicht stimmt (ungültiges Manifest, Importfehler, Ausnahme in card), wird der Fehler in der Zeile der Erweiterung und auf der Karte angezeigt; der Rest des Dashboards funktioniert weiter.

Erweiterungen liegen in deinem Workspace: Updates berühren sie nie, und das Backup enthält sie.

HTML-Widgets (Spiele, eigene Zeichnungen)

Wenn eine Karte nicht reicht - ein kleines Spiel, eine Canvas-Zeichnung, eine eigene Diagrammbibliothek - schreibe das Widget stattdessen in HTML. Gleicher Ordner, mit widget.html statt (oder neben) api.py:

/root/maxxing/extensions/snake-widget/
  manifest.json     {"type": "widget", "name": "Snake", "icon": "gamepad-2", "height": 300}
  widget.html       one self-contained file: inline <style> and <script>
  api.py            optional: handle(ctx, method, path, query, body) for data it saves or fetches

Das Dashboard zeichnet widget.html in einem Sandbox-Frame: Skripte laufen, aber der Frame kann weder die App noch ihre Cookies oder ihre API lesen - ein Widget kann also nie als du handeln. Es spricht mit seiner eigenen api.py über eine kleine Brücke, die für dich eingefügt wird:

In widget.html Was es tut
await maxxing.api("best") · maxxing.api("best", {method: "PUT", body: {...}}) ruft handle() in der api.py dieser Erweiterung auf (/api/ext/<id>/best) und gibt deren JSON zurück
maxxing.resize(400) setzt die Frame-Höhe in px (die height im Manifest, 120-800, ist der Standard)
maxxing.theme · maxxing.onTheme(fn) "dark" oder "light" und ein Callback, wenn es sich ändert
class="ui-card", ui-btn, ui-input, ui-badge, ui-stat... das UI-Kit der App (siehe Seiten-Erweiterungen), bereits im Frame geladen
var(--card) --foreground --muted-foreground --border --primary... (und die älteren --mx-fg --mx-muted --mx-border --mx-accent --mx-card --mx-bg) die Farben der App, aktualisiert mit dem Theme und der Akzentfarbe

Regeln: nur eine Datei (keine andere Datei des Ordners wird an den Frame ausgeliefert - binde alles inline ein oder lade Bibliotheken von einem CDN <script src="https://...">); kein localStorage/keine Cookies (die Sandbox hat keine: speichere über api.py und ctx["state_dir"]). Die Aktualisieren-Schaltfläche der Karte lädt den Frame neu. Vollständiges Beispiel: examples/extensions/snake-widget/ im Code-Verzeichnis.

💡 Nicht weiter? Öffne eine Session im Topic system › help und frag in deinen eigenen Worten: Claude liest diese Doku auf deinem Server und antwortet.✏️ Diese Seite verbessern