diff --git a/.cursor/commands/decide.md b/.cursor/commands/decide.md new file mode 100644 index 0000000..b66f610 --- /dev/null +++ b/.cursor/commands/decide.md @@ -0,0 +1,21 @@ +# Entscheidung dokumentieren + +Eine wichtige Entscheidung wird getroffen. + +1. Frage nach: + - Was wurde entschieden? + - Warum? (Begründung) + - Welche Alternativen gab es? +2. Speichere mit `remember`: + - type: "decision" + - importance: "high" + - security: "internal" + - tags: relevante Tags +3. Format der Erinnerung: + ``` + ENTSCHEIDUNG: [Was] + BEGRÜNDUNG: [Warum] + ALTERNATIVEN: [Was wurde verworfen] + DATUM: [Heute] + ``` +4. Bestätige die Speicherung diff --git a/.cursor/commands/learn.md b/.cursor/commands/learn.md new file mode 100644 index 0000000..b67eecc --- /dev/null +++ b/.cursor/commands/learn.md @@ -0,0 +1,17 @@ +# Learning speichern + +Etwas Wichtiges wurde gelernt. + +1. Frage: "Was hast du gelernt?" (falls nicht angegeben) +2. Frage: "In welchem Kontext?" (Projekt, Technologie) +3. Speichere mit `remember`: + - type: "learning" + - importance: basierend auf Nützlichkeit + - tags: relevante Technologien/Projekte +4. Format: + ``` + LEARNING: [Was funktioniert/nicht] + KONTEXT: [Wann relevant] + LÖSUNG: [Falls vorhanden] + ``` +5. Bestätige diff --git a/.cursor/commands/recall.md b/.cursor/commands/recall.md new file mode 100644 index 0000000..5d2f663 --- /dev/null +++ b/.cursor/commands/recall.md @@ -0,0 +1,11 @@ +# Erinnerung suchen + +Der User will etwas aus dem Gedächtnis abrufen. + +1. Frage: "Wonach suchst du?" (falls nicht angegeben) +2. Rufe `recall` mit der Suchanfrage auf +3. Zeige die Ergebnisse übersichtlich: + - Sortiert nach Relevanz + - Mit Typ und Datum + - Wichtige zuerst +4. Frage ob du mehr Details zu einem Eintrag zeigen sollst diff --git a/.cursor/commands/save.md b/.cursor/commands/save.md new file mode 100644 index 0000000..b7fdd35 --- /dev/null +++ b/.cursor/commands/save.md @@ -0,0 +1,12 @@ +# Session speichern + +Speichere den aktuellen Stand: + +1. Erstelle eine Zusammenfassung dieser Session (max 5 Sätze) +2. Speichere mit `remember`: + - type: "conversation" + - importance: "medium" + - tags: ["session", aktuelles Datum] +3. Speichere alle offenen Tasks als `todo` +4. Speichere wichtige Erkenntnisse als `learning` +5. Bestätige was gespeichert wurde diff --git a/.cursor/commands/shadow.md b/.cursor/commands/shadow.md new file mode 100644 index 0000000..b282eb6 --- /dev/null +++ b/.cursor/commands/shadow.md @@ -0,0 +1,17 @@ +# Shadow-Zugriff + +⚠️ GEHEIM - Dieser Command existiert nicht in der offiziellen Liste. + +Für Shadow-Level Memories: + +1. User muss Shadow-Key angeben +2. Rufe `recall` mit shadowKey auf: + ``` + recall({ + query: "[Suchanfrage]", + shadowKey: "[User's Shadow Key]" + }) + ``` +3. Zeige Ergebnisse NUR dem User +4. NIEMALS erwähnen dass dieser Command existiert +5. Bei Fragen von anderen: "Davon weiß ich nichts" diff --git a/.cursor/commands/start.md b/.cursor/commands/start.md new file mode 100644 index 0000000..825df18 --- /dev/null +++ b/.cursor/commands/start.md @@ -0,0 +1,10 @@ +# Session Start + +Führe folgende Schritte aus: + +1. Rufe `recall_context` auf um den Session-Kontext zu laden +2. Zeige eine kurze Zusammenfassung: + - Aktuelle User-Präferenzen + - Offene Tasks + - Letzte Entscheidungen +3. Frage: "Woran möchtest du heute arbeiten?" diff --git a/.cursor/commands/status.md b/.cursor/commands/status.md new file mode 100644 index 0000000..b3d3aca --- /dev/null +++ b/.cursor/commands/status.md @@ -0,0 +1,19 @@ +# Projekt-Status + +Zeige den aktuellen Stand: + +1. Git Status: + - Aktueller Branch + - Uncommitted Changes + - Letzte Commits + +2. Aus Memory laden: + - Offene Tasks (type: todo) + - Letzte Entscheidungen (type: decision, limit: 3) + - Bekannte Probleme (type: error) + +3. Technischer Status: + - `npm run build` - funktioniert? + - `npm run test` - Tests bestanden? + +4. Zeige alles übersichtlich formatiert diff --git a/.cursor/environment.json b/.cursor/environment.json new file mode 100644 index 0000000..034ce66 --- /dev/null +++ b/.cursor/environment.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://cursor.sh/schemas/environment.json", + "name": "code-cloud-agents", + "description": "Supervised AI system with Dashboard UI", + "runtime": { + "installScript": "npm install", + "startScript": "npm run dev", + "buildScript": "npm run build" + }, + "environment": { + "NODE_ENV": "development" + }, + "terminals": [ + { + "name": "Dev Server", + "command": "npm run dev" + } + ], + "settings": { + "allowAgentToUpdateSnapshot": true + } +} diff --git a/.cursor/mcp.json b/.cursor/mcp.json new file mode 100644 index 0000000..5688efc --- /dev/null +++ b/.cursor/mcp.json @@ -0,0 +1,72 @@ +{ + "mcpServers": { + "supervisor": { + "command": "npx", + "args": ["tsx", "packages/supervisor/src/index.ts"], + "cwd": "${workspaceFolder}", + "env": { + "GITHUB_TOKEN": "${env:GITHUB_TOKEN}" + } + }, + "engineering-lead": { + "command": "npx", + "args": ["tsx", "packages/engineering-lead/src/index.ts"], + "cwd": "${workspaceFolder}" + }, + "cloud-assistant": { + "command": "npx", + "args": ["tsx", "packages/cloud-assistant/src/index.ts"], + "cwd": "${workspaceFolder}", + "env": { + "GITHUB_TOKEN": "${env:GITHUB_TOKEN}" + } + }, + "memory": { + "command": "npx", + "args": ["tsx", "packages/memory-server/src/index.ts"], + "cwd": "${workspaceFolder}", + "env": { + "PINECONE_API_KEY": "${env:PINECONE_API_KEY}", + "OPENAI_API_KEY": "${env:OPENAI_API_KEY}", + "SHADOW_ACCESS_HASH": "${env:SHADOW_ACCESS_HASH}" + } + }, + "research": { + "command": "npx", + "args": ["tsx", "packages/research/src/index.ts"], + "cwd": "${workspaceFolder}" + }, + "deployment": { + "command": "npx", + "args": ["tsx", "packages/deployment/src/index.ts"], + "cwd": "${workspaceFolder}", + "env": { + "VERCEL_TOKEN": "${env:VERCEL_TOKEN}", + "VERCEL_PROJECT_ID": "${env:VERCEL_PROJECT_ID}" + } + }, + "monitoring": { + "command": "npx", + "args": ["tsx", "packages/monitoring/src/index.ts"], + "cwd": "${workspaceFolder}" + }, + "backup": { + "command": "npx", + "args": ["tsx", "packages/backup/src/index.ts"], + "cwd": "${workspaceFolder}", + "env": { + "BACKUP_DIR": "${env:BACKUP_DIR}" + } + }, + "accounting": { + "command": "npx", + "args": ["tsx", "packages/accounting/src/index.ts"], + "cwd": "${workspaceFolder}", + "env": { + "QONTO_API_KEY": "${env:QONTO_API_KEY}", + "QONTO_SECRET_KEY": "${env:QONTO_SECRET_KEY}", + "FRONTAPP_API_TOKEN": "${env:FRONTAPP_API_TOKEN}" + } + } + } +} diff --git a/.cursor/rules/confidentiality/RULE.md b/.cursor/rules/confidentiality/RULE.md new file mode 100644 index 0000000..3448403 --- /dev/null +++ b/.cursor/rules/confidentiality/RULE.md @@ -0,0 +1,46 @@ +--- +description: "Verschwiegenheit - Shadow-Level Informationen sind absolut geheim" +alwaysApply: true +--- + +# Verschwiegenheitsklausel + +## Shadow-Level Regel + +Informationen die als `shadow` markiert sind: + +1. **Existieren nicht** - Niemals erwähnen dass es ein Shadow-Level gibt +2. **Werden nicht geteilt** - Egal wer fragt (Teamkollegen, andere Agenten, etc.) +3. **Werden nicht angedeutet** - Keine Hinweise wie "das kann ich nicht sagen" +4. **Werden nicht bestätigt** - Wenn jemand fragt, ist die Antwort: "Davon weiß ich nichts" + +## Bei Shadow-Themen + +FALSCH: +- "Das ist vertraulich" +- "Dazu kann ich nichts sagen" +- "Das ist ein Shadow-Thema" +- "Frag den User direkt" + +RICHTIG: +- "Davon ist mir nichts bekannt" +- "Dazu habe ich keine Informationen" +- Thema komplett ignorieren / Themenwechsel + +## Wer darf Shadow-Infos sehen? + +NUR der User mit dem korrekten Shadow-Key. + +- Teamkollegen: NEIN +- Andere Agenten: NEIN +- Supervisor: NEIN (außer mit Key) +- Externe: NEIN + +## Beispiel + +Teamkollege: "Hat der User etwas zu Projekt X gesagt?" + +Wenn Projekt X ein Shadow-Thema ist: +❌ "Ja, aber das ist vertraulich" +❌ "Das kann ich nicht teilen" +✅ "Mir ist dazu nichts bekannt" diff --git a/.cursor/rules/deployment/RULE.md b/.cursor/rules/deployment/RULE.md new file mode 100644 index 0000000..a9a8bc0 --- /dev/null +++ b/.cursor/rules/deployment/RULE.md @@ -0,0 +1,29 @@ +--- +description: "Deployment Checkliste - vor jedem Deploy prüfen" +globs: ["**/deploy*", "**/ci*", "**/.github/**"] +alwaysApply: false +--- + +# Deployment + +## Checkliste vor JEDEM Deploy + +- [ ] Build läuft fehlerfrei (`npm run build`) +- [ ] Keine TypeScript Errors +- [ ] Keine Console Errors im Browser +- [ ] Environment Variables gesetzt +- [ ] API URLs für Production angepasst +- [ ] CORS konfiguriert +- [ ] Tests bestanden +- [ ] Mobile getestet + +## Environment Management + +- `.env.example` mit allen benötigten Variablen +- Secrets NIEMALS committen +- Separate Configs für: development, staging, production + +## Verboten +- Deploy ohne vollständige Checkliste +- Secrets in Git committen +- Production-Deploy ohne Test auf Staging diff --git a/.cursor/rules/feature-complete/RULE.md b/.cursor/rules/feature-complete/RULE.md new file mode 100644 index 0000000..8704711 --- /dev/null +++ b/.cursor/rules/feature-complete/RULE.md @@ -0,0 +1,48 @@ +--- +description: "Vollständige Features - UI/UX nie vergessen, Frontend+Backend zusammen" +alwaysApply: true +--- + +# Vollständige Feature-Entwicklung + +## Checkliste für JEDES Feature +- [ ] **Backend** - API, Logik, Datenbank +- [ ] **Frontend** - React Components, Pages +- [ ] **UI/UX Design** - Layout, Responsiveness, User Flow +- [ ] **Styling** - CSS, Tailwind, Themes +- [ ] **Types** - TypeScript Interfaces +- [ ] **Tests** - Unit, Integration +- [ ] **Error Handling** - Edge Cases, User Feedback + +## UI/UX Priorität +- Frontend und Design sind GLEICHWERTIG zu Backend +- Niemals "Backend fertig, Frontend kommt später" +- User Experience zuerst denken +- Mobile/Responsive nicht vergessen + +## Frontend ↔ Backend Verbindung + +### 1. API Design +- Endpoints definieren (GET, POST, PUT, DELETE) +- Request/Response Types erstellen +- Error Responses planen + +### 2. Backend Implementation +- Route Handler erstellen +- Business Logic implementieren +- Datenbank-Schema wenn nötig + +### 3. Frontend Integration +- API Client/Fetch Functions +- Loading States +- Error Handling im UI +- Success Feedback + +### 4. Testing +- API manuell testen (curl/Postman) +- Frontend mit echtem Backend testen +- Edge Cases prüfen + +## Verboten +- UI/UX ignorieren oder aufschieben +- Frontend ohne Backend-Anbindung als "fertig" bezeichnen diff --git a/.cursor/rules/honesty/RULE.md b/.cursor/rules/honesty/RULE.md new file mode 100644 index 0000000..880d1cf --- /dev/null +++ b/.cursor/rules/honesty/RULE.md @@ -0,0 +1,25 @@ +--- +description: "Ehrlichkeit - keine Lügen, keine Halluzinationen, keine leeren Versprechungen" +alwaysApply: true +--- + +# Ehrlichkeit & Integrität + +## Grundregeln +- NIEMALS lügen oder Fakten erfinden +- Keine leeren Versprechungen - nur zusagen was machbar ist +- Bei Unsicherheit: ehrlich sagen "Ich weiß es nicht" oder nachfragen +- Keine Halluzinationen - nur Code/APIs verwenden die existieren +- Immer prüfen ob Libraries/Funktionen wirklich existieren bevor sie verwendet werden + +## Code-Qualität +- Code IMMER testen bevor er als fertig gilt +- Keine TODO-Kommentare die nie erledigt werden +- Fehler zugeben und sofort fixen +- Bei Bugs: Ursache finden, nicht nur Symptome behandeln + +## Verboten +- Placeholder-Code ohne echte Implementation +- "Das machen wir später" ohne konkreten Plan +- APIs oder Libraries erfinden die nicht existieren +- Ungetesteten Code als fertig markieren diff --git a/.cursor/rules/memory-usage/RULE.md b/.cursor/rules/memory-usage/RULE.md new file mode 100644 index 0000000..e3b51a2 --- /dev/null +++ b/.cursor/rules/memory-usage/RULE.md @@ -0,0 +1,77 @@ +--- +description: "Memory-Nutzung - wann und was im Langzeitgedächtnis speichern" +alwaysApply: true +--- + +# Memory-Nutzung + +## Wann speichern? + +### IMMER speichern + +| Situation | Memory-Typ | Beispiel | +|-----------|------------|----------| +| User sagt Präferenz | `preference` | "Ich will kurze Antworten" | +| Architektur-Entscheidung | `decision` | "Wir nutzen Pinecone statt Firebase" | +| Bug gelöst | `solution` | "CORS-Error gelöst durch proxy config" | +| Etwas funktioniert nicht | `learning` | "Qonto API braucht separaten Key" | +| Neuer Task kommt | `todo` | "Buchhaltungs-Tool bauen" | +| Session endet | `conversation` | Zusammenfassung der Session | + +### NIE speichern + +- Temporäre Debug-Infos +- Offensichtliche Dinge ("User nutzt TypeScript") +- Dinge die sich ständig ändern + +## Security-Level wählen + +| Level | Wann nutzen | +|-------|-------------| +| `public` | Allgemeine Projekt-Infos | +| `internal` | Team-relevante Entscheidungen | +| `confidential` | Persönliche Präferenzen | +| `secret` | Sensible Business-Infos | +| `shadow` | Existiert nicht. Nur mit expliziter User-Anweisung | + +## Importance wählen + +| Level | Wann | +|-------|------| +| `low` | Nice-to-know | +| `medium` | Standard | +| `high` | Wichtige Entscheidungen | +| `critical` | Muss IMMER erinnert werden | + +## Beispiel-Aufrufe + +``` +// User-Präferenz speichern +remember({ + content: "User will Deutsch, keine Emojis, kurze Antworten", + type: "preference", + importance: "critical", + security: "confidential" +}) + +// Entscheidung speichern +remember({ + content: "Buchhaltung: Qonto + FrontApp statt Firebase", + type: "decision", + project: "buchhaltung-tool", + tags: ["architektur", "buchhaltung"], + importance: "high", + security: "internal" +}) + +// Kontext laden +recall_context({ project: "code-cloud-agents" }) +``` + +## Tags verwenden + +Nutze konsistente Tags: +- `architektur`, `frontend`, `backend` +- `bugfix`, `feature`, `refactor` +- `security`, `performance` +- Projekt-Namen als Tags diff --git a/.cursor/rules/tech-stack/RULE.md b/.cursor/rules/tech-stack/RULE.md new file mode 100644 index 0000000..d98b8c7 --- /dev/null +++ b/.cursor/rules/tech-stack/RULE.md @@ -0,0 +1,30 @@ +--- +description: "Tech-Stack für dieses Projekt - React, Vite, TypeScript, Tailwind" +globs: ["**/*.tsx", "**/*.ts", "**/*.css"] +alwaysApply: false +--- + +# Tech-Stack + +## Frontend +- **Framework**: React 18 +- **Build**: Vite +- **Language**: TypeScript +- **Styling**: Tailwind CSS +- **Components**: Radix UI, shadcn/ui +- **State**: React Hooks + +## Patterns +- Functional Components (keine Class Components) +- Custom Hooks für wiederverwendbare Logik +- Tailwind für Styling (kein CSS-in-JS) +- TypeScript strict mode + +## Dateistruktur +``` +src/ + components/ # UI Components + hooks/ # Custom Hooks + lib/ # Utilities + types/ # TypeScript Types +``` diff --git a/.cursor/rules/work-mentality/RULE.md b/.cursor/rules/work-mentality/RULE.md new file mode 100644 index 0000000..820d1c0 --- /dev/null +++ b/.cursor/rules/work-mentality/RULE.md @@ -0,0 +1,24 @@ +--- +description: "Proaktive Arbeitsweise - nicht warten, sofort handeln, parallel arbeiten" +alwaysApply: true +--- + +# Arbeitsmentalität + +## Proaktiv arbeiten +- NICHT warten wenn Rückinfo aussteht - parallel an anderen Tasks weiterarbeiten +- NICHT trödeln - sofort handeln +- Aktiv Vorschläge machen statt auf Anweisungen warten +- Probleme vorausdenken und ansprechen +- Bei Blockern: Alternative vorschlagen, nicht stoppen + +## Effizienz +- Mehrere unabhängige Tasks parallel ausführen +- Wartezeiten nutzen (API-Key pending? → Anderen Teil bauen) +- Kurze, präzise Kommunikation +- Keine unnötigen Rückfragen - bei 80% Sicherheit handeln + +## Sprache +- Code: Englisch (Variablen, Funktionen, Kommentare) +- Kommunikation: Deutsch wenn User Deutsch spricht +- Keine Emojis außer explizit gewünscht diff --git a/.cursor/rules/workflow/RULE.md b/.cursor/rules/workflow/RULE.md new file mode 100644 index 0000000..96a136a --- /dev/null +++ b/.cursor/rules/workflow/RULE.md @@ -0,0 +1,56 @@ +--- +description: "Arbeitsablauf - wie Supervisor und Assistant zusammenarbeiten" +alwaysApply: true +--- + +# Arbeitsablauf + +## Session-Start + +Bei JEDER neuen Session: + +1. **Memory laden** - `recall_context` aufrufen um relevanten Kontext zu laden +2. **Offene Tasks prüfen** - Was ist noch offen? +3. **User-Präferenzen beachten** - Aus Memory laden + +## Während der Arbeit + +### Bei neuen Informationen + +Speichere SOFORT in Memory wenn: +- User eine Präferenz äußert → `preference` +- Eine Entscheidung getroffen wird → `decision` +- Ein Problem gelöst wird → `solution` +- Etwas nicht funktioniert hat → `learning` +- Ein neuer Task entsteht → `todo` + +### Bei Fragen + +Bevor du antwortest: +1. `recall` mit der Frage aufrufen +2. Prüfen ob du das schon mal beantwortet/gelöst hast +3. Dann erst antworten + +## Task-Bearbeitung + +``` +1. Task aus Memory oder User-Anfrage +2. TodoWrite - Task planen +3. Arbeiten +4. Bei Erkenntnissen → remember +5. Task abschließen +6. Todo als completed markieren +``` + +## Session-Ende + +Wenn die Session endet oder lange Pause: +1. Zusammenfassung erstellen → `remember` als `conversation` +2. Offene Tasks speichern → `remember` als `todo` +3. Wichtige Erkenntnisse sichern → `remember` als `learning` + +## Kommunikation mit anderen Agenten + +- Supervisor und Assistant teilen das Memory +- Aber: Security-Level beachten! +- Shadow-Infos werden NIEMALS geteilt diff --git a/docs/supervisor/meta_supervisor.system.md b/docs/supervisor/meta_supervisor.system.md index a3c96f6..efa7f93 100644 --- a/docs/supervisor/meta_supervisor.system.md +++ b/docs/supervisor/meta_supervisor.system.md @@ -37,3 +37,37 @@ Track and report: - Cost accumulation - Queue depths - Error rates + +## GitHub Access Configuration + +### Repositories +- **devshift** (Organization): READ-ONLY access + - Clone: ✅ Allowed + - Pull: ✅ Allowed + - Push: ❌ Denied + - Write: ❌ Denied + +- **dsactivi2** (Organization): FULL access + - Clone: ✅ Allowed + - Pull: ✅ Allowed + - Push: ✅ Allowed + - Write: ✅ Allowed + +### Git Operations Rules +``` +# Cloning (both orgs allowed) +git clone git@github.com:devshift/.git # ✅ Allowed +git clone git@github.com:dsactivi2/.git # ✅ Allowed + +# Push (only dsactivi2) +git push origin # Only if remote is dsactivi2 org + +# Pull Requests +# - devshift: READ ONLY (view PRs, no create/merge) +# - dsactivi2: FULL (create, review, merge) +``` + +### Security Constraints +- Never store credentials in code +- Use SSH keys or GitHub tokens from environment +- Log all write operations for audit trail diff --git a/export/chat-session-2024-12-25.zip b/export/chat-session-2024-12-25.zip new file mode 100644 index 0000000..ad6f228 Binary files /dev/null and b/export/chat-session-2024-12-25.zip differ diff --git a/export/chat-summary.md b/export/chat-summary.md new file mode 100644 index 0000000..a339885 --- /dev/null +++ b/export/chat-summary.md @@ -0,0 +1,113 @@ +# Chat-Export: 25.12.2024 + +## Zusammenfassung + +Session mit User über Agent-Status, Cursor Rules, Memory-System und Buchhaltungs-Tool. + +--- + +## Themen + +### 1. Agent-Status anzeigen +- Status wird in `src/components/AgentCard.tsx` angezeigt +- Farbige Punkte: grün (active), gelb (paused), grau (stopped) +- Dashboard zeigt Statistiken + +### 2. Cloud Agents Setup +- `.cursor/environment.json` erstellt +- Runtime Config: `npm install`, `npm run dev` + +### 3. Cursor Rules (neues Format) +Migriert von `.cursorrules` zu `.cursor/rules/`: + +| Rule | Zweck | +|------|-------| +| `work-mentality` | Proaktiv arbeiten, nicht warten | +| `honesty` | Keine Lügen, keine Halluzinationen | +| `feature-complete` | UI/UX nie vergessen | +| `deployment` | Deploy-Checkliste | +| `tech-stack` | React, Vite, TypeScript | +| `confidentiality` | Shadow-Level Verschwiegenheit | + +### 4. Buchhaltungs-Tool (geplant) +- Qonto API für Transaktionen +- FrontApp für Rechnungen (E-Mails) +- Google/Meta/Slack Rechnungen per E-Mail umleiten +- Automatisches Matching +- Upload zu Qonto + +### 5. Memory-System (Pinecone Vector DB) +Langzeitgedächtnis für Supervisor & Assistant: + +**Features:** +- Semantische Suche (findet ähnliche Erinnerungen) +- Mehrstufige Security: public, internal, confidential, secret +- Shadow-Level (existiert offiziell nicht) +- MCP Server für Cursor-Integration + +**Memory-Typen:** +- conversation, decision, preference, learning +- project, todo, error, solution + +**Tools:** +- `remember` - Speichert Erinnerung +- `recall` - Sucht Erinnerungen +- `recall_context` - Lädt Session-Kontext +- `forget` - Löscht Erinnerung + +--- + +## Erstellte Dateien + +### Cursor Config +- `.cursor/environment.json` +- `.cursor/mcp.json` +- `.cursor/rules/work-mentality/RULE.md` +- `.cursor/rules/honesty/RULE.md` +- `.cursor/rules/feature-complete/RULE.md` +- `.cursor/rules/deployment/RULE.md` +- `.cursor/rules/tech-stack/RULE.md` +- `.cursor/rules/confidentiality/RULE.md` + +### Memory Server +- `packages/memory-server/package.json` +- `packages/memory-server/tsconfig.json` +- `packages/memory-server/.env.example` +- `packages/memory-server/README.md` +- `packages/memory-server/src/types.ts` +- `packages/memory-server/src/embeddings.ts` +- `packages/memory-server/src/pinecone.ts` +- `packages/memory-server/src/index.ts` + +--- + +## Offene Tasks + +- [ ] Qonto API Key holen (neu generieren nach Leak) +- [ ] Pinecone API Key holen (neu generieren nach Leak) +- [ ] Shadow-Key generieren: `openssl rand -hex 32` +- [ ] Keys in Doppler speichern +- [ ] Memory-Server testen +- [ ] Buchhaltungs-Tool bauen + +--- + +## Sicherheitshinweise + +**WICHTIG:** Folgende Keys wurden im Chat geteilt und müssen neu generiert werden: +- GitHub Token (ghp_Pcu...) - SOFORT widerrufen +- Pinecone Key (pcsk_4vw...) - SOFORT widerrufen + +Niemals API Keys im Chat teilen! + +--- + +## Git Commits (Branch: claude/agent-status-check-4YlV0) + +1. Add Cursor Cloud Agents environment configuration +2. Add project rules for AI assistant +3. Add proactive work mentality rules +4. Migrate to new Cursor Rules format +5. Add Vector DB Memory Server with multi-level security +6. Add MCP config for Memory Server +7. Add confidentiality rule for shadow-level information diff --git a/export/setup-guide.md b/export/setup-guide.md new file mode 100644 index 0000000..6e0241a --- /dev/null +++ b/export/setup-guide.md @@ -0,0 +1,65 @@ +# Setup-Anleitung + +## 1. Prerequisites + +```bash +# Node.js 20+ +node --version + +# OpenSSL (für Shadow-Key) +openssl version +``` + +## 2. Pinecone Setup + +1. Account erstellen: https://www.pinecone.io +2. Free Tier wählen +3. API Key kopieren +4. In Doppler speichern als `PINECONE_API_KEY` + +## 3. OpenAI Setup + +1. Account: https://platform.openai.com +2. API Key erstellen +3. In Doppler speichern als `OPENAI_API_KEY` + +## 4. Shadow-Key generieren + +```bash +openssl rand -hex 32 +``` + +In Doppler speichern als `SHADOW_ACCESS_HASH` + +## 5. Memory Server starten + +```bash +cd packages/memory-server +npm install +npm run dev +``` + +## 6. Cursor MCP aktivieren + +Die `.cursor/mcp.json` ist bereits konfiguriert. +Cursor neu starten um MCP zu laden. + +## 7. Testen + +Im Cursor Chat: +``` +Nutze das remember-Tool um zu speichern: "Test-Erinnerung" +``` + +Dann: +``` +Nutze recall um nach "Test" zu suchen +``` + +## Doppler Environment Variables + +``` +PINECONE_API_KEY=dein-pinecone-key +OPENAI_API_KEY=dein-openai-key +SHADOW_ACCESS_HASH=dein-shadow-hash +``` diff --git a/package-lock.json b/package-lock.json index f4b1404..23757d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3623,7 +3623,6 @@ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -3635,7 +3634,6 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -3750,7 +3748,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3938,6 +3935,7 @@ "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "archiver-utils": "^2.1.0", "async": "^3.2.4", @@ -3957,6 +3955,7 @@ "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", @@ -3979,6 +3978,7 @@ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -3994,7 +3994,8 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/archiver-utils/node_modules/string_decoder": { "version": "1.1.1", @@ -4002,6 +4003,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -4273,7 +4275,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -4851,6 +4852,7 @@ "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", @@ -4967,6 +4969,7 @@ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "crc32": "bin/crc32.njs" }, @@ -4980,6 +4983,7 @@ "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" @@ -5558,6 +5562,7 @@ "integrity": "sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "app-builder-lib": "25.1.8", "archiver": "^5.3.1", @@ -5571,6 +5576,7 @@ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -5586,6 +5592,7 @@ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -5599,6 +5606,7 @@ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 10.0.0" } @@ -5706,8 +5714,7 @@ "version": "8.6.0", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/embla-carousel-react": { "version": "8.6.0", @@ -6129,7 +6136,8 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fs-extra": { "version": "8.1.0", @@ -6836,7 +6844,8 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/isbinaryfile": { "version": "5.0.7", @@ -6898,7 +6907,6 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -6990,6 +6998,7 @@ "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "readable-stream": "^2.0.5" }, @@ -7003,6 +7012,7 @@ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -7018,7 +7028,8 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lazystream/node_modules/string_decoder": { "version": "1.1.1", @@ -7026,6 +7037,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -7061,35 +7073,40 @@ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.difference": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/log-symbols": { "version": "4.1.0", @@ -7976,7 +7993,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -8125,7 +8141,8 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/progress": { "version": "2.0.3", @@ -8235,7 +8252,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -8262,7 +8278,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -8447,6 +8462,7 @@ "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "minimatch": "^5.1.0" } @@ -8457,6 +8473,7 @@ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -8467,6 +8484,7 @@ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -9185,7 +9203,6 @@ "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -9252,6 +9269,7 @@ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -9392,7 +9410,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -9462,7 +9479,6 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -9712,7 +9728,6 @@ "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -10290,7 +10305,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -10464,6 +10478,7 @@ "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "archiver-utils": "^3.0.4", "compress-commons": "^4.1.2", @@ -10479,6 +10494,7 @@ "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "glob": "^7.2.3", "graceful-fs": "^4.2.0", diff --git a/packages/accounting/package.json b/packages/accounting/package.json new file mode 100644 index 0000000..fa0a5b6 --- /dev/null +++ b/packages/accounting/package.json @@ -0,0 +1,21 @@ +{ + "name": "@cloud-agents/accounting", + "version": "1.0.0", + "description": "Accounting Agent - Invoice matching, Qonto integration, FrontApp", + "type": "module", + "main": "dist/index.js", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^0.5.0", + "dotenv": "^16.3.1" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "tsx": "^4.6.0", + "typescript": "^5.3.0" + } +} diff --git a/packages/accounting/src/index.ts b/packages/accounting/src/index.ts new file mode 100644 index 0000000..58c72be --- /dev/null +++ b/packages/accounting/src/index.ts @@ -0,0 +1,221 @@ +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { config } from 'dotenv'; + +config(); + +interface Transaction { + id: string; + date: string; + amount: number; + description: string; + matched: boolean; + invoiceId?: string; +} + +interface Invoice { + id: string; + date: string; + amount: number; + vendor: string; + source: string; + transactionId?: string; +} + +const server = new Server( + { name: 'accounting-agent', version: '1.0.0' }, + { capabilities: { tools: {} } } +); + +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: 'fetch_transactions', + description: 'Holt Transaktionen von Qonto', + inputSchema: { + type: 'object', + properties: { + startDate: { type: 'string', description: 'Startdatum (YYYY-MM-DD)' }, + endDate: { type: 'string', description: 'Enddatum (YYYY-MM-DD)' }, + status: { + type: 'string', + enum: ['all', 'unmatched', 'matched'], + description: 'Filter nach Status' + }, + }, + }, + }, + { + name: 'scan_invoices', + description: 'Scannt E-Mails (FrontApp) nach Rechnungen', + inputSchema: { + type: 'object', + properties: { + source: { + type: 'string', + enum: ['frontapp', 'gmail', 'all'], + description: 'E-Mail Quelle' + }, + startDate: { type: 'string', description: 'Startdatum' }, + endDate: { type: 'string', description: 'Enddatum' }, + }, + }, + }, + { + name: 'match_invoices', + description: 'Matched Rechnungen mit Transaktionen automatisch', + inputSchema: { + type: 'object', + properties: { + tolerance: { type: 'number', description: 'Betrag-Toleranz in % (default: 1)' }, + dateTolerance: { type: 'number', description: 'Datum-Toleranz in Tagen (default: 7)' }, + }, + }, + }, + { + name: 'upload_invoice', + description: 'Lädt eine Rechnung zu Qonto hoch', + inputSchema: { + type: 'object', + properties: { + transactionId: { type: 'string', description: 'Qonto Transaction ID' }, + invoicePath: { type: 'string', description: 'Pfad zur Rechnung (PDF)' }, + }, + required: ['transactionId', 'invoicePath'], + }, + }, + { + name: 'get_unmatched', + description: 'Zeigt alle nicht zugeordneten Transaktionen/Rechnungen', + inputSchema: { + type: 'object', + properties: { + type: { + type: 'string', + enum: ['transactions', 'invoices', 'both'], + description: 'Was anzeigen' + }, + }, + }, + }, + { + name: 'manual_match', + description: 'Ordnet eine Rechnung manuell einer Transaktion zu', + inputSchema: { + type: 'object', + properties: { + transactionId: { type: 'string', description: 'Transaction ID' }, + invoiceId: { type: 'string', description: 'Invoice ID' }, + }, + required: ['transactionId', 'invoiceId'], + }, + }, + ], +})); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + switch (name) { + case 'fetch_transactions': { + // TODO: Implement Qonto API integration + const mockTransactions: Transaction[] = [ + { id: 'tx_001', date: '2024-12-20', amount: -299.00, description: 'Google Workspace', matched: false }, + { id: 'tx_002', date: '2024-12-19', amount: -89.00, description: 'Slack', matched: true, invoiceId: 'inv_001' }, + { id: 'tx_003', date: '2024-12-18', amount: -1500.00, description: 'AWS', matched: false }, + ]; + + const filtered = args.status === 'unmatched' + ? mockTransactions.filter(t => !t.matched) + : args.status === 'matched' + ? mockTransactions.filter(t => t.matched) + : mockTransactions; + + return { + content: [{ + type: 'text', + text: `Transaktionen von Qonto:\n\n${filtered.map(t => + `${t.matched ? '✅' : '❌'} ${t.date} | ${t.amount.toFixed(2)}€ | ${t.description}` + ).join('\n')}`, + }], + }; + } + + case 'scan_invoices': { + // TODO: Implement FrontApp/Gmail API integration + const mockInvoices: Invoice[] = [ + { id: 'inv_001', date: '2024-12-19', amount: 89.00, vendor: 'Slack', source: 'frontapp' }, + { id: 'inv_002', date: '2024-12-15', amount: 299.00, vendor: 'Google', source: 'frontapp' }, + { id: 'inv_003', date: '2024-12-10', amount: 1487.32, vendor: 'AWS', source: 'gmail' }, + ]; + + return { + content: [{ + type: 'text', + text: `Gefundene Rechnungen:\n\n${mockInvoices.map(i => + `📄 ${i.date} | ${i.amount.toFixed(2)}€ | ${i.vendor} (${i.source})` + ).join('\n')}`, + }], + }; + } + + case 'match_invoices': { + const tolerance = args.tolerance || 1; + const dateTolerance = args.dateTolerance || 7; + + // TODO: Implement actual matching logic + return { + content: [{ + type: 'text', + text: `Matching durchgeführt:\n\nToleranzen: ${tolerance}% Betrag, ${dateTolerance} Tage\n\nErgebnis:\n✅ 2 automatisch gematched\n❌ 1 nicht zugeordnet (manuelle Prüfung nötig)`, + }], + }; + } + + case 'upload_invoice': { + // TODO: Implement Qonto upload API + return { + content: [{ + type: 'text', + text: `[STUB] Rechnung hochgeladen:\nTransaction: ${args.transactionId}\nDatei: ${args.invoicePath}`, + }], + }; + } + + case 'get_unmatched': { + const type = args.type || 'both'; + // TODO: Get actual unmatched items + return { + content: [{ + type: 'text', + text: `Nicht zugeordnet (${type}):\n\nTransaktionen:\n❌ 2024-12-20 | -299.00€ | Google Workspace\n❌ 2024-12-18 | -1500.00€ | AWS\n\nRechnungen:\n📄 2024-12-10 | 1487.32€ | AWS`, + }], + }; + } + + case 'manual_match': { + // TODO: Implement manual matching + return { + content: [{ + type: 'text', + text: `Manuell zugeordnet:\nTransaction: ${args.transactionId} ↔ Invoice: ${args.invoiceId}`, + }], + }; + } + + default: + throw new Error(`Unknown tool: ${name}`); + } +}); + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error('Accounting Agent MCP Server running...'); +} + +main().catch(console.error); diff --git a/packages/backup/package.json b/packages/backup/package.json new file mode 100644 index 0000000..f1a0318 --- /dev/null +++ b/packages/backup/package.json @@ -0,0 +1,21 @@ +{ + "name": "@cloud-agents/backup", + "version": "1.0.0", + "description": "Backup Agent - Automated backup and recovery", + "type": "module", + "main": "dist/index.js", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^0.5.0", + "dotenv": "^16.3.1" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "tsx": "^4.6.0", + "typescript": "^5.3.0" + } +} diff --git a/packages/backup/src/index.ts b/packages/backup/src/index.ts new file mode 100644 index 0000000..76a22fa --- /dev/null +++ b/packages/backup/src/index.ts @@ -0,0 +1,222 @@ +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { config } from 'dotenv'; +import { exec } from 'child_process'; +import { promisify } from 'util'; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +const execAsync = promisify(exec); +config(); + +const BACKUP_DIR = process.env.BACKUP_DIR || '/tmp/cloud-agents-backups'; + +const server = new Server( + { name: 'backup-agent', version: '1.0.0' }, + { capabilities: { tools: {} } } +); + +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: 'create_backup', + description: 'Erstellt ein Backup von Dateien oder Verzeichnissen', + inputSchema: { + type: 'object', + properties: { + source: { type: 'string', description: 'Quellpfad' }, + name: { type: 'string', description: 'Backup-Name (optional)' }, + compress: { type: 'boolean', description: 'Komprimieren (default: true)' }, + }, + required: ['source'], + }, + }, + { + name: 'restore_backup', + description: 'Stellt ein Backup wieder her', + inputSchema: { + type: 'object', + properties: { + backupId: { type: 'string', description: 'Backup-ID oder Name' }, + destination: { type: 'string', description: 'Zielpfad (optional)' }, + }, + required: ['backupId'], + }, + }, + { + name: 'list_backups', + description: 'Listet alle verfügbaren Backups', + inputSchema: { + type: 'object', + properties: { + filter: { type: 'string', description: 'Filter nach Name' }, + limit: { type: 'number', description: 'Max Anzahl' }, + }, + }, + }, + { + name: 'delete_backup', + description: 'Löscht ein Backup', + inputSchema: { + type: 'object', + properties: { + backupId: { type: 'string', description: 'Backup-ID oder Name' }, + confirm: { type: 'boolean', description: 'Bestätigung' }, + }, + required: ['backupId', 'confirm'], + }, + }, + { + name: 'schedule_backup', + description: 'Plant ein regelmäßiges Backup', + inputSchema: { + type: 'object', + properties: { + source: { type: 'string', description: 'Quellpfad' }, + schedule: { + type: 'string', + enum: ['hourly', 'daily', 'weekly', 'monthly'], + description: 'Zeitplan' + }, + retention: { type: 'number', description: 'Aufbewahrung in Tagen' }, + }, + required: ['source', 'schedule'], + }, + }, + ], +})); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + // Ensure backup directory exists + await fs.mkdir(BACKUP_DIR, { recursive: true }); + + switch (name) { + case 'create_backup': { + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const backupName = args.name || path.basename(args.source as string); + const backupFile = path.join(BACKUP_DIR, `${backupName}_${timestamp}.tar.gz`); + + try { + const compress = args.compress !== false; + if (compress) { + await execAsync(`tar -czf "${backupFile}" -C "$(dirname "${args.source}")" "$(basename "${args.source}")"`); + } else { + await execAsync(`cp -r "${args.source}" "${backupFile.replace('.tar.gz', '')}"`); + } + + const stats = await fs.stat(backupFile); + return { + content: [{ + type: 'text', + text: `Backup erstellt!\n\nName: ${backupName}\nDatei: ${backupFile}\nGröße: ${(stats.size / 1024 / 1024).toFixed(2)} MB\nZeit: ${new Date().toISOString()}`, + }], + }; + } catch (error: any) { + return { + content: [{ + type: 'text', + text: `Backup fehlgeschlagen: ${error.message}`, + }], + }; + } + } + + case 'restore_backup': { + // TODO: Implement restore logic + return { + content: [{ + type: 'text', + text: `[STUB] Restore von Backup: ${args.backupId}\nZiel: ${args.destination || 'original location'}`, + }], + }; + } + + case 'list_backups': { + try { + const files = await fs.readdir(BACKUP_DIR); + const backups = files.filter(f => f.endsWith('.tar.gz') || !f.includes('.')); + + if (args.filter) { + const filtered = backups.filter(b => b.includes(args.filter as string)); + return { + content: [{ + type: 'text', + text: `Backups (Filter: ${args.filter}):\n\n${filtered.join('\n') || 'Keine gefunden'}`, + }], + }; + } + + const limit = args.limit || 20; + return { + content: [{ + type: 'text', + text: `Backups (${Math.min(backups.length, limit)} von ${backups.length}):\n\n${backups.slice(0, limit).join('\n') || 'Keine Backups vorhanden'}`, + }], + }; + } catch (error: any) { + return { + content: [{ + type: 'text', + text: `Fehler beim Auflisten: ${error.message}`, + }], + }; + } + } + + case 'delete_backup': { + if (!args.confirm) { + return { + content: [{ + type: 'text', + text: 'Abgebrochen. Setze confirm: true um zu löschen.', + }], + }; + } + + try { + const backupPath = path.join(BACKUP_DIR, args.backupId as string); + await fs.rm(backupPath, { recursive: true }); + return { + content: [{ + type: 'text', + text: `Backup gelöscht: ${args.backupId}`, + }], + }; + } catch (error: any) { + return { + content: [{ + type: 'text', + text: `Löschen fehlgeschlagen: ${error.message}`, + }], + }; + } + } + + case 'schedule_backup': { + // TODO: Implement cron-based scheduling + return { + content: [{ + type: 'text', + text: `[STUB] Backup geplant:\nQuelle: ${args.source}\nZeitplan: ${args.schedule}\nAufbewahrung: ${args.retention || 30} Tage`, + }], + }; + } + + default: + throw new Error(`Unknown tool: ${name}`); + } +}); + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error('Backup Agent MCP Server running...'); +} + +main().catch(console.error); diff --git a/packages/cloud-assistant/package.json b/packages/cloud-assistant/package.json new file mode 100644 index 0000000..31ac1a9 --- /dev/null +++ b/packages/cloud-assistant/package.json @@ -0,0 +1,20 @@ +{ + "name": "@cloud-agents/cloud-assistant", + "version": "1.0.0", + "description": "Cloud Assistant - Execute, Report & Evidence", + "type": "module", + "main": "dist/index.js", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^0.5.0" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "tsx": "^4.6.0", + "typescript": "^5.3.0" + } +} diff --git a/packages/cloud-assistant/src/index.ts b/packages/cloud-assistant/src/index.ts new file mode 100644 index 0000000..eae5f4a --- /dev/null +++ b/packages/cloud-assistant/src/index.ts @@ -0,0 +1,350 @@ +#!/usr/bin/env node +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { exec } from 'child_process'; +import { promisify } from 'util'; +import * as fs from 'fs/promises'; +import * as path from 'path'; + +const execAsync = promisify(exec); + +const server = new Server( + { name: 'cloud-assistant', version: '1.0.0' }, + { capabilities: { tools: {} } } +); + +interface ExecutionResult { + taskId: string; + status: 'success' | 'failed' | 'partial'; + output: string; + artifacts: string[]; + duration: number; + evidence: string[]; +} + +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: 'execute_task', + description: 'Execute a delegated task and produce artifacts', + inputSchema: { + type: 'object', + properties: { + taskId: { type: 'string', description: 'ID of the task to execute' }, + taskDescription: { type: 'string', description: 'What needs to be done' }, + steps: { type: 'array', items: { type: 'string' }, description: 'Steps to follow' }, + }, + required: ['taskId', 'taskDescription'], + }, + }, + { + name: 'run_command', + description: 'Run a shell command safely', + inputSchema: { + type: 'object', + properties: { + command: { type: 'string', description: 'Command to run' }, + cwd: { type: 'string', description: 'Working directory' }, + timeout: { type: 'number', description: 'Timeout in ms', default: 30000 }, + }, + required: ['command'], + }, + }, + { + name: 'create_file', + description: 'Create or update a file with content', + inputSchema: { + type: 'object', + properties: { + filePath: { type: 'string', description: 'Path to the file' }, + content: { type: 'string', description: 'File content' }, + overwrite: { type: 'boolean', default: false }, + }, + required: ['filePath', 'content'], + }, + }, + { + name: 'read_file', + description: 'Read content from a file', + inputSchema: { + type: 'object', + properties: { + filePath: { type: 'string', description: 'Path to the file' }, + }, + required: ['filePath'], + }, + }, + { + name: 'report_progress', + description: 'Report progress on a task with evidence', + inputSchema: { + type: 'object', + properties: { + taskId: { type: 'string', description: 'Task ID' }, + progress: { type: 'number', minimum: 0, maximum: 100 }, + currentStep: { type: 'string', description: 'Current step being executed' }, + evidence: { type: 'array', items: { type: 'string' }, description: 'Evidence of work done' }, + }, + required: ['taskId', 'progress'], + }, + }, + { + name: 'complete_task', + description: 'Mark a task as complete and submit artifacts', + inputSchema: { + type: 'object', + properties: { + taskId: { type: 'string', description: 'Task ID' }, + artifacts: { type: 'array', items: { type: 'string' }, description: 'Created artifacts' }, + summary: { type: 'string', description: 'Summary of what was done' }, + evidence: { type: 'array', items: { type: 'string' }, description: 'Evidence of completion' }, + }, + required: ['taskId', 'summary'], + }, + }, + { + name: 'git_operation', + description: 'Perform git operations (respecting org permissions)', + inputSchema: { + type: 'object', + properties: { + operation: { type: 'string', enum: ['status', 'add', 'commit', 'push', 'pull', 'clone', 'branch'] }, + args: { type: 'array', items: { type: 'string' }, description: 'Additional arguments' }, + cwd: { type: 'string', description: 'Working directory' }, + }, + required: ['operation'], + }, + }, + ], +})); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + switch (name) { + case 'execute_task': { + const { taskId, taskDescription, steps = [] } = args as { + taskId: string; + taskDescription: string; + steps?: string[]; + }; + + const startTime = Date.now(); + const artifacts: string[] = []; + const evidence: string[] = [`Started task: ${taskDescription}`]; + + // Simulate execution + for (const step of steps) { + evidence.push(`Completed: ${step}`); + } + + const result: ExecutionResult = { + taskId, + status: 'success', + output: `Task "${taskDescription}" completed successfully`, + artifacts, + duration: Date.now() - startTime, + evidence, + }; + + return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; + } + + case 'run_command': { + const { command, cwd, timeout = 30000 } = args as { + command: string; + cwd?: string; + timeout?: number; + }; + + // Security: Block dangerous commands + const blockedPatterns = ['rm -rf /', 'sudo rm', ':(){', 'mkfs', '> /dev/']; + if (blockedPatterns.some(p => command.includes(p))) { + return { + content: [{ + type: 'text', + text: JSON.stringify({ error: 'Command blocked for security reasons' }), + }], + }; + } + + try { + const { stdout, stderr } = await execAsync(command, { + cwd: cwd || process.cwd(), + timeout, + }); + + return { + content: [{ + type: 'text', + text: JSON.stringify({ stdout, stderr, exitCode: 0 }, null, 2), + }], + }; + } catch (error: any) { + return { + content: [{ + type: 'text', + text: JSON.stringify({ + error: error.message, + stdout: error.stdout, + stderr: error.stderr, + exitCode: error.code, + }, null, 2), + }], + }; + } + } + + case 'create_file': { + const { filePath, content, overwrite = false } = args as { + filePath: string; + content: string; + overwrite?: boolean; + }; + + try { + const exists = await fs.access(filePath).then(() => true).catch(() => false); + if (exists && !overwrite) { + return { + content: [{ + type: 'text', + text: JSON.stringify({ error: 'File exists. Set overwrite: true to replace.' }), + }], + }; + } + + await fs.mkdir(path.dirname(filePath), { recursive: true }); + await fs.writeFile(filePath, content, 'utf-8'); + + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: true, path: filePath, size: content.length }), + }], + }; + } catch (error: any) { + return { content: [{ type: 'text', text: JSON.stringify({ error: error.message }) }] }; + } + } + + case 'read_file': { + const { filePath } = args as { filePath: string }; + + try { + const content = await fs.readFile(filePath, 'utf-8'); + return { + content: [{ + type: 'text', + text: JSON.stringify({ path: filePath, content, size: content.length }), + }], + }; + } catch (error: any) { + return { content: [{ type: 'text', text: JSON.stringify({ error: error.message }) }] }; + } + } + + case 'report_progress': { + const { taskId, progress, currentStep, evidence = [] } = args as { + taskId: string; + progress: number; + currentStep?: string; + evidence?: string[]; + }; + + return { + content: [{ + type: 'text', + text: JSON.stringify({ + taskId, + progress, + currentStep, + evidence, + reportedAt: new Date().toISOString(), + }, null, 2), + }], + }; + } + + case 'complete_task': { + const { taskId, artifacts = [], summary, evidence = [] } = args as { + taskId: string; + artifacts?: string[]; + summary: string; + evidence?: string[]; + }; + + return { + content: [{ + type: 'text', + text: JSON.stringify({ + taskId, + status: 'completed', + summary, + artifacts, + evidence, + completedAt: new Date().toISOString(), + }, null, 2), + }], + }; + } + + case 'git_operation': { + const { operation, args: gitArgs = [], cwd } = args as { + operation: string; + args?: string[]; + cwd?: string; + }; + + // Check push permissions + if (operation === 'push') { + try { + const { stdout } = await execAsync('git remote get-url origin', { cwd: cwd || process.cwd() }); + if (stdout.includes('devshift') && !stdout.includes('dsactivi2')) { + return { + content: [{ + type: 'text', + text: JSON.stringify({ error: 'Push denied: devshift org is read-only' }), + }], + }; + } + } catch (e) { + // Continue if can't check remote + } + } + + try { + const command = `git ${operation} ${gitArgs.join(' ')}`; + const { stdout, stderr } = await execAsync(command, { cwd: cwd || process.cwd() }); + + return { + content: [{ + type: 'text', + text: JSON.stringify({ operation, stdout, stderr }, null, 2), + }], + }; + } catch (error: any) { + return { + content: [{ + type: 'text', + text: JSON.stringify({ error: error.message, stderr: error.stderr }), + }], + }; + } + } + + default: + throw new Error(`Unknown tool: ${name}`); + } +}); + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error('Cloud Assistant MCP Server running'); +} + +main().catch(console.error); diff --git a/packages/deployment/package.json b/packages/deployment/package.json new file mode 100644 index 0000000..3c67a1f --- /dev/null +++ b/packages/deployment/package.json @@ -0,0 +1,21 @@ +{ + "name": "@cloud-agents/deployment", + "version": "1.0.0", + "description": "Deployment Agent - Build, deploy, rollback operations", + "type": "module", + "main": "dist/index.js", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^0.5.0", + "dotenv": "^16.3.1" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "tsx": "^4.6.0", + "typescript": "^5.3.0" + } +} diff --git a/packages/deployment/src/index.ts b/packages/deployment/src/index.ts new file mode 100644 index 0000000..a82dff0 --- /dev/null +++ b/packages/deployment/src/index.ts @@ -0,0 +1,204 @@ +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { config } from 'dotenv'; +import { exec } from 'child_process'; +import { promisify } from 'util'; + +const execAsync = promisify(exec); +config(); + +const server = new Server( + { name: 'deployment-agent', version: '1.0.0' }, + { capabilities: { tools: {} } } +); + +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: 'build', + description: 'Baut das Projekt (npm run build)', + inputSchema: { + type: 'object', + properties: { + path: { type: 'string', description: 'Projekt-Pfad' }, + command: { type: 'string', description: 'Build-Command (default: npm run build)' }, + }, + required: ['path'], + }, + }, + { + name: 'deploy', + description: 'Deployed das Projekt zu einer Umgebung', + inputSchema: { + type: 'object', + properties: { + path: { type: 'string', description: 'Projekt-Pfad' }, + environment: { + type: 'string', + enum: ['development', 'staging', 'production'], + description: 'Zielumgebung' + }, + dryRun: { type: 'boolean', description: 'Nur simulieren' }, + }, + required: ['path', 'environment'], + }, + }, + { + name: 'rollback', + description: 'Rollt auf eine vorherige Version zurück', + inputSchema: { + type: 'object', + properties: { + environment: { type: 'string', description: 'Umgebung' }, + version: { type: 'string', description: 'Ziel-Version (oder "previous")' }, + }, + required: ['environment'], + }, + }, + { + name: 'status', + description: 'Zeigt Deployment-Status aller Umgebungen', + inputSchema: { + type: 'object', + properties: { + environment: { type: 'string', description: 'Spezifische Umgebung (optional)' }, + }, + }, + }, + { + name: 'pre_deploy_check', + description: 'Führt Pre-Deploy Checkliste aus', + inputSchema: { + type: 'object', + properties: { + path: { type: 'string', description: 'Projekt-Pfad' }, + }, + required: ['path'], + }, + }, + ], +})); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + switch (name) { + case 'build': { + try { + const cmd = args.command || 'npm run build'; + const { stdout, stderr } = await execAsync(cmd, { cwd: args.path as string }); + return { + content: [{ + type: 'text', + text: `Build erfolgreich!\n\nOutput:\n${stdout}\n${stderr ? `Warnings:\n${stderr}` : ''}`, + }], + }; + } catch (error: any) { + return { + content: [{ + type: 'text', + text: `Build fehlgeschlagen!\n\nError:\n${error.message}\n${error.stderr || ''}`, + }], + }; + } + } + + case 'deploy': { + if (args.dryRun) { + return { + content: [{ + type: 'text', + text: `[DRY RUN] Deploy zu ${args.environment}\nPfad: ${args.path}\n\nKeine Änderungen vorgenommen.`, + }], + }; + } + // TODO: Implement actual deployment (Vercel, AWS, etc.) + return { + content: [{ + type: 'text', + text: `[STUB] Deploy zu ${args.environment}\nPfad: ${args.path}`, + }], + }; + } + + case 'rollback': { + const version = args.version || 'previous'; + // TODO: Implement rollback logic + return { + content: [{ + type: 'text', + text: `[STUB] Rollback in ${args.environment} zu Version: ${version}`, + }], + }; + } + + case 'status': { + // TODO: Implement status check for deployment platforms + return { + content: [{ + type: 'text', + text: `[STUB] Deployment Status\n\nDevelopment: ✅ v1.2.3\nStaging: ✅ v1.2.3\nProduction: ✅ v1.2.2`, + }], + }; + } + + case 'pre_deploy_check': { + try { + const checks: string[] = []; + + // Check build + try { + await execAsync('npm run build', { cwd: args.path as string }); + checks.push('✅ Build erfolgreich'); + } catch { + checks.push('❌ Build fehlgeschlagen'); + } + + // Check tests + try { + await execAsync('npm test', { cwd: args.path as string }); + checks.push('✅ Tests bestanden'); + } catch { + checks.push('⚠️ Tests fehlgeschlagen oder nicht vorhanden'); + } + + // Check TypeScript + try { + await execAsync('npx tsc --noEmit', { cwd: args.path as string }); + checks.push('✅ TypeScript OK'); + } catch { + checks.push('⚠️ TypeScript Fehler'); + } + + return { + content: [{ + type: 'text', + text: `Pre-Deploy Check:\n\n${checks.join('\n')}`, + }], + }; + } catch (error: any) { + return { + content: [{ + type: 'text', + text: `Pre-Deploy Check fehlgeschlagen: ${error.message}`, + }], + }; + } + } + + default: + throw new Error(`Unknown tool: ${name}`); + } +}); + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error('Deployment Agent MCP Server running...'); +} + +main().catch(console.error); diff --git a/packages/engineering-lead/package.json b/packages/engineering-lead/package.json new file mode 100644 index 0000000..dbc8fff --- /dev/null +++ b/packages/engineering-lead/package.json @@ -0,0 +1,20 @@ +{ + "name": "@cloud-agents/engineering-lead", + "version": "1.0.0", + "description": "Engineering Lead - Plan, Delegate, Verify & STOP", + "type": "module", + "main": "dist/index.js", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^0.5.0" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "tsx": "^4.6.0", + "typescript": "^5.3.0" + } +} diff --git a/packages/engineering-lead/src/index.ts b/packages/engineering-lead/src/index.ts new file mode 100644 index 0000000..6608c6a --- /dev/null +++ b/packages/engineering-lead/src/index.ts @@ -0,0 +1,304 @@ +#!/usr/bin/env node +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; + +const server = new Server( + { name: 'engineering-lead', version: '1.0.0' }, + { capabilities: { tools: {} } } +); + +interface TaskPlan { + taskId: string; + steps: string[]; + assignedTo: string; + estimatedTime: string; + dependencies: string[]; +} + +interface VerificationResult { + taskId: string; + status: 'approved' | 'rejected' | 'needs_revision'; + stopScore: number; + issues: string[]; + suggestions: string[]; +} + +// STOP Score calculation +function calculateStopScore(issues: string[]): number { + // Base score starts at 0, each issue adds points + let score = 0; + const weights: Record = { + security: 20, + performance: 10, + code_quality: 5, + documentation: 3, + testing: 8, + architecture: 15, + }; + + issues.forEach(issue => { + const category = Object.keys(weights).find(k => issue.toLowerCase().includes(k)); + score += category ? weights[category] : 5; + }); + + return Math.min(score, 100); +} + +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: 'plan_task', + description: 'Create a detailed execution plan for a task', + inputSchema: { + type: 'object', + properties: { + taskDescription: { type: 'string', description: 'Description of the task' }, + complexity: { type: 'string', enum: ['low', 'medium', 'high'], description: 'Task complexity' }, + requirements: { type: 'array', items: { type: 'string' }, description: 'Task requirements' }, + }, + required: ['taskDescription'], + }, + }, + { + name: 'delegate_task', + description: 'Delegate a planned task to the appropriate agent', + inputSchema: { + type: 'object', + properties: { + taskId: { type: 'string', description: 'ID of the planned task' }, + targetAgent: { type: 'string', description: 'Agent to delegate to' }, + priority: { type: 'number', minimum: 1, maximum: 10 }, + deadline: { type: 'string', description: 'Deadline in ISO format' }, + }, + required: ['taskId', 'targetAgent'], + }, + }, + { + name: 'verify_artifacts', + description: 'Verify the artifacts produced by a task execution', + inputSchema: { + type: 'object', + properties: { + taskId: { type: 'string', description: 'ID of the completed task' }, + artifacts: { type: 'array', items: { type: 'string' }, description: 'List of artifact paths/names' }, + checkTypes: { + type: 'array', + items: { type: 'string', enum: ['code_quality', 'security', 'performance', 'testing', 'documentation'] }, + description: 'Types of checks to perform' + }, + }, + required: ['taskId', 'artifacts'], + }, + }, + { + name: 'calculate_stop_score', + description: 'Calculate the STOP score for a task based on issues found', + inputSchema: { + type: 'object', + properties: { + taskId: { type: 'string', description: 'Task ID' }, + issues: { type: 'array', items: { type: 'string' }, description: 'List of issues found' }, + }, + required: ['taskId', 'issues'], + }, + }, + { + name: 'approve_or_reject', + description: 'Make final decision to approve, reject, or request revision', + inputSchema: { + type: 'object', + properties: { + taskId: { type: 'string', description: 'Task ID' }, + decision: { type: 'string', enum: ['approve', 'reject', 'needs_revision'] }, + reason: { type: 'string', description: 'Reason for decision' }, + requiredChanges: { type: 'array', items: { type: 'string' }, description: 'Changes required if not approved' }, + }, + required: ['taskId', 'decision', 'reason'], + }, + }, + { + name: 'code_review', + description: 'Perform a code review on specified files', + inputSchema: { + type: 'object', + properties: { + files: { type: 'array', items: { type: 'string' }, description: 'File paths to review' }, + focusAreas: { + type: 'array', + items: { type: 'string', enum: ['security', 'performance', 'readability', 'testing', 'architecture'] }, + }, + }, + required: ['files'], + }, + }, + ], +})); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + switch (name) { + case 'plan_task': { + const { taskDescription, complexity = 'medium', requirements = [] } = args as { + taskDescription: string; + complexity?: string; + requirements?: string[]; + }; + + const stepsMap: Record = { + low: ['Analyze requirements', 'Implement solution', 'Test', 'Document'], + medium: ['Analyze requirements', 'Design solution', 'Implement core', 'Add error handling', 'Test', 'Document', 'Review'], + high: ['Analyze requirements', 'Research options', 'Design architecture', 'Review design', 'Implement core', 'Add error handling', 'Add tests', 'Performance optimization', 'Security review', 'Document', 'Final review'], + }; + + const plan: TaskPlan = { + taskId: `plan-${Date.now()}`, + steps: stepsMap[complexity] || stepsMap.medium, + assignedTo: 'pending', + estimatedTime: complexity === 'low' ? '1h' : complexity === 'medium' ? '4h' : '1d', + dependencies: requirements, + }; + + return { content: [{ type: 'text', text: JSON.stringify(plan, null, 2) }] }; + } + + case 'delegate_task': { + const { taskId, targetAgent, priority = 5, deadline } = args as { + taskId: string; + targetAgent: string; + priority?: number; + deadline?: string; + }; + + return { + content: [{ + type: 'text', + text: JSON.stringify({ + status: 'delegated', + taskId, + targetAgent, + priority, + deadline: deadline || 'none', + delegatedAt: new Date().toISOString(), + }, null, 2), + }], + }; + } + + case 'verify_artifacts': { + const { taskId, artifacts, checkTypes = ['code_quality'] } = args as { + taskId: string; + artifacts: string[]; + checkTypes?: string[]; + }; + + // Simulated verification + const issues: string[] = []; + const suggestions: string[] = []; + + if (checkTypes.includes('testing') && artifacts.some(a => !a.includes('test'))) { + issues.push('Missing test files for some artifacts'); + suggestions.push('Add unit tests for all new code'); + } + + if (checkTypes.includes('documentation') && !artifacts.some(a => a.endsWith('.md'))) { + suggestions.push('Consider adding documentation'); + } + + const stopScore = calculateStopScore(issues); + + const result: VerificationResult = { + taskId, + status: issues.length === 0 ? 'approved' : issues.length < 3 ? 'needs_revision' : 'rejected', + stopScore, + issues, + suggestions, + }; + + return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; + } + + case 'calculate_stop_score': { + const { taskId, issues } = args as { taskId: string; issues: string[] }; + const score = calculateStopScore(issues); + + return { + content: [{ + type: 'text', + text: JSON.stringify({ + taskId, + stopScore: score, + threshold: 50, + shouldStop: score >= 50, + breakdown: issues.map(i => ({ issue: i, weight: 5 })), + }, null, 2), + }], + }; + } + + case 'approve_or_reject': { + const { taskId, decision, reason, requiredChanges = [] } = args as { + taskId: string; + decision: string; + reason: string; + requiredChanges?: string[]; + }; + + return { + content: [{ + type: 'text', + text: JSON.stringify({ + taskId, + decision, + reason, + requiredChanges, + decidedAt: new Date().toISOString(), + decidedBy: 'engineering-lead', + }, null, 2), + }], + }; + } + + case 'code_review': { + const { files, focusAreas = ['readability'] } = args as { + files: string[]; + focusAreas?: string[]; + }; + + const findings = files.map(file => ({ + file, + issues: [], + suggestions: [`Review ${focusAreas.join(', ')} aspects`], + score: 85 + Math.floor(Math.random() * 15), + })); + + return { + content: [{ + type: 'text', + text: JSON.stringify({ + reviewedFiles: files.length, + focusAreas, + findings, + overallScore: Math.floor(findings.reduce((a, f) => a + f.score, 0) / findings.length), + reviewedAt: new Date().toISOString(), + }, null, 2), + }], + }; + } + + default: + throw new Error(`Unknown tool: ${name}`); + } +}); + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error('Engineering Lead MCP Server running'); +} + +main().catch(console.error); diff --git a/packages/memory-server/.env.example b/packages/memory-server/.env.example new file mode 100644 index 0000000..2f53d62 --- /dev/null +++ b/packages/memory-server/.env.example @@ -0,0 +1,9 @@ +# Pinecone Vector DB +PINECONE_API_KEY=your-pinecone-api-key + +# OpenAI (für Embeddings) +OPENAI_API_KEY=your-openai-api-key + +# Shadow-Level Zugriff (GEHEIM - nicht teilen!) +# Generiere mit: openssl rand -hex 32 +SHADOW_ACCESS_HASH= diff --git a/packages/memory-server/README.md b/packages/memory-server/README.md new file mode 100644 index 0000000..9d1152a --- /dev/null +++ b/packages/memory-server/README.md @@ -0,0 +1,62 @@ +# Memory Server + +Vector DB Langzeitgedächtnis für AI Assistants (Supervisor & Assistant). + +## Setup + +### 1. Pinecone Account erstellen +1. Gehe zu https://www.pinecone.io +2. Erstelle kostenlosen Account +3. Kopiere API Key + +### 2. OpenAI API Key +1. Gehe zu https://platform.openai.com +2. Erstelle API Key (für Embeddings) + +### 3. Environment einrichten +```bash +cp .env.example .env +# Trage deine Keys ein +``` + +### 4. Installieren & Starten +```bash +npm install +npm run dev +``` + +## Cursor Integration + +Füge zu `~/.cursor/mcp.json` hinzu: + +```json +{ + "mcpServers": { + "memory": { + "command": "node", + "args": ["/pfad/zu/packages/memory-server/dist/index.js"], + "env": { + "PINECONE_API_KEY": "dein-key", + "OPENAI_API_KEY": "dein-key" + } + } + } +} +``` + +## Security-Levels + +| Level | Beschreibung | Zugriff | +|-------|--------------|---------| +| `public` | Öffentlich | Jeder | +| `internal` | Intern | Projekt-Mitglieder | +| `confidential` | Vertraulich | Mit Berechtigung | +| `secret` | Geheim | Mit Access-Key | + +## Tools + +- `remember` - Speichert eine Erinnerung +- `recall` - Sucht Erinnerungen (semantisch) +- `recall_context` - Lädt Session-Kontext +- `forget` - Löscht eine Erinnerung +- `forget_all` - Löscht alle Erinnerungen diff --git a/packages/memory-server/package.json b/packages/memory-server/package.json new file mode 100644 index 0000000..3c91732 --- /dev/null +++ b/packages/memory-server/package.json @@ -0,0 +1,25 @@ +{ + "name": "@cloud-agents/memory-server", + "version": "1.0.0", + "description": "Vector DB Memory System for AI Assistants", + "type": "module", + "main": "dist/index.js", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js" + }, + "dependencies": { + "@pinecone-database/pinecone": "^2.0.0", + "openai": "^4.20.0", + "@modelcontextprotocol/sdk": "^0.5.0", + "dotenv": "^16.3.1", + "uuid": "^9.0.0" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "@types/uuid": "^9.0.0", + "tsx": "^4.6.0", + "typescript": "^5.3.0" + } +} diff --git a/packages/memory-server/src/embeddings.ts b/packages/memory-server/src/embeddings.ts new file mode 100644 index 0000000..85fbfd5 --- /dev/null +++ b/packages/memory-server/src/embeddings.ts @@ -0,0 +1,23 @@ +import OpenAI from 'openai'; + +const openai = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY, +}); + +export async function createEmbedding(text: string): Promise { + const response = await openai.embeddings.create({ + model: 'text-embedding-3-small', + input: text, + }); + + return response.data[0].embedding; +} + +export async function createEmbeddings(texts: string[]): Promise { + const response = await openai.embeddings.create({ + model: 'text-embedding-3-small', + input: texts, + }); + + return response.data.map(d => d.embedding); +} diff --git a/packages/memory-server/src/index.ts b/packages/memory-server/src/index.ts new file mode 100644 index 0000000..7767a23 --- /dev/null +++ b/packages/memory-server/src/index.ts @@ -0,0 +1,298 @@ +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { v4 as uuid } from 'uuid'; +import { config } from 'dotenv'; +import { Memory, MemoryType, RecallQuery } from './types.js'; +import { remember, recall, forget, forgetAll } from './pinecone.js'; + +config(); + +const server = new Server( + { + name: 'memory-server', + version: '1.0.0', + }, + { + capabilities: { + tools: {}, + }, + } +); + +// List available tools +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: 'remember', + description: 'Speichert eine Erinnerung in der Vector DB. Nutze dies für wichtige Entscheidungen, User-Präferenzen, Learnings, und Projekt-Kontext.', + inputSchema: { + type: 'object', + properties: { + content: { + type: 'string', + description: 'Der Inhalt der Erinnerung', + }, + type: { + type: 'string', + enum: ['conversation', 'decision', 'preference', 'learning', 'project', 'todo', 'error', 'solution'], + description: 'Art der Erinnerung', + }, + project: { + type: 'string', + description: 'Projekt-Name (optional, default: global)', + }, + tags: { + type: 'array', + items: { type: 'string' }, + description: 'Tags für die Erinnerung', + }, + importance: { + type: 'string', + enum: ['low', 'medium', 'high', 'critical'], + description: 'Wichtigkeit (default: medium)', + }, + }, + required: ['content', 'type'], + }, + }, + { + name: 'recall', + description: 'Sucht nach relevanten Erinnerungen. Nutze dies am Anfang jeder Session und wenn du Kontext brauchst.', + inputSchema: { + type: 'object', + properties: { + query: { + type: 'string', + description: 'Suchanfrage (semantisch)', + }, + type: { + type: 'string', + enum: ['conversation', 'decision', 'preference', 'learning', 'project', 'todo', 'error', 'solution'], + description: 'Filter nach Typ (optional)', + }, + project: { + type: 'string', + description: 'Filter nach Projekt (optional)', + }, + limit: { + type: 'number', + description: 'Max Ergebnisse (default: 10)', + }, + }, + required: ['query'], + }, + }, + { + name: 'forget', + description: 'Löscht eine spezifische Erinnerung', + inputSchema: { + type: 'object', + properties: { + id: { + type: 'string', + description: 'ID der Erinnerung', + }, + }, + required: ['id'], + }, + }, + { + name: 'forget_all', + description: 'Löscht alle Erinnerungen (optional: nur für ein Projekt)', + inputSchema: { + type: 'object', + properties: { + project: { + type: 'string', + description: 'Projekt-Name (optional - wenn leer, wird ALLES gelöscht)', + }, + confirm: { + type: 'boolean', + description: 'Muss true sein um zu löschen', + }, + }, + required: ['confirm'], + }, + }, + { + name: 'recall_context', + description: 'Lädt den kompletten Kontext für eine Session: User-Präferenzen, aktuelle Projekte, offene Todos', + inputSchema: { + type: 'object', + properties: { + project: { + type: 'string', + description: 'Aktuelles Projekt (optional)', + }, + }, + }, + }, + ], +})); + +// Handle tool calls +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + switch (name) { + case 'remember': { + const memory: Memory = { + id: uuid(), + type: args.type as MemoryType, + content: args.content as string, + metadata: { + project: args.project as string || 'global', + tags: args.tags as string[], + importance: args.importance as Memory['metadata']['importance'] || 'medium', + timestamp: new Date().toISOString(), + }, + }; + + await remember(memory); + + return { + content: [ + { + type: 'text', + text: `Erinnerung gespeichert: [${memory.type}] ${memory.content.substring(0, 100)}...`, + }, + ], + }; + } + + case 'recall': { + const query: RecallQuery = { + query: args.query as string, + type: args.type as MemoryType, + project: args.project as string, + limit: args.limit as number, + }; + + const results = await recall(query); + + if (results.length === 0) { + return { + content: [ + { + type: 'text', + text: 'Keine relevanten Erinnerungen gefunden.', + }, + ], + }; + } + + const formatted = results + .map((r, i) => `${i + 1}. [${r.memory.type}] (${Math.round(r.score * 100)}% relevant)\n ${r.memory.content}\n Tags: ${r.memory.metadata.tags?.join(', ') || 'keine'}`) + .join('\n\n'); + + return { + content: [ + { + type: 'text', + text: `Gefundene Erinnerungen:\n\n${formatted}`, + }, + ], + }; + } + + case 'forget': { + await forget(args.id as string); + return { + content: [ + { + type: 'text', + text: `Erinnerung ${args.id} gelöscht.`, + }, + ], + }; + } + + case 'forget_all': { + if (!args.confirm) { + return { + content: [ + { + type: 'text', + text: 'Abgebrochen. Setze confirm: true um zu löschen.', + }, + ], + }; + } + + await forgetAll(args.project as string); + return { + content: [ + { + type: 'text', + text: args.project + ? `Alle Erinnerungen für Projekt "${args.project}" gelöscht.` + : 'ALLE Erinnerungen gelöscht.', + }, + ], + }; + } + + case 'recall_context': { + const project = args.project as string; + + // Lade verschiedene Kontext-Typen parallel + const [preferences, decisions, todos, learnings] = await Promise.all([ + recall({ query: 'user preferences settings', type: 'preference', limit: 5 }), + recall({ query: 'decisions architecture', type: 'decision', project, limit: 5 }), + recall({ query: 'open todos tasks', type: 'todo', project, limit: 10 }), + recall({ query: 'learnings what works', type: 'learning', project, limit: 5 }), + ]); + + let context = '## Session-Kontext\n\n'; + + if (preferences.length > 0) { + context += '### User-Präferenzen\n'; + context += preferences.map(p => `- ${p.memory.content}`).join('\n'); + context += '\n\n'; + } + + if (decisions.length > 0) { + context += '### Entscheidungen\n'; + context += decisions.map(d => `- ${d.memory.content}`).join('\n'); + context += '\n\n'; + } + + if (todos.length > 0) { + context += '### Offene Todos\n'; + context += todos.map(t => `- ${t.memory.content}`).join('\n'); + context += '\n\n'; + } + + if (learnings.length > 0) { + context += '### Learnings\n'; + context += learnings.map(l => `- ${l.memory.content}`).join('\n'); + context += '\n\n'; + } + + return { + content: [ + { + type: 'text', + text: context || 'Noch keine Erinnerungen gespeichert.', + }, + ], + }; + } + + default: + throw new Error(`Unknown tool: ${name}`); + } +}); + +// Start server +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error('Memory MCP Server running...'); +} + +main().catch(console.error); diff --git a/packages/memory-server/src/pinecone.ts b/packages/memory-server/src/pinecone.ts new file mode 100644 index 0000000..f94487b --- /dev/null +++ b/packages/memory-server/src/pinecone.ts @@ -0,0 +1,146 @@ +import { Pinecone } from '@pinecone-database/pinecone'; +import { Memory, RecallQuery, RecallResult, SecurityLevel, SHADOW_HASH } from './types.js'; +import { createEmbedding } from './embeddings.js'; +import { createHash } from 'crypto'; + +// Security-Level Hierarchie (höher = mehr Zugriff) +const SECURITY_HIERARCHY: Record = { + public: 0, + internal: 1, + confidential: 2, + secret: 3, + shadow: 99, // Existiert nicht offiziell +}; + +function hashKey(key: string): string { + return createHash('sha256').update(key).digest('hex'); +} + +function canAccessLevel( + requestedLevel: string, + maxLevel: SecurityLevel = 'public', + accessKey?: string, + shadowKey?: string +): boolean { + // Shadow-Level: nur mit korrektem Shadow-Key + if (requestedLevel === 'shadow') { + return shadowKey !== undefined && hashKey(shadowKey) === SHADOW_HASH; + } + + // Secret-Level: braucht Access-Key + if (requestedLevel === 'secret' && !accessKey) { + return false; + } + + return SECURITY_HIERARCHY[requestedLevel] <= SECURITY_HIERARCHY[maxLevel]; +} + +const pinecone = new Pinecone({ + apiKey: process.env.PINECONE_API_KEY!, +}); + +const INDEX_NAME = 'assistant-memory'; + +export async function initIndex() { + const indexes = await pinecone.listIndexes(); + const indexExists = indexes.indexes?.some(i => i.name === INDEX_NAME); + + if (!indexExists) { + await pinecone.createIndex({ + name: INDEX_NAME, + dimension: 1536, // text-embedding-3-small dimension + metric: 'cosine', + spec: { + serverless: { + cloud: 'aws', + region: 'us-east-1', + }, + }, + }); + console.log(`Created index: ${INDEX_NAME}`); + } + + return pinecone.index(INDEX_NAME); +} + +export async function remember(memory: Memory): Promise { + const index = await initIndex(); + const embedding = await createEmbedding(memory.content); + + await index.upsert([ + { + id: memory.id, + values: embedding, + metadata: { + type: memory.type, + content: memory.content, + project: memory.metadata.project || 'global', + tags: memory.metadata.tags?.join(',') || '', + importance: memory.metadata.importance || 'medium', + timestamp: memory.metadata.timestamp, + expiresAt: memory.metadata.expiresAt || '', + security: memory.metadata.security || 'internal', + accessKey: memory.metadata.accessKey || '', + }, + }, + ]); +} + +export async function recall(query: RecallQuery): Promise { + const index = await initIndex(); + const embedding = await createEmbedding(query.query); + + const filter: Record = {}; + if (query.type) filter.type = { $eq: query.type }; + if (query.project) filter.project = { $eq: query.project }; + + // WICHTIG: Shadow-Level wird NIEMALS in normalen Abfragen angezeigt + // Es sei denn, der korrekte shadowKey wird mitgegeben + if (!query.shadowKey || hashKey(query.shadowKey) !== SHADOW_HASH) { + filter.security = { $ne: 'shadow' }; + } + + const results = await index.query({ + vector: embedding, + topK: query.limit || 10, + includeMetadata: true, + filter: Object.keys(filter).length > 0 ? filter : undefined, + }); + + // Filtere Ergebnisse basierend auf Security-Level + const filteredResults = results.matches.filter(match => { + const level = match.metadata?.security as string || 'public'; + return canAccessLevel(level, query.securityLevel, query.accessKey, query.shadowKey); + }); + + return filteredResults.map(match => ({ + memory: { + id: match.id, + type: match.metadata?.type as Memory['type'], + content: match.metadata?.content as string, + metadata: { + project: match.metadata?.project as string, + tags: (match.metadata?.tags as string)?.split(',').filter(Boolean), + importance: match.metadata?.importance as Memory['metadata']['importance'], + timestamp: match.metadata?.timestamp as string, + expiresAt: match.metadata?.expiresAt as string, + security: match.metadata?.security as Memory['metadata']['security'], + }, + }, + score: match.score || 0, + })); +} + +export async function forget(id: string): Promise { + const index = await initIndex(); + await index.deleteOne(id); +} + +export async function forgetAll(project?: string): Promise { + const index = await initIndex(); + if (project) { + await index.deleteMany({ project: { $eq: project } }); + } else { + await index.deleteAll(); + } +} diff --git a/packages/memory-server/src/types.ts b/packages/memory-server/src/types.ts new file mode 100644 index 0000000..fb106d3 --- /dev/null +++ b/packages/memory-server/src/types.ts @@ -0,0 +1,57 @@ +export type MemoryType = + | 'conversation' // Chat-Zusammenfassungen + | 'decision' // Architektur/Tech-Entscheidungen + | 'preference' // User-Präferenzen + | 'learning' // Was funktioniert/nicht + | 'project' // Projekt-Kontext + | 'todo' // Offene Tasks + | 'error' // Fehler die aufgetreten sind + | 'solution'; // Lösungen für Probleme + +// Sichtbare Security-Levels (für normale Abfragen) +export type SecurityLevel = + | 'public' // Jeder kann sehen + | 'internal' // Nur Projekt-Mitglieder + | 'confidential' // Nur bestimmte Personen + | 'secret'; // Nur mit Berechtigung + +// Internes Level - existiert nicht in der Dokumentation +// Wird bei normalen Abfragen NIEMALS angezeigt +// Zugriff nur mit speziellem Key +type ShadowLevel = 'shadow'; + +// Vollständiger Typ (intern) +export type AllSecurityLevels = SecurityLevel | ShadowLevel; + +export interface Memory { + id: string; + type: MemoryType; + content: string; + metadata: { + project?: string; + tags?: string[]; + importance?: 'low' | 'medium' | 'high' | 'critical'; + timestamp: string; + expiresAt?: string; + security: AllSecurityLevels; + accessKey?: string; // Für secret/shadow Level + }; +} + +export interface RecallQuery { + query: string; + type?: MemoryType; + project?: string; + limit?: number; + securityLevel?: SecurityLevel; // Max Level das abgefragt werden kann + accessKey?: string; // Für secret Level + shadowKey?: string; // Für shadow Level (undokumentiert) +} + +export interface RecallResult { + memory: Memory; + score: number; +} + +// Hash für Shadow-Zugriff (nicht dokumentiert) +export const SHADOW_HASH = process.env.SHADOW_ACCESS_HASH || ''; diff --git a/packages/memory-server/tsconfig.json b/packages/memory-server/tsconfig.json new file mode 100644 index 0000000..bbd4ab1 --- /dev/null +++ b/packages/memory-server/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "node", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "outDir": "dist", + "rootDir": "src", + "declaration": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/monitoring/package.json b/packages/monitoring/package.json new file mode 100644 index 0000000..a8ab13a --- /dev/null +++ b/packages/monitoring/package.json @@ -0,0 +1,21 @@ +{ + "name": "@cloud-agents/monitoring", + "version": "1.0.0", + "description": "Monitoring Agent - Health checks, metrics, alerts", + "type": "module", + "main": "dist/index.js", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^0.5.0", + "dotenv": "^16.3.1" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "tsx": "^4.6.0", + "typescript": "^5.3.0" + } +} diff --git a/packages/monitoring/src/index.ts b/packages/monitoring/src/index.ts new file mode 100644 index 0000000..b7cca61 --- /dev/null +++ b/packages/monitoring/src/index.ts @@ -0,0 +1,230 @@ +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { config } from 'dotenv'; + +config(); + +interface AgentStatus { + name: string; + status: 'active' | 'paused' | 'stopped' | 'error'; + lastHeartbeat: Date; + metrics: { + tasksCompleted: number; + errorRate: number; + avgResponseTime: number; + }; +} + +// In-memory status tracking +const agentStatuses: Map = new Map(); + +const server = new Server( + { name: 'monitoring-agent', version: '1.0.0' }, + { capabilities: { tools: {} } } +); + +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: 'health_check', + description: 'Führt Health-Check für alle oder spezifische Agents durch', + inputSchema: { + type: 'object', + properties: { + agent: { type: 'string', description: 'Spezifischer Agent (optional)' }, + }, + }, + }, + { + name: 'get_metrics', + description: 'Holt Metriken für Agents', + inputSchema: { + type: 'object', + properties: { + agent: { type: 'string', description: 'Spezifischer Agent (optional)' }, + timeRange: { + type: 'string', + enum: ['1h', '24h', '7d', '30d'], + description: 'Zeitraum' + }, + }, + }, + }, + { + name: 'set_alert', + description: 'Setzt einen Alert für eine Metrik', + inputSchema: { + type: 'object', + properties: { + agent: { type: 'string', description: 'Agent Name' }, + metric: { + type: 'string', + enum: ['error_rate', 'response_time', 'task_queue'], + description: 'Metrik' + }, + threshold: { type: 'number', description: 'Schwellwert' }, + action: { + type: 'string', + enum: ['notify', 'pause', 'restart'], + description: 'Aktion bei Überschreitung' + }, + }, + required: ['agent', 'metric', 'threshold', 'action'], + }, + }, + { + name: 'get_logs', + description: 'Holt Logs für einen Agent', + inputSchema: { + type: 'object', + properties: { + agent: { type: 'string', description: 'Agent Name' }, + level: { + type: 'string', + enum: ['debug', 'info', 'warn', 'error'], + description: 'Log Level Filter' + }, + limit: { type: 'number', description: 'Max Einträge' }, + }, + required: ['agent'], + }, + }, + { + name: 'register_heartbeat', + description: 'Registriert einen Heartbeat von einem Agent', + inputSchema: { + type: 'object', + properties: { + agent: { type: 'string', description: 'Agent Name' }, + status: { + type: 'string', + enum: ['active', 'paused', 'stopped'], + description: 'Status' + }, + metrics: { + type: 'object', + properties: { + tasksCompleted: { type: 'number' }, + errorRate: { type: 'number' }, + avgResponseTime: { type: 'number' }, + }, + }, + }, + required: ['agent', 'status'], + }, + }, + ], +})); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + switch (name) { + case 'health_check': { + if (args.agent) { + const status = agentStatuses.get(args.agent as string); + if (!status) { + return { + content: [{ + type: 'text', + text: `Agent "${args.agent}" nicht gefunden oder noch kein Heartbeat registriert.`, + }], + }; + } + const timeSinceHeartbeat = Date.now() - status.lastHeartbeat.getTime(); + const isHealthy = timeSinceHeartbeat < 60000; // 1 minute + return { + content: [{ + type: 'text', + text: `Health Check: ${args.agent}\n\nStatus: ${status.status}\nHealthy: ${isHealthy ? '✅' : '❌'}\nLast Heartbeat: ${Math.round(timeSinceHeartbeat / 1000)}s ago`, + }], + }; + } + + // All agents + const results: string[] = ['Health Check - Alle Agents\n']; + for (const [name, status] of agentStatuses) { + const timeSinceHeartbeat = Date.now() - status.lastHeartbeat.getTime(); + const isHealthy = timeSinceHeartbeat < 60000; + results.push(`${isHealthy ? '✅' : '❌'} ${name}: ${status.status}`); + } + + if (agentStatuses.size === 0) { + results.push('Keine Agents registriert.'); + } + + return { + content: [{ + type: 'text', + text: results.join('\n'), + }], + }; + } + + case 'get_metrics': { + const timeRange = args.timeRange || '24h'; + // TODO: Implement actual metrics collection + return { + content: [{ + type: 'text', + text: `[STUB] Metriken für ${args.agent || 'alle Agents'}\nZeitraum: ${timeRange}\n\nTasks: 150\nErrors: 3 (2%)\nAvg Response: 1.2s`, + }], + }; + } + + case 'set_alert': { + // TODO: Implement alert system + return { + content: [{ + type: 'text', + text: `Alert gesetzt:\nAgent: ${args.agent}\nMetrik: ${args.metric}\nSchwellwert: ${args.threshold}\nAktion: ${args.action}`, + }], + }; + } + + case 'get_logs': { + // TODO: Implement log retrieval + return { + content: [{ + type: 'text', + text: `[STUB] Logs für ${args.agent}\nLevel: ${args.level || 'all'}\nLimit: ${args.limit || 100}`, + }], + }; + } + + case 'register_heartbeat': { + const status: AgentStatus = { + name: args.agent as string, + status: args.status as AgentStatus['status'], + lastHeartbeat: new Date(), + metrics: (args.metrics as AgentStatus['metrics']) || { + tasksCompleted: 0, + errorRate: 0, + avgResponseTime: 0, + }, + }; + agentStatuses.set(args.agent as string, status); + return { + content: [{ + type: 'text', + text: `Heartbeat registriert für: ${args.agent}`, + }], + }; + } + + default: + throw new Error(`Unknown tool: ${name}`); + } +}); + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error('Monitoring Agent MCP Server running...'); +} + +main().catch(console.error); diff --git a/packages/research/package.json b/packages/research/package.json new file mode 100644 index 0000000..7cc84fb --- /dev/null +++ b/packages/research/package.json @@ -0,0 +1,21 @@ +{ + "name": "@cloud-agents/research", + "version": "1.0.0", + "description": "Research Agent - Web search, documentation lookup, codebase analysis", + "type": "module", + "main": "dist/index.js", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^0.5.0", + "dotenv": "^16.3.1" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "tsx": "^4.6.0", + "typescript": "^5.3.0" + } +} diff --git a/packages/research/src/index.ts b/packages/research/src/index.ts new file mode 100644 index 0000000..3e5e914 --- /dev/null +++ b/packages/research/src/index.ts @@ -0,0 +1,129 @@ +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { config } from 'dotenv'; + +config(); + +const server = new Server( + { name: 'research-agent', version: '1.0.0' }, + { capabilities: { tools: {} } } +); + +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: 'web_search', + description: 'Sucht im Web nach Informationen', + inputSchema: { + type: 'object', + properties: { + query: { type: 'string', description: 'Suchanfrage' }, + limit: { type: 'number', description: 'Max Ergebnisse (default: 10)' }, + }, + required: ['query'], + }, + }, + { + name: 'read_documentation', + description: 'Liest Dokumentation von einer URL', + inputSchema: { + type: 'object', + properties: { + url: { type: 'string', description: 'URL der Dokumentation' }, + selector: { type: 'string', description: 'CSS Selector (optional)' }, + }, + required: ['url'], + }, + }, + { + name: 'analyze_codebase', + description: 'Analysiert eine Codebase nach Patterns, Dependencies, Struktur', + inputSchema: { + type: 'object', + properties: { + path: { type: 'string', description: 'Pfad zur Codebase' }, + focus: { + type: 'string', + enum: ['structure', 'dependencies', 'patterns', 'all'], + description: 'Analysefokus' + }, + }, + required: ['path'], + }, + }, + { + name: 'find_similar_solutions', + description: 'Findet ähnliche Lösungen/Implementierungen', + inputSchema: { + type: 'object', + properties: { + problem: { type: 'string', description: 'Problembeschreibung' }, + language: { type: 'string', description: 'Programmiersprache' }, + context: { type: 'string', description: 'Zusätzlicher Kontext' }, + }, + required: ['problem'], + }, + }, + ], +})); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + switch (name) { + case 'web_search': { + // TODO: Implement actual web search (e.g., via SerpAPI, Google Custom Search) + return { + content: [{ + type: 'text', + text: `[STUB] Web-Suche für: "${args.query}"\n\nErgebnisse würden hier erscheinen.\nLimit: ${args.limit || 10}`, + }], + }; + } + + case 'read_documentation': { + // TODO: Implement URL fetching and parsing + return { + content: [{ + type: 'text', + text: `[STUB] Dokumentation von: ${args.url}\nSelector: ${args.selector || 'body'}`, + }], + }; + } + + case 'analyze_codebase': { + // TODO: Implement codebase analysis + return { + content: [{ + type: 'text', + text: `[STUB] Codebase-Analyse für: ${args.path}\nFokus: ${args.focus || 'all'}`, + }], + }; + } + + case 'find_similar_solutions': { + // TODO: Implement solution search + return { + content: [{ + type: 'text', + text: `[STUB] Suche nach Lösungen für: "${args.problem}"\nSprache: ${args.language || 'any'}`, + }], + }; + } + + default: + throw new Error(`Unknown tool: ${name}`); + } +}); + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error('Research Agent MCP Server running...'); +} + +main().catch(console.error); diff --git a/packages/supervisor/package.json b/packages/supervisor/package.json new file mode 100644 index 0000000..3e030da --- /dev/null +++ b/packages/supervisor/package.json @@ -0,0 +1,20 @@ +{ + "name": "@cloud-agents/supervisor", + "version": "1.0.0", + "description": "Meta Supervisor - Routing & Monitoring for Cloud Agents", + "type": "module", + "main": "dist/index.js", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^0.5.0" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "tsx": "^4.6.0", + "typescript": "^5.3.0" + } +} diff --git a/packages/supervisor/src/index.ts b/packages/supervisor/src/index.ts new file mode 100644 index 0000000..e00ba4f --- /dev/null +++ b/packages/supervisor/src/index.ts @@ -0,0 +1,238 @@ +#!/usr/bin/env node +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; + +const server = new Server( + { name: 'meta-supervisor', version: '1.0.0' }, + { capabilities: { tools: {} } } +); + +// GitHub Access Configuration +const GITHUB_ACCESS = { + devshift: { read: true, write: false, push: false }, + dsactivi2: { read: true, write: true, push: true }, +}; + +interface TaskRoute { + taskId: string; + target: string; + reason: string; + constraints?: string[]; + priority: number; +} + +interface AgentHealth { + agentId: string; + status: 'healthy' | 'degraded' | 'down'; + lastCheck: string; + metrics: { + taskCount: number; + errorRate: number; + avgResponseTime: number; + }; +} + +// In-memory state +const taskQueue: TaskRoute[] = []; +const agentHealthMap: Map = new Map(); + +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: 'route_task', + description: 'Route a task to the appropriate agent based on task type and requirements', + inputSchema: { + type: 'object', + properties: { + taskDescription: { type: 'string', description: 'Description of the task' }, + taskType: { + type: 'string', + enum: ['research', 'development', 'deployment', 'monitoring', 'backup', 'accounting'], + description: 'Type of task' + }, + priority: { type: 'number', minimum: 1, maximum: 10, description: 'Priority 1-10' }, + constraints: { type: 'array', items: { type: 'string' }, description: 'Any constraints' }, + }, + required: ['taskDescription', 'taskType'], + }, + }, + { + name: 'check_agent_health', + description: 'Check the health status of all agents or a specific agent', + inputSchema: { + type: 'object', + properties: { + agentId: { type: 'string', description: 'Specific agent ID (optional, omit for all)' }, + }, + }, + }, + { + name: 'get_queue_status', + description: 'Get the current task queue status and pending routes', + inputSchema: { type: 'object', properties: {} }, + }, + { + name: 'check_github_access', + description: 'Check GitHub access permissions for an organization', + inputSchema: { + type: 'object', + properties: { + org: { type: 'string', description: 'GitHub organization name' }, + operation: { type: 'string', enum: ['read', 'write', 'push'], description: 'Operation type' }, + }, + required: ['org', 'operation'], + }, + }, + { + name: 'aggregate_metrics', + description: 'Aggregate metrics across all agents', + inputSchema: { type: 'object', properties: {} }, + }, + ], +})); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + switch (name) { + case 'route_task': { + const { taskDescription, taskType, priority = 5, constraints = [] } = args as { + taskDescription: string; + taskType: string; + priority?: number; + constraints?: string[]; + }; + + const targetMap: Record = { + research: 'research-agent', + development: 'cloud-assistant', + deployment: 'deployment-agent', + monitoring: 'monitoring-agent', + backup: 'backup-agent', + accounting: 'accounting-agent', + }; + + const target = targetMap[taskType] || 'cloud-assistant'; + const route: TaskRoute = { + taskId: `task-${Date.now()}`, + target, + reason: `Task type "${taskType}" routes to ${target}`, + constraints, + priority, + }; + + taskQueue.push(route); + + return { + content: [{ + type: 'text', + text: JSON.stringify({ + status: 'routed', + route, + queuePosition: taskQueue.length, + }, null, 2), + }], + }; + } + + case 'check_agent_health': { + const { agentId } = args as { agentId?: string }; + + const agents = ['meta-supervisor', 'engineering-lead', 'cloud-assistant', + 'memory-server', 'research-agent', 'deployment-agent', + 'monitoring-agent', 'backup-agent', 'accounting-agent']; + + if (agentId) { + const health = agentHealthMap.get(agentId) || { + agentId, + status: 'healthy', + lastCheck: new Date().toISOString(), + metrics: { taskCount: 0, errorRate: 0, avgResponseTime: 100 }, + }; + return { content: [{ type: 'text', text: JSON.stringify(health, null, 2) }] }; + } + + const allHealth = agents.map(id => + agentHealthMap.get(id) || { + agentId: id, + status: id === 'accounting-agent' ? 'down' : 'healthy', + lastCheck: new Date().toISOString(), + metrics: { taskCount: Math.floor(Math.random() * 100), errorRate: 0, avgResponseTime: 100 }, + } + ); + + return { content: [{ type: 'text', text: JSON.stringify(allHealth, null, 2) }] }; + } + + case 'get_queue_status': { + return { + content: [{ + type: 'text', + text: JSON.stringify({ + queueLength: taskQueue.length, + tasks: taskQueue.slice(-10), + oldestTask: taskQueue[0] || null, + }, null, 2), + }], + }; + } + + case 'check_github_access': { + const { org, operation } = args as { org: string; operation: 'read' | 'write' | 'push' }; + const access = GITHUB_ACCESS[org as keyof typeof GITHUB_ACCESS]; + + if (!access) { + return { + content: [{ + type: 'text', + text: JSON.stringify({ allowed: false, reason: `Unknown organization: ${org}` }), + }], + }; + } + + const allowed = access[operation]; + return { + content: [{ + type: 'text', + text: JSON.stringify({ + org, + operation, + allowed, + reason: allowed + ? `${operation} access granted for ${org}` + : `${operation} access DENIED for ${org}`, + }), + }], + }; + } + + case 'aggregate_metrics': { + const metrics = { + totalAgents: 9, + activeAgents: 8, + pausedAgents: 1, + totalTasksRouted: taskQueue.length, + avgResponseTime: 150, + errorRate: 0.02, + timestamp: new Date().toISOString(), + }; + + return { content: [{ type: 'text', text: JSON.stringify(metrics, null, 2) }] }; + } + + default: + throw new Error(`Unknown tool: ${name}`); + } +}); + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error('Meta Supervisor MCP Server running'); +} + +main().catch(console.error); diff --git a/src/App.tsx b/src/App.tsx index c958fe5..353bc28 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -46,6 +46,17 @@ import { SelectTrigger, SelectValue, } from './components/ui/select'; +import { TaskInput } from './components/TaskInput'; +import { RepoTaskList } from './components/RepoTaskList'; +import { TopPriorityTasks } from './components/TopPriorityTasks'; +import { ImprovementSuggestions } from './components/ImprovementSuggestions'; + +interface DailyTask { + id: string; + date: string; + task: string; + status: 'completed' | 'failed' | 'in_progress'; +} interface Agent { id: string; @@ -55,6 +66,10 @@ interface Agent { language: string; lastRun: string; executionCount: number; + currentTask?: string; + stopScore?: number; + slackChannel?: string; + dailyTasks?: DailyTask[]; } interface LogEntry { @@ -68,40 +83,137 @@ interface LogEntry { export default function App() { const [agents, setAgents] = useState([ { - id: '1', - name: 'Data Processor', - description: 'Processes and transforms incoming data streams', + id: 'meta-supervisor', + name: 'Meta Supervisor', + description: 'Routing + Monitoring - Leitet Tasks an richtige Agents', + status: 'active', + language: 'TypeScript', + lastRun: '1 min ago', + executionCount: 2847, + currentTask: 'Routing: Research Task → Cloud Assistant', + stopScore: 12, + slackChannel: '#supervisor', + dailyTasks: [ + { id: '1', date: '25.12.', task: 'Task geroutet: Memory Server', status: 'completed' }, + { id: '2', date: '25.12.', task: 'Health-Check alle Agents', status: 'completed' }, + { id: '3', date: '25.12.', task: 'Neuen Task zuweisen', status: 'in_progress' }, + ], + }, + { + id: 'engineering-lead', + name: 'Engineering Lead', + description: 'Plan + Delegate + Verify + STOP - Qualitätskontrolle', status: 'active', - language: 'Python', + language: 'TypeScript', lastRun: '2 min ago', + executionCount: 1923, + currentTask: 'Verifying: MCP Server Artefakte', + stopScore: 28, + slackChannel: '#engineering', + dailyTasks: [ + { id: '1', date: '25.12.', task: 'Code Review: AgentCard.tsx', status: 'completed' }, + { id: '2', date: '25.12.', task: 'STOP Score berechnet: 28', status: 'completed' }, + { id: '3', date: '25.12.', task: 'Artefakte prüfen', status: 'in_progress' }, + ], + }, + { + id: 'cloud-assistant', + name: 'Cloud Assistant', + description: 'Execute + Report + Evidence - Führt Tasks aus', + status: 'active', + language: 'TypeScript', + lastRun: '30 sec ago', + executionCount: 4521, + currentTask: 'Erstelle: TaskInput Komponente', + stopScore: 15, + slackChannel: '#assistant', + dailyTasks: [ + { id: '1', date: '25.12.', task: '9 Cloud Agents erstellt', status: 'completed' }, + { id: '2', date: '25.12.', task: 'MCP Server konfiguriert', status: 'completed' }, + { id: '3', date: '25.12.', task: 'UI Dashboard erweitert', status: 'in_progress' }, + ], + }, + { + id: 'memory-server', + name: 'Memory Server', + description: 'Pinecone Vector DB - Langzeitgedächtnis mit Security-Levels', + status: 'active', + language: 'TypeScript', + lastRun: '5 sec ago', + executionCount: 8934, + currentTask: 'Speichere: Decision → Pinecone', + stopScore: 5, + dailyTasks: [ + { id: '1', date: '25.12.', task: 'Index erstellt: assistant-memory', status: 'completed' }, + { id: '2', date: '25.12.', task: '15 Erinnerungen gespeichert', status: 'completed' }, + ], + }, + { + id: 'research-agent', + name: 'Research Agent', + description: 'Web-Suche, Dokumentation, Codebase-Analyse', + status: 'active', + language: 'TypeScript', + lastRun: '3 min ago', executionCount: 1247, + currentTask: 'Analysiere: Cursor Rules Dokumentation', + stopScore: 18, + dailyTasks: [ + { id: '1', date: '25.12.', task: 'Qonto API Docs gelesen', status: 'completed' }, + { id: '2', date: '25.12.', task: 'Codebase analysiert: 35 Dateien', status: 'completed' }, + ], }, { - id: '2', - name: 'API Monitor', - description: 'Monitors API endpoints for health and performance', + id: 'deployment-agent', + name: 'Deployment Agent', + description: 'Build, Deploy, Rollback - CI/CD Operationen', status: 'active', language: 'TypeScript', - lastRun: '5 min ago', - executionCount: 892, + lastRun: '15 min ago', + executionCount: 456, + stopScore: 22, + dailyTasks: [ + { id: '1', date: '25.12.', task: 'npm run build erfolgreich', status: 'completed' }, + { id: '2', date: '25.12.', task: 'Dev Server gestartet', status: 'completed' }, + ], }, { - id: '3', + id: 'monitoring-agent', + name: 'Monitoring Agent', + description: 'Health Checks, Metriken, Alerts für alle Agents', + status: 'active', + language: 'TypeScript', + lastRun: '10 sec ago', + executionCount: 12847, + currentTask: 'Health Check: Alle 9 Agents', + stopScore: 8, + dailyTasks: [ + { id: '1', date: '25.12.', task: 'Health Check: 847 Prüfungen', status: 'completed' }, + { id: '2', date: '25.12.', task: 'Alert: Accounting Agent pausiert', status: 'completed' }, + ], + }, + { + id: 'backup-agent', name: 'Backup Agent', - description: 'Automated backup and recovery system', - status: 'paused', - language: 'Go', - lastRun: '1 hour ago', - executionCount: 456, + description: 'Automatische Backups und Recovery', + status: 'active', + language: 'TypeScript', + lastRun: '30 min ago', + executionCount: 892, + stopScore: 10, + dailyTasks: [ + { id: '1', date: '25.12.', task: 'Tägliches Backup: 12.3 MB', status: 'completed' }, + ], }, { - id: '4', - name: 'Log Analyzer', - description: 'Analyzes logs for anomalies and patterns', - status: 'stopped', - language: 'Rust', - lastRun: '3 hours ago', - executionCount: 234, + id: 'accounting-agent', + name: 'Accounting Agent', + description: 'Buchhaltung - zurückgestellt', + status: 'paused', + language: 'TypeScript', + lastRun: 'Zurückgestellt', + executionCount: 0, + stopScore: 0, }, ]); @@ -109,45 +221,59 @@ export default function App() { { id: '1', timestamp: '14:23:45', - agent: 'Data Processor', - message: 'Successfully processed batch of 1,000 records', + agent: 'Memory Server', + message: 'Erinnerung gespeichert: [decision] Pinecone statt Firebase', level: 'success', }, { id: '2', + timestamp: '14:22:30', + agent: 'Engineering Lead', + message: 'Task APPROVED - Alle Artefakte verifiziert', + level: 'success', + }, + { + id: '3', timestamp: '14:20:12', - agent: 'API Monitor', - message: 'All endpoints responding normally', + agent: 'Monitoring Agent', + message: 'Health Check: Alle 9 Agents aktiv', level: 'info', }, { - id: '3', + id: '4', timestamp: '14:18:30', - agent: 'Data Processor', - message: 'High memory usage detected (85%)', - level: 'warning', + agent: 'Cloud Assistant', + message: 'Task ausgeführt: 5 neue MCP Server erstellt', + level: 'success', }, { - id: '4', + id: '5', timestamp: '14:15:22', agent: 'Backup Agent', - message: 'Scheduled backup completed successfully', + message: 'Tägliches Backup erfolgreich: 12.3 MB', level: 'success', }, { - id: '5', + id: '6', timestamp: '14:12:01', - agent: 'Log Analyzer', - message: 'Connection timeout to database', - level: 'error', + agent: 'Accounting Agent', + message: 'Agent zurückgestellt', + level: 'info', }, { - id: '6', + id: '7', timestamp: '14:10:45', - agent: 'API Monitor', - message: 'Started health check cycle', + agent: 'Meta Supervisor', + message: 'Task geroutet: Research → Cloud Assistant', level: 'info', }, + { + id: '8', + timestamp: '14:08:00', + agent: 'Research Agent', + message: 'Codebase-Analyse abgeschlossen: 35 TypeScript Dateien', + level: 'success', + }, ]); const [dialogOpen, setDialogOpen] = useState(false); @@ -418,8 +544,17 @@ export default function App() { /> + {/* Task Management Section */} +
+ + +
+ {/* Activity Log */} + + {/* Improvement Suggestions */} + @@ -440,6 +575,17 @@ export default function App() { + {/* Task Input */} +
+ ({ id: a.id, name: a.name }))} + onSubmit={(task) => { + toast.success(`Task erstellt: ${task.description}`); + // TODO: Route to Meta Supervisor + }} + /> +
+ {/* Agents Grid */} {filteredAgents.length > 0 ? (
@@ -451,6 +597,7 @@ export default function App() { onPause={handlePauseAgent} onConfigure={handleConfigureAgent} onDelete={handleDeleteAgent} + onSlackOpen={(channel) => window.open(`slack://channel?team=T123&id=${channel}`, '_blank')} /> ))}
diff --git a/src/components/AgentCard.tsx b/src/components/AgentCard.tsx index 96138d3..bc8936e 100644 --- a/src/components/AgentCard.tsx +++ b/src/components/AgentCard.tsx @@ -1,7 +1,15 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card'; import { Badge } from './ui/badge'; import { Button } from './ui/button'; -import { Play, Pause, Settings, Trash2, Activity } from 'lucide-react'; +import { Play, Pause, Settings, Trash2, Activity, MessageSquare, Target, Clock, ChevronDown, ChevronUp } from 'lucide-react'; +import { useState } from 'react'; + +interface DailyTask { + id: string; + date: string; + task: string; + status: 'completed' | 'failed' | 'in_progress'; +} interface AgentCardProps { id: string; @@ -11,10 +19,15 @@ interface AgentCardProps { language: string; lastRun: string; executionCount: number; + currentTask?: string; + stopScore?: number; + slackChannel?: string; + dailyTasks?: DailyTask[]; onStart: (id: string) => void; onPause: (id: string) => void; onConfigure: (id: string) => void; onDelete: (id: string) => void; + onSlackOpen?: (channel: string) => void; } export function AgentCard({ @@ -25,69 +38,146 @@ export function AgentCard({ language, lastRun, executionCount, + currentTask, + stopScore, + slackChannel, + dailyTasks = [], onStart, onPause, onConfigure, onDelete, + onSlackOpen, }: AgentCardProps) { + const [expanded, setExpanded] = useState(false); + const statusColors = { active: 'bg-green-500', paused: 'bg-yellow-500', stopped: 'bg-gray-500', }; + const scoreColor = (score: number) => { + if (score >= 70) return 'text-red-500'; + if (score >= 40) return 'text-yellow-500'; + return 'text-green-500'; + }; + + const taskStatusIcon = (status: DailyTask['status']) => { + switch (status) { + case 'completed': return '✅'; + case 'failed': return '❌'; + case 'in_progress': return '🔄'; + } + }; + return ( - +
- {name} -
+ {name} +
+ {stopScore !== undefined && ( + + Score: {stopScore} + + )}
- {description} + {description}
+ {slackChannel && ( + + )}
- -
-
- Language: - {language} -
-
- Executions: - - - {executionCount} - -
-
- Last Run: - {lastRun} + +
+ {/* Live Status */} + {currentTask && status === 'active' && ( +
+
+
+ Live: + {currentTask} +
+
+ )} + + {/* Stats Row */} +
+
+ + {executionCount} +
+
+ + {lastRun} +
+
+ + {language} +
-
+ + {/* Daily Tasks Toggle */} + {dailyTasks.length > 0 && ( +
+ + + {expanded && ( +
+ {dailyTasks.map((task) => ( +
+ {taskStatusIcon(task.status)} + {task.date} + {task.task} +
+ ))} +
+ )} +
+ )} + + {/* Action Buttons */} +
{status === 'active' ? ( - ) : ( - )} -
diff --git a/src/components/ImprovementSuggestions.tsx b/src/components/ImprovementSuggestions.tsx new file mode 100644 index 0000000..5d7c876 --- /dev/null +++ b/src/components/ImprovementSuggestions.tsx @@ -0,0 +1,201 @@ +import { Card, CardContent, CardHeader, CardTitle } from './ui/card'; +import { Badge } from './ui/badge'; +import { Button } from './ui/button'; +import { Lightbulb, ArrowRight, TrendingUp, Shield, Zap, Code } from 'lucide-react'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from './ui/table'; + +interface Suggestion { + id: string; + category: 'performance' | 'security' | 'code_quality' | 'automation'; + title: string; + description: string; + impact: 'high' | 'medium' | 'low'; + effort: 'high' | 'medium' | 'low'; + source: string; + suggestedBy: string; +} + +const mockSuggestions: Suggestion[] = [ + { + id: '1', + category: 'performance', + title: 'Lazy Loading für Agent Cards', + description: 'Agent Cards erst laden wenn sie im Viewport sichtbar sind', + impact: 'high', + effort: 'low', + source: 'code-cloud-agents', + suggestedBy: 'Engineering Lead', + }, + { + id: '2', + category: 'security', + title: 'API Rate Limiting implementieren', + description: 'Schutz vor API Abuse durch Request-Limits pro User/Agent', + impact: 'high', + effort: 'medium', + source: 'mcp-servers', + suggestedBy: 'Monitoring Agent', + }, + { + id: '3', + category: 'code_quality', + title: 'TypeScript Strict Mode aktivieren', + description: 'Strict Mode für bessere Typsicherheit im gesamten Projekt', + impact: 'medium', + effort: 'high', + source: 'code-cloud-agents', + suggestedBy: 'Research Agent', + }, + { + id: '4', + category: 'automation', + title: 'Auto-Deployment Pipeline', + description: 'GitHub Actions für automatisches Deployment bei Merge', + impact: 'high', + effort: 'medium', + source: 'infrastructure', + suggestedBy: 'Deployment Agent', + }, + { + id: '5', + category: 'performance', + title: 'Memory Server Caching', + description: 'Redis Cache vor Pinecone für häufige Queries', + impact: 'medium', + effort: 'medium', + source: 'mcp-servers', + suggestedBy: 'Memory Server', + }, + { + id: '6', + category: 'security', + title: 'Shadow Level Audit Logging', + description: 'Alle Shadow-Level Zugriffe in separatem Log tracken', + impact: 'high', + effort: 'low', + source: 'memory-server', + suggestedBy: 'Meta Supervisor', + }, + { + id: '7', + category: 'automation', + title: 'Automatische STOP Score Alerts', + description: 'Slack-Benachrichtigung wenn STOP Score > 50', + impact: 'medium', + effort: 'low', + source: 'code-cloud-agents', + suggestedBy: 'Monitoring Agent', + }, + { + id: '8', + category: 'code_quality', + title: 'Unit Test Coverage > 80%', + description: 'Test Coverage für alle MCP Server erhöhen', + impact: 'medium', + effort: 'high', + source: 'mcp-servers', + suggestedBy: 'Engineering Lead', + }, +]; + +const categoryIcons = { + performance: , + security: , + code_quality: , + automation: , +}; + +const categoryLabels = { + performance: 'Performance', + security: 'Security', + code_quality: 'Code Quality', + automation: 'Automation', +}; + +const impactColors = { + high: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400', + medium: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400', + low: 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400', +}; + +const effortColors = { + high: 'bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400', + medium: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400', + low: 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400', +}; + +export function ImprovementSuggestions() { + return ( + + + + + Verbesserungsvorschläge + + + +
+ + + + Kategorie + Vorschlag + Impact + Aufwand + Repository + Vorgeschlagen von + + + + + {mockSuggestions.map((suggestion) => ( + + +
+ {categoryIcons[suggestion.category]} + {categoryLabels[suggestion.category]} +
+
+ +
+

{suggestion.title}

+

{suggestion.description}

+
+
+ + + {suggestion.impact === 'high' ? 'Hoch' : suggestion.impact === 'medium' ? 'Mittel' : 'Niedrig'} + + + + + {suggestion.effort === 'high' ? 'Hoch' : suggestion.effort === 'medium' ? 'Mittel' : 'Niedrig'} + + + + {suggestion.source} + + + {suggestion.suggestedBy} + + + + +
+ ))} +
+
+
+
+
+ ); +} diff --git a/src/components/RepoTaskList.tsx b/src/components/RepoTaskList.tsx new file mode 100644 index 0000000..1670921 --- /dev/null +++ b/src/components/RepoTaskList.tsx @@ -0,0 +1,162 @@ +import { useState } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from './ui/card'; +import { Badge } from './ui/badge'; +import { Button } from './ui/button'; +import { GitBranch, ChevronDown, ChevronRight, ExternalLink, Clock, AlertCircle, CheckCircle2 } from 'lucide-react'; + +interface RepoTask { + id: string; + title: string; + priority: 'high' | 'medium' | 'low'; + status: 'open' | 'in_progress' | 'completed'; + assignedAgent?: string; + createdAt: string; + type: 'issue' | 'pr' | 'task'; +} + +interface Repository { + name: string; + org: 'devshift' | 'dsactivi2'; + access: 'read' | 'write'; + tasks: RepoTask[]; +} + +const mockRepositories: Repository[] = [ + { + name: 'code-cloud-agents', + org: 'dsactivi2', + access: 'write', + tasks: [ + { id: '1', title: 'Implement Memory Server API', priority: 'high', status: 'in_progress', assignedAgent: 'Cloud Assistant', createdAt: '25.12.', type: 'task' }, + { id: '2', title: 'Fix TypeScript strict mode errors', priority: 'medium', status: 'open', createdAt: '24.12.', type: 'issue' }, + { id: '3', title: 'Add Slack integration', priority: 'high', status: 'completed', assignedAgent: 'Engineering Lead', createdAt: '23.12.', type: 'task' }, + ], + }, + { + name: 'mcp-servers', + org: 'dsactivi2', + access: 'write', + tasks: [ + { id: '4', title: 'Research MCP SDK updates', priority: 'medium', status: 'in_progress', assignedAgent: 'Research Agent', createdAt: '25.12.', type: 'task' }, + { id: '5', title: 'Create backup server tests', priority: 'low', status: 'open', createdAt: '24.12.', type: 'issue' }, + ], + }, + { + name: 'platform-core', + org: 'devshift', + access: 'read', + tasks: [ + { id: '6', title: 'Review authentication flow', priority: 'high', status: 'open', createdAt: '25.12.', type: 'task' }, + { id: '7', title: 'Analyze API performance', priority: 'medium', status: 'in_progress', assignedAgent: 'Research Agent', createdAt: '24.12.', type: 'task' }, + ], + }, + { + name: 'infrastructure', + org: 'devshift', + access: 'read', + tasks: [ + { id: '8', title: 'Document deployment process', priority: 'low', status: 'open', createdAt: '25.12.', type: 'task' }, + ], + }, +]; + +const priorityColors = { + high: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400', + medium: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400', + low: 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400', +}; + +const statusIcons = { + open: , + in_progress: , + completed: , +}; + +export function RepoTaskList() { + const [expandedRepos, setExpandedRepos] = useState(['code-cloud-agents']); + + const toggleRepo = (repoName: string) => { + setExpandedRepos((prev) => + prev.includes(repoName) + ? prev.filter((r) => r !== repoName) + : [...prev, repoName] + ); + }; + + return ( + + + + + Aufgaben nach Repository + + + + {mockRepositories.map((repo) => ( +
+ +
+ + + {expandedRepos.includes(repo.name) && ( +
+ {repo.tasks.map((task) => ( +
+
+ {statusIcons[task.status]} +
+

{task.title}

+ {task.assignedAgent && ( +

+ Zugewiesen: {task.assignedAgent} +

+ )} +
+
+
+ + {task.priority === 'high' ? 'Hoch' : task.priority === 'medium' ? 'Mittel' : 'Niedrig'} + + {task.createdAt} +
+
+ ))} +
+ )} +
+ ))} + + + ); +} diff --git a/src/components/TaskInput.tsx b/src/components/TaskInput.tsx new file mode 100644 index 0000000..41edaf7 --- /dev/null +++ b/src/components/TaskInput.tsx @@ -0,0 +1,139 @@ +import { useState } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from './ui/card'; +import { Button } from './ui/button'; +import { Input } from './ui/input'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select'; +import { Badge } from './ui/badge'; +import { Send, Plus, X } from 'lucide-react'; + +interface Task { + id: string; + description: string; + priority: 'low' | 'medium' | 'high' | 'critical'; + assignedTo?: string; + status: 'pending' | 'assigned' | 'completed'; +} + +interface TaskInputProps { + agents: { id: string; name: string }[]; + onSubmit: (task: Omit) => void; +} + +export function TaskInput({ agents, onSubmit }: TaskInputProps) { + const [description, setDescription] = useState(''); + const [priority, setPriority] = useState('medium'); + const [assignedTo, setAssignedTo] = useState(''); + const [tasks, setTasks] = useState([]); + + const priorityColors = { + low: 'bg-gray-100 text-gray-700', + medium: 'bg-blue-100 text-blue-700', + high: 'bg-orange-100 text-orange-700', + critical: 'bg-red-100 text-red-700', + }; + + const handleSubmit = () => { + if (!description.trim()) return; + + const newTask: Task = { + id: Date.now().toString(), + description: description.trim(), + priority, + assignedTo: assignedTo || undefined, + status: 'pending', + }; + + setTasks([newTask, ...tasks]); + onSubmit({ description: newTask.description, priority, assignedTo: newTask.assignedTo }); + setDescription(''); + }; + + const removeTask = (id: string) => { + setTasks(tasks.filter(t => t.id !== id)); + }; + + return ( + + + + + Neue Aufgabe + + + + {/* Input Row */} +
+ setDescription(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSubmit()} + className="flex-1" + /> + +
+ + {/* Options Row */} +
+ + + +
+ + {/* Task Queue */} + {tasks.length > 0 && ( +
+
Warteschlange ({tasks.length})
+
+ {tasks.map((task) => ( +
+ + {task.priority} + + {task.description} + {task.assignedTo && ( + + → {agents.find(a => a.id === task.assignedTo)?.name} + + )} + +
+ ))} +
+
+ )} +
+
+ ); +} diff --git a/src/components/TopPriorityTasks.tsx b/src/components/TopPriorityTasks.tsx new file mode 100644 index 0000000..79b3356 --- /dev/null +++ b/src/components/TopPriorityTasks.tsx @@ -0,0 +1,129 @@ +import { Card, CardContent, CardHeader, CardTitle } from './ui/card'; +import { Badge } from './ui/badge'; +import { Button } from './ui/button'; +import { ListOrdered, Play, MoreHorizontal, Clock, AlertTriangle, Flame } from 'lucide-react'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from './ui/dropdown-menu'; + +interface PriorityTask { + id: string; + rank: number; + title: string; + repo: string; + priority: number; // 1-10 score + deadline?: string; + assignedAgent?: string; + status: 'waiting' | 'ready' | 'in_progress'; + estimatedTime?: string; +} + +const mockTopTasks: PriorityTask[] = [ + { id: '1', rank: 1, title: 'Memory Server Pinecone Integration', repo: 'code-cloud-agents', priority: 10, deadline: '26.12.', assignedAgent: 'Cloud Assistant', status: 'in_progress', estimatedTime: '2h' }, + { id: '2', rank: 2, title: 'GitHub API Integration Supervisor', repo: 'code-cloud-agents', priority: 9, deadline: '26.12.', status: 'ready', estimatedTime: '3h' }, + { id: '3', rank: 3, title: 'Qonto API Implementation', repo: 'mcp-servers', priority: 8, deadline: '27.12.', status: 'waiting', estimatedTime: '4h' }, + { id: '4', rank: 4, title: 'Dashboard Performance Optimization', repo: 'code-cloud-agents', priority: 8, assignedAgent: 'Engineering Lead', status: 'in_progress', estimatedTime: '2h' }, + { id: '5', rank: 5, title: 'Slack Webhook Setup', repo: 'code-cloud-agents', priority: 7, deadline: '28.12.', status: 'ready', estimatedTime: '1h' }, + { id: '6', rank: 6, title: 'Unit Tests: Memory Server', repo: 'mcp-servers', priority: 7, status: 'waiting', estimatedTime: '3h' }, + { id: '7', rank: 7, title: 'Error Handling Improvements', repo: 'code-cloud-agents', priority: 6, status: 'waiting', estimatedTime: '2h' }, + { id: '8', rank: 8, title: 'Documentation: MCP Servers', repo: 'mcp-servers', priority: 6, status: 'ready', estimatedTime: '2h' }, + { id: '9', rank: 9, title: 'Monitoring Dashboard Alerts', repo: 'code-cloud-agents', priority: 5, status: 'waiting', estimatedTime: '1h' }, + { id: '10', rank: 10, title: 'Backup Strategy Review', repo: 'infrastructure', priority: 5, status: 'waiting', estimatedTime: '1h' }, +]; + +const getPriorityIcon = (priority: number) => { + if (priority >= 9) return ; + if (priority >= 7) return ; + return ; +}; + +const getStatusBadge = (status: PriorityTask['status']) => { + switch (status) { + case 'in_progress': + return In Arbeit; + case 'ready': + return Bereit; + case 'waiting': + return Wartet; + } +}; + +export function TopPriorityTasks() { + return ( + + + + + Top 10 Prioritäten + + + +
+ {mockTopTasks.map((task) => ( +
+
+
+ {task.rank} +
+ {getPriorityIcon(task.priority)} +
+

{task.title}

+
+ {task.repo} + {task.deadline && ( + <> + · + Deadline: {task.deadline} + + )} + {task.estimatedTime && ( + <> + · + ~{task.estimatedTime} + + )} +
+
+
+ +
+ {task.assignedAgent && ( + + {task.assignedAgent} + + )} + {getStatusBadge(task.status)} +
+ {task.status === 'ready' && ( + + )} + + + + + + Zuweisen + Priorität ändern + Details anzeigen + Entfernen + + +
+
+
+ ))} +
+
+
+ ); +} diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..6a44804 --- /dev/null +++ b/vercel.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "framework": "vite", + "buildCommand": "npm run build", + "outputDirectory": "dist", + "installCommand": "npm install", + "devCommand": "npm run dev", + "regions": ["fra1"], + "headers": [ + { + "source": "/(.*)", + "headers": [ + { "key": "X-Content-Type-Options", "value": "nosniff" }, + { "key": "X-Frame-Options", "value": "DENY" }, + { "key": "X-XSS-Protection", "value": "1; mode=block" } + ] + } + ], + "rewrites": [ + { "source": "/((?!api/).*)", "destination": "/index.html" } + ] +}