diff --git a/arakava-news/artikel/cursor-memory-system-artikel.md b/arakava-news/artikel/cursor-memory-system-artikel.md new file mode 100644 index 00000000..84349df2 --- /dev/null +++ b/arakava-news/artikel/cursor-memory-system-artikel.md @@ -0,0 +1,201 @@ +# Wie ich meiner KI ein Gedächtnis gebaut habe — und warum das alles verändert + +**Kategorie:** Technik / KI / Homelab +**Tags:** Cursor, Claude, AI Memory, Homelab, Git, Automatisierung, Prompt Engineering +**Lesezeit:** ca. 8 Minuten + +--- + +
Dieser Artikel erscheint zusammen mit einem öffentlichen GitHub-Repository: cursor-memory-system — alles was du brauchst um das System nachzubauen.
+ +--- + +## Das Problem: Die KI weiß nach jedem Gespräch nichts mehr + +Ich nutze Cursor mit Claude täglich für mein Homelab. Server einrichten, Code schreiben, Automatisierungen bauen. Das funktioniert gut — bis die Session endet. + +Beim nächsten Tag fängt die KI wieder bei null an. Ich erkläre zum dritten Mal welche Container laufen, welche IP mein WordPress hat, was das Projekt eigentlich soll. Wertvolle Minuten, manchmal eine halbe Stunde, nur um den Kontext wiederherzustellen. + +**Das ist kein Bug. Das ist wie diese KI-Systeme funktionieren.** Jedes Gespräch ist eine leere Seite. + +Ich wollte das ändern. + +--- + +## Die Idee: Ein Git-Repository als Gehirn + +Die Lösung kam aus einer simplen Beobachtung: Die KI kann Dateien lesen. Also gebe ich ihr die richtigen Dateien — mit allem was sie über mein System wissen muss. + +Kein Plugin. Kein teurer API-Wrapper. Nur ein Git-Repository mit Markdown-Dateien. + +Ich nenne es **homelab-brain**. + +*[Grafik: Animiertes Flussdiagramm — Live-Infrastruktur links, homelab-brain Repo mitte, Cursor/KI rechts, Datenpfeile fließen von links nach rechts]* + +--- + +## Wie es funktioniert + +### 1. Die Routing-Tabelle + +Die wichtigste Datei ist `.cursorrules` im Workspace-Root. Sie ist die **Telefonzentrale** des Systems: + +```markdown +## Routing-Tabelle +| Aufgabe betrifft... | Lade diese Datei | +|-----------------------------|-------------------------------| +| WordPress / RSS | arakava-news/STATE.md | +| KI-Redakteur | redax-wp/STATE.md | +| Smart Home / ioBroker | smart-home/STATE.md | +| ESP32 / Display / Heizung | esp32/PLAN.md | +| Server / Container / Proxmox| infrastructure/STATE.md | +``` + +Wenn ich eine neue Cursor-Session öffne und sage "richte mir einen neuen Container ein", liest die KI zuerst `.cursorrules` und weiß: *Infrastruktur → lade `infrastructure/STATE.md`*. Nur diese eine Datei. Das Kontextfenster bleibt sauber. + +### 2. Die STATE.md Dateien + +Für jedes Projekt gibt es eine `STATE.md`. Sie enthält alles was die KI wissen muss: + +```markdown +# STATE: Redax-WP +**Stand: 28.02.2026** + +## Zugang +| Was | URL | +|-----|-----| +| Dashboard | https://redax.orbitalo.net | +| Login | admin / astral66 | + +## Stack +redax-web Flask Dashboard (:8080) +redax-wordpress WordPress intern (:80) +redax-db MySQL 8 + +## Letzter Stand +- Multi-Publish zu 2 WordPress-Instanzen aktiv +- Drag & Drop im Redaktionsplan +- ESP32-Serie Teil 2 als Entwurf (Post 1340) +``` + +IPs, Passwörter, Container-Nummern, letzter Entwicklungsstand — alles drin. Die KI fragt nicht nach. + +### 3. Das Auto-Sync-Script + +Das Geniale: Die STATE.md-Dateien für laufende Services werden **automatisch aktualisiert**. Ein Cron-Job auf dem Server läuft alle 15 Minuten: + +```bash +# Läuft auf pve-hetzner, alle 15 Min +*/15 * * * * /opt/homelab-brain/scripts/sync-state.sh +``` + +Das Script fragt live die Container ab — ist der RSS-Manager aktiv? Wie viel OpenRouter-Guthaben ist noch da? Wie voll ist die Festplatte? — und schreibt die Ergebnisse in die STATE.md. Dann committed und pushed es auf Forgejo (mein internes Git). + +*[Screenshot: STATE.md mit Live-Daten — Timestamp, Service-Status grün, OpenRouter-Guthaben]* + +Wenn ich morgens Cursor öffne, hat die KI den **Stand von heute Nacht** — nicht von vor drei Wochen. + +--- + +## Was sich verändert hat + +**Vorher:** +> Ich: "Richte mir auf CT 113 den Redakteur ein." +> KI: "Was ist CT 113? Auf welchem Server? Welches OS? Was soll der Redakteur tun?" + +**Nachher:** +> Ich: "Richte mir auf CT 113 den Redakteur ein." +> KI: *liest infrastructure/STATE.md* → weiß: CT 113 ist auf pve-hetzner, Debian 12, IP 10.10.10.113, Docker läuft, Tailscale aktiv. Schreibt direkt den Deployment-Befehl. + +Der Unterschied ist nicht nur Zeit. Es ist **Qualität**. Die KI macht keine Annahmen mehr über meine Infrastruktur — sie kennt sie. + +--- + +## Das Ergebnis in Zahlen + +Ich betreibe damit: +- **7 aktive Projekte** (WordPress-Blog, KI-Redakteur, Edelmetall-Bot, Flugpreisscanner, Smart-Home, ESP32-Heizung, FünfVorAcht-Telegram-Bot) +- **12 Container** auf 2 Proxmox-Servern +- **Auto-Sync alle 15 Minuten** für 4 Live-Services + +Und ich muss der KI **nie wieder erklären** was mein System ist. + +--- + +## Der Trick mit dem Kontextfenster + +Ein häufiger Fehler: alles in eine riesige Datei packen und immer komplett laden. Das funktioniert nicht — Kontextfenster sind begrenzt, und je mehr irrelevanter Kontext drin ist, desto schlechter werden die Antworten. + +Meine Lösung: **Routing + Lazy Loading**. + +``` +.cursorrules ← wird IMMER geladen (klein, ~30 Zeilen) + └── Routing-Tabelle + ├── Aufgabe A → lade STATE-A.md + ├── Aufgabe B → lade STATE-B.md + └── Aufgabe C → lade STATE-C.md +``` + +Die KI lädt nur was gerade relevant ist. Bei 7 Projekten mit je 100-200 Zeilen STATE.md würde alles auf einmal ~1500 Zeilen Kontext fressen. Mit Routing sind es ~30 Zeilen Basis + ~150 Zeilen pro Session. + +*[Grafik: Vergleich Kontextfenster — ohne Routing (voll, rot) vs. mit Routing (schmal, grün)]* + +--- + +## Wie du es nachbaust + +Ich habe das komplette System als Template auf GitHub veröffentlicht: + +**→ [github.com/Orbitalo/cursor-memory-system](https://github.com/Orbitalo/cursor-memory-system)** + +Das Repository enthält: +- `.cursorrules` Vorlage mit Routing-Tabelle +- `STATE-template.md` zum Anpassen +- `sync-state.sh` Auto-Sync-Script (für Linux-Server) +- `SETUP.md` — Schritt-für-Schritt Anleitung + +**Minimale Version in 10 Minuten:** + +1. Repo klonen oder Template kopieren +2. `.cursorrules` in deinen Workspace-Root +3. Routing-Tabelle auf deine Projekte anpassen +4. Eine `STATE.md` für dein erstes Projekt anlegen +5. Cursor neu starten + +Das Auto-Sync ist optional — auch eine manuell gepflegte STATE.md ist 10x besser als keine. + +--- + +## Was kommt als nächstes + +Das System wächst mit dem Homelab. Geplant: + +- **ESP32-Integration**: Sobald die Heizungssteuerung läuft, werden Echtzeit-Temperaturen direkt in `smart-home/STATE.md` geschrieben — die KI sieht live ob der Pufferspeicher warm genug ist +- **Alert-System**: Wenn ein Service ausfällt, schreibt der Watchdog ein `⚠️ ALERT` in die STATE.md — die KI sieht es beim nächsten Öffnen sofort +- **Mehrsprachig**: README und Templates auf Englisch für die internationale Community + +--- + +## Fazit + +Das Konzept ist simpel bis zur Peinlichkeit: **Gib der KI die richtigen Dateien, und sie wird klüger.** + +Kein Plugin, kein Abo, keine schwarze Magie. Ein Git-Repository, ein paar Markdown-Dateien, optional ein Cron-Job. Das war's. + +Die KI hat kein schlechtes Gedächtnis. Sie hat nur kein Gedächtnis bekommen. + +--- + +*Dieser Artikel beschreibt mein persönliches Homelab-Setup. Das GitHub-Repository ist ein Template — anpassen erwünscht.* + +*→ GitHub: [cursor-memory-system](https://github.com/Orbitalo/cursor-memory-system)* +*→ Nächster Artikel: [ESP32-Heizungsprojekt Teil 2 — Die Hardware](/...)* + +--- + +### Grafik-Ideen für diesen Artikel + +1. **Animiertes Flussdiagramm**: Infrastruktur → homelab-brain → KI — mit leuchtenden Datenpfeilen (SVG, wie Fließschaltbild) +2. **Kontextfenster-Vergleich**: Balkendiagramm "ohne System" (voll, rot) vs. "mit Routing" (schlank, grün) +3. **Screenshot**: Cursor öffnet sich, KI antwortet sofort mit korrekten IPs/Befehlen ohne Nachfragen +4. **Repo-Struktur**: Dateibaum als schöne Grafik (dark theme) diff --git a/arakava-news/artikel/memory-system-diagram.svg b/arakava-news/artikel/memory-system-diagram.svg new file mode 100644 index 00000000..3ea701b7 Binary files /dev/null and b/arakava-news/artikel/memory-system-diagram.svg differ diff --git a/fuenfvoacht/docker-compose.yml b/fuenfvoacht/docker-compose.yml new file mode 100644 index 00000000..e69de29b diff --git a/fuenfvoacht/src/app.py b/fuenfvoacht/src/app.py new file mode 100644 index 00000000..8da687f1 --- /dev/null +++ b/fuenfvoacht/src/app.py @@ -0,0 +1,555 @@ +from flask import Flask, render_template, request, jsonify, redirect, url_for, Response +from functools import wraps +from datetime import datetime, date, timedelta +import os +import asyncio +import logging +import requests as req_lib + +import database as db +import openrouter +import logger as flog + +app = Flask(__name__) +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +TZ_NAME = os.environ.get('TIMEZONE', 'Europe/Berlin') +BOT_TOKEN = os.environ.get('TELEGRAM_BOT_TOKEN', '') +POST_TIME = os.environ.get('POST_TIME', '19:55') + +AUTH_USER = os.environ.get('AUTH_USER', 'Holgerhh') +AUTH_PASS = os.environ.get('AUTH_PASS', 'ddlhh') + +BRAND_MARKER = "Pax et Lux Terranaut01 https://t.me/DieneDemLeben" +BRAND_SIGNATURE = ( + "Wir schützen die Zukunft unserer Kinder und das Leben❤️\n\n" + "Pax et Lux Terranaut01 https://t.me/DieneDemLeben\n\n" + "Unterstützt die Menschen, die für Uns einstehen❗️" +) + + +def check_auth(username, password): + return username == AUTH_USER and password == AUTH_PASS + + +def authenticate(): + return Response( + 'Zugang verweigert.', 401, + {'WWW-Authenticate': 'Basic realm="FünfVorAcht"'}) + + +@app.before_request +def before_request_auth(): + auth = request.authorization + if not auth or not check_auth(auth.username, auth.password): + return authenticate() + + +@app.after_request +def add_no_cache(response): + response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0' + response.headers['Pragma'] = 'no-cache' + return response + + +def today_str(): + import pytz + return datetime.now(pytz.timezone(TZ_NAME)).strftime('%Y-%m-%d') + + +def today_display(): + import pytz + return datetime.now(pytz.timezone(TZ_NAME)).strftime('%d. %B %Y') + + +def week_range(): + today = date.today() + start = today - timedelta(days=today.weekday()) + return [(start + timedelta(days=i)).strftime('%Y-%m-%d') for i in range(7)] + + +def planning_days(count=7): + import pytz + tz = pytz.timezone(TZ_NAME) + t = datetime.now(tz).date() + return [(t + timedelta(days=i)).strftime('%Y-%m-%d') for i in range(count)] + + +def with_branding(content: str) -> str: + text = (content or "").rstrip() + if BRAND_MARKER in text: + return text + return f"{text}\n\n{BRAND_SIGNATURE}" if text else BRAND_SIGNATURE + + +def send_telegram_message(chat_id, text, reply_markup=None): + url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage" + payload = {"chat_id": chat_id, "text": text, "parse_mode": "HTML"} + if reply_markup: + import json + payload["reply_markup"] = json.dumps(reply_markup) + try: + r = req_lib.post(url, json=payload, timeout=10) + return r.json() + except Exception as e: + logger.error("Telegram send fehlgeschlagen: %s", e) + return None + + +def notify_all_reviewers(text, reply_markup=None): + results = [] + for chat_id in db.get_reviewer_chat_ids(): + result = send_telegram_message(chat_id, text, reply_markup) + results.append({'chat_id': chat_id, 'ok': bool(result and result.get('ok'))}) + return results + + +# ── Main Dashboard ──────────────────────────────────────────────────────────── + +@app.route('/') +def index(): + today = today_str() + articles_today = db.get_articles_by_date(today) + article_today = articles_today[0] if articles_today else None + week_days = week_range() + week_articles_raw = db.get_week_articles(week_days[0], week_days[-1]) + # Mehrere Artikel pro Tag: dict date → list + week_articles = {} + for a in week_articles_raw: + week_articles.setdefault(a['date'], []).append(a) + + recent = db.get_recent_articles(10) + stats = db.get_monthly_stats() + channel = db.get_channel() + prompts = db.get_prompts() + tags = db.get_tags() + favorites = db.get_favorites() + locations = db.get_locations() + current_location = db.get_current_location() + reviewers = db.get_reviewers() + last_posted = db.get_last_posted() + + plan_days = planning_days(7) + plan_raw = db.get_week_articles(plan_days[0], plan_days[-1]) + plan_articles = {} + for a in plan_raw: + plan_articles.setdefault(a['date'], []).append(a) + + month_start = date.today().replace(day=1).strftime('%Y-%m-%d') + month_end = (date.today().replace(day=28) + timedelta(days=4)).replace(day=1) - timedelta(days=1) + month_articles = {} + for a in db.get_week_articles(month_start, month_end.strftime('%Y-%m-%d')): + month_articles.setdefault(a['date'], []).append(a['status']) + + return render_template('index.html', + today=today, + article_today=article_today, + articles_today=articles_today, + week_days=week_days, + week_articles=week_articles, + plan_days=plan_days, + plan_articles=plan_articles, + month_articles=month_articles, + recent=recent, + stats=stats, + channel=channel, + post_time=POST_TIME, + prompts=prompts, + tags=tags, + favorites=favorites, + locations=locations, + current_location=current_location, + reviewers=reviewers, + last_posted=last_posted) + + +# ── History ─────────────────────────────────────────────────────────────────── + +@app.route('/history') +def history(): + articles = db.get_recent_articles(30) + return render_template('history.html', articles=articles) + + +# ── Prompts ─────────────────────────────────────────────────────────────────── + +@app.route('/prompts') +def prompts(): + all_prompts = db.get_prompts() + return render_template('prompts.html', prompts=all_prompts) + + +@app.route('/prompts/save', methods=['POST']) +def save_prompt(): + pid = request.form.get('id') + name = request.form.get('name', '').strip() + system_prompt = request.form.get('system_prompt', '').strip() + if pid: + db.save_prompt(int(pid), name, system_prompt) + else: + db.create_prompt(name, system_prompt) + return redirect(url_for('prompts')) + + +@app.route('/prompts/default/URL eines Artikels, Videos oder Vortrags einfügen — oder ein Thema als Text beschreiben.
+Tipp: Häufig genutzte Quellen als Favoriten speichern → Dropdown nutzen.
+Tag (z.B. Politik, Tech) und den gewünschten KI-Prompt auswählen.
+Prompts können unter Prompts bearbeitet und getestet werden.
+Button Artikel generieren klicken — die KI erstellt einen fertigen Telegram-Beitrag. Rechts erscheint sofort die Telegram-Vorschau.
+Nicht zufrieden? Neu generieren erstellt eine neue Version (v2, v3 …). Alle Versionen werden gespeichert.
+Text im Editor direkt bearbeiten. Die Telegram-Vorschau aktualisiert sich in Echtzeit. Zeichenanzahl wird live angezeigt (max. 4096).
+Das Markenzeichen wird automatisch am Ende eingefügt — nicht manuell nötig.
+Nach dem Generieren auf Einplanen klicken. Ein Panel öffnet sich:
+Bot-Benachrichtigung: Sofort vorausgewählt
+Automatisch: Vortag 17:00 Uhr vorausgewählt
+Blockiert wenn Slot belegt — belegte Zeiten sind ausgegraut
+Zum geplanten Zeitpunkt (oder sofort bei manuell senden) schickt der Bot den Artikel an alle Redakteure mit zwei Buttons:
+✏️ drücken → Bot zeigt aktuellen Text → einfach neue Version als nächste Nachricht schicken → Bot bestätigt + zeigt erneut Review-Buttons.
+/start Übersicht & Hilfe/heute Alle Slots von heute/queue Nächste 3 Tage/skip Hauptslot heute überspringenTäglich um 10:00 Uhr schickt der Bot automatisch einen Überblick:
+Der Scheduler prüft jede Minute: gibt es einen freigegebenen Artikel dessen Uhrzeit jetzt fällig ist?
+Klick auf einen Tag im Redaktionsplan lädt den Artikel direkt ins Studio — ohne neu generieren.
+Direkt im Board: neues Datum oder Uhrzeit wählen. Bei Zeitkonflikt wird geblockt und ein freier Slot vorgeschlagen.
+Artikel aus einem Slot entfernen — mit Sicherheitsabfrage. Slot wird danach wieder als frei angezeigt.
+Kanal-ID oder @username des Ziel-Kanals eintragen. Der Bot muss Admin im Kanal sein.
Default-Uhrzeit für neue Artikel. Kann pro Artikel beim Einplanen überschrieben werden.
+Neue Redakteure per Chat-ID hinzufügen. Beim Hinzufügen erhält der neue Redakteur automatisch eine Willkommensnachricht. Chat-ID herausfinden: @userinfobot in Telegram.
Häufig genutzte URLs speichern — erscheinen im Studio als Dropdown für schnellen Zugriff.
+Aktuellen Standort einstellen. Die Reminder-Zeiten werden automatisch auf MEZ umgerechnet.
+KI-Prompts erstellen, bearbeiten und mit einer Testquelle ausprobieren. Default-Prompt für neue Artikel festlegen.
+Um 18:00 Uhr kommt ein Reminder. Falls bis zur Posting-Zeit kein freigegebener Artikel vorhanden ist, wird der Slot übersprungen und ein Alarm gesendet.
+Im Dashboard nicht rückwirkend — aber in Telegram kannst du die Nachricht direkt bearbeiten (Telegram-Editier-Funktion).
+In Telegram @userinfobot anschreiben → gibt die eigene Chat-ID zurück. Oder die Person schreibt dem @Diendemleben_bot — die ID erscheint dann im Bot-Log.
Im Dashboard-Header: letzter Post-Zeitstempel. Im Bot: /start senden — Antwort bedeutet Bot ist aktiv. Auf dem Server: docker ps in CT 112.
Ja — jeden Zeitslot (15-Min-Raster) einmal belegen. Jeder Slot wird unabhängig gepostet. Doppelt belegte Slots werden automatisch blockiert.
+