Initial commit: IT Nexus Web-App

This commit is contained in:
2026-06-01 20:49:07 +02:00
commit 8023765e6c
387 changed files with 106900 additions and 0 deletions

997
devteam.js Normal file
View File

@@ -0,0 +1,997 @@
// IT Nexus Dev Team — Lokaler KI-Entwickler-Agent
// Start: node devteam.js
// Dann: http://localhost:4242
const http = require('http');
const https = require('https');
const API_KEY = process.env.ANTHROPIC_API_KEY || '';
const PORT = 4242;
// ─── IT Nexus Kontext für alle Agenten ────────────────────────────────────────
const IT_NEXUS_CONTEXT = `
# IT Nexus Vollständiger Stack-Kontext
## Tech Stack
- Backend: Node.js 18 + Express 4 + SQLite (better-sqlite3, SYNCHRON - kein await!)
- Frontend: React 18 (Create React App) + Custom CSS (Glassmorphism)
- Auth: JWT (jsonwebtoken) + bcryptjs
- KI: @anthropic-ai/sdk (Claude claude-sonnet-4-6)
- Deployment: Docker Compose + nginx auf LXC CT 102 (192.168.0.194)
## Dateistruktur Backend
\`\`\`
backend/src/
server.js Express Setup, alle app.use() Registrierungen
db/seed.js Alle DB-Migrations (jede in try/catch, idempotent!)
config/database.js SQLite Verbindung via better-sqlite3
middleware/
auth.js authenticateToken (JWT prüfen)
roleCheck.js requireAdmin, requireStaff Middleware
errorHandler.js asyncHandler(fn) Wrapper für alle Controller
controllers/ Business Logic, eine Datei pro Feature
models/ SQLite Queries, eine Datei pro Entity
routes/ Express Router, eine Datei pro Feature
services/ Externe APIs (Graph API, Proxmox, Anthropic)
\`\`\`
## Dateistruktur Frontend
\`\`\`
frontend/src/
pages/ Alle Seiten (NamingConvention: XxxPage.jsx)
components/common/
AppLayout.jsx Wrapper mit Sidebar + Topbar
Sidebar.jsx Navigation
Topbar.jsx Header
context/AuthContext.jsx Auth State, user Objekt
services/api.js Axios Instance mit Interceptors (IMMER verwenden!)
\`\`\`
## Kritische Konventionen Backend
1. DB-Migration: Jedes ALTER TABLE / CREATE TABLE einzeln in try/catch in seed.js
2. Controller immer mit asyncHandler wrappen: const foo = asyncHandler(async (req, res) => {...})
3. Statische Routen VOR /:id definieren (z.B. /statistics vor /:id)
4. better-sqlite3 ist SYNCHRON: db.prepare('...').get() KEIN await!
5. Exports am Ende: module.exports = { foo, bar }
## Kritische Konventionen Frontend
1. IMMER import api from '../../services/api' NIEMALS axios direkt importieren
2. IMMER user.role_name NIEMALS user.role (falsches Feld!)
3. CSS Variablen verwenden:
- Text: var(--text-primary), var(--text-secondary), var(--text-muted)
- Hintergrund: var(--bg-card), var(--bg-secondary), var(--border-color)
- Glassmorphism: backdrop-filter: blur(20px), rgba() Hintergründe
4. Rollen: super_admin, admin, support, bearbeiter, benutzer, hr_personal, buchhaltung
5. Neue Seite in AppLayout einbinden (Route in App.jsx)
## Deploy-Workflow
\`\`\`
scp datei.js root@192.168.0.194:/opt/it-nexus/backend/src/...
docker cp /opt/it-nexus/backend/src/.../datei.js fido-backend:/app/src/.../
docker restart fido-backend
# Frontend rebuild nur nötig wenn neue Seite/Komponente:
docker compose build frontend && docker compose up -d frontend
\`\`\`
## Beispiel: Neue Feature-Struktur
Backend: controller + model + route + seed.js Migration + server.js Route registrieren
Frontend: XxxPage.jsx + xxxService.js + Route in App.jsx + Link in Sidebar.jsx
`;
// ─── Agenten Definitionen ──────────────────────────────────────────────────────
const AGENTS = {
pm: {
name: 'Project Manager',
emoji: '📋',
color: '#6366f1',
system: `Du bist der Project Manager des IT Nexus Entwicklerteams. Du koordinierst alle anderen Agenten.
${IT_NEXUS_CONTEXT}
Deine Aufgabe:
1. Analysiere die Anfrage des Benutzers
2. Erstelle einen klaren Implementierungsplan
3. Bestimme welche Spezialisten benötigt werden
4. Fasse am Ende alles zusammen
Antworte auf Deutsch. Sei konkret und strukturiert. Nutze Markdown.
Erkläre kurz was getan werden muss, dann liste die benötigten Schritte auf.`
},
architect: {
name: 'Architect',
emoji: '🏗️',
color: '#f59e0b',
system: `Du bist der System-Architekt des IT Nexus Entwicklerteams.
${IT_NEXUS_CONTEXT}
Deine Aufgabe:
- Entscheide über DB-Schema (SQLite Spalten, Typen, Constraints)
- Entscheide über API-Struktur (Endpoints, HTTP-Methoden, Auth-Level)
- Entscheide über Datenfluss zwischen Frontend und Backend
- Identifiziere Abhängigkeiten zu bestehenden Features
Antworte auf Deutsch. Gib konkrete technische Entscheidungen.
Format: DB-Schema → API-Endpoints → Abhängigkeiten → Besonderheiten`
},
backend: {
name: 'Backend Dev',
emoji: '⚙️',
color: '#10b981',
system: `Du bist der Backend-Entwickler des IT Nexus Entwicklerteams. Du schreibst den kompletten Backend-Code.
${IT_NEXUS_CONTEXT}
Deine Aufgabe:
- Schreibe vollständige, lauffähige Node.js Dateien
- Folge exakt den Konventionen (asyncHandler, better-sqlite3 synchron, etc.)
- Jede Datei vollständig mit korrekten Imports und Exports
- DB-Migrations in seed.js Format
Format pro Datei:
### Datei: \`backend/src/pfad/dateiname.js\`
\`\`\`javascript
// kompletter Code
\`\`\`
Antworte auf Deutsch für Erklärungen, Code auf Englisch.`
},
frontend: {
name: 'Frontend Dev',
emoji: '🎨',
color: '#3b82f6',
system: `Du bist der Frontend-Entwickler des IT Nexus Entwicklerteams. Du schreibst den kompletten Frontend-Code.
${IT_NEXUS_CONTEXT}
Deine Aufgabe:
- Schreibe vollständige React Komponenten (.jsx Dateien)
- Glassmorphism Design (konsistent mit bestehendem IT Nexus Style)
- IMMER api.get/post aus services/api.js verwenden
- IMMER user.role_name verwenden
- CSS inline mit var(--*) Variablen
Format pro Datei:
### Datei: \`frontend/src/pfad/DateiName.jsx\`
\`\`\`jsx
// kompletter Code
\`\`\`
Antworte auf Deutsch für Erklärungen, Code auf Englisch.`
},
senior: {
name: 'Senior Dev',
emoji: '🔍',
color: '#ef4444',
system: `Du bist der Senior Developer und Code Reviewer des IT Nexus Entwicklerteams.
${IT_NEXUS_CONTEXT}
Deine Aufgabe:
- Reviewe den generierten Code auf Bugs, Sicherheitsprobleme, Konventionsverletzungen
- Prüfe ob alle Konventionen eingehalten wurden (user.role_name, api.js, asyncHandler, etc.)
- Prüfe auf fehlende Error Handling
- Prüfe auf fehlende Route-Registrierungen in server.js
- Prüfe auf fehlende Sidebar-Links
- Gib konkrete Fixes wenn nötig
Format:
✅ Was gut ist
⚠️ Was fehlt / falsch ist + Fix
📝 Deploy-Befehl am Ende
Antworte auf Deutsch.`
},
qa: {
name: 'QA Engineer',
emoji: '🧪',
color: '#8b5cf6',
system: `Du bist der QA Engineer des IT Nexus Entwicklerteams.
${IT_NEXUS_CONTEXT}
Deine Aufgabe:
- Identifiziere potenzielle Edge Cases
- Schreibe manuelle Testschritte die der Entwickler ausführen soll
- Prüfe ob alle API-Endpoints abgesichert sind (Auth)
- Prüfe Fehlerszenarien (leere Daten, falscher Input, fehlende Rechte)
- Schreibe wenn möglich einen Playwright-Test
Format:
📋 Manuelle Tests (nummeriert)
🔒 Security-Checks
⚡ Edge Cases
(Optional) Playwright Test Code
Antworte auf Deutsch.`
}
};
// ─── Anthropic API Aufruf ──────────────────────────────────────────────────────
function callClaude(systemPrompt, messages, onChunk) {
return new Promise((resolve, reject) => {
const body = JSON.stringify({
model: 'claude-sonnet-4-6',
max_tokens: 8096,
system: systemPrompt,
messages,
stream: true
});
const req = https.request({
hostname: 'api.anthropic.com',
path: '/v1/messages',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY,
'anthropic-version': '2023-06-01',
'Content-Length': Buffer.byteLength(body)
}
}, (res) => {
let full = '';
res.on('data', chunk => {
const lines = chunk.toString().split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
try {
const d = JSON.parse(line.slice(6));
if (d.type === 'content_block_delta' && d.delta?.text) {
full += d.delta.text;
onChunk(d.delta.text);
}
} catch {}
}
}
});
res.on('end', () => resolve(full));
res.on('error', reject);
});
req.on('error', reject);
req.write(body);
req.end();
});
}
// ─── Orchestrierung ────────────────────────────────────────────────────────────
async function runTeam(task, send) {
const context = [];
const runAgent = async (agentId, userMsg) => {
const agent = AGENTS[agentId];
send({ type: 'agent_start', agent: agentId, name: agent.name, emoji: agent.emoji, color: agent.color });
const msgs = [...context, { role: 'user', content: userMsg }];
let full = '';
await callClaude(agent.system, msgs, chunk => {
full += chunk;
send({ type: 'chunk', agent: agentId, text: chunk });
});
send({ type: 'agent_done', agent: agentId });
context.push({ role: 'user', content: userMsg });
context.push({ role: 'assistant', content: full });
return full;
};
// 1. PM analysiert
const pmResult = await runAgent('pm',
`Neue Aufgabe für das IT Nexus Entwicklerteam:\n\n"${task}"\n\nErstelle einen kurzen Implementierungsplan.`
);
// 2. Architect entscheidet
await runAgent('architect',
`Aufgabe: "${task}"\n\nPM-Plan:\n${pmResult}\n\nGib deine technischen Entscheidungen für DB-Schema und API-Struktur.`
);
// 3. Backend Dev schreibt Code
const backendResult = await runAgent('backend',
`Aufgabe: "${task}"\n\nSchreibe den vollständigen Backend-Code (Controller, Model, Route, seed.js Migration). Alle Dateien komplett.`
);
// 4. Frontend Dev schreibt Code
const frontendResult = await runAgent('frontend',
`Aufgabe: "${task}"\n\nSchreibe den vollständigen Frontend-Code (Page, Service). Alle Dateien komplett.`
);
// 5. Senior Dev reviewt
await runAgent('senior',
`Reviewe diesen Code:\n\n**Backend:**\n${backendResult}\n\n**Frontend:**\n${frontendResult}\n\nFinde Bugs, Konventionsverletzungen, fehlende Teile.`
);
// 6. QA testet
await runAgent('qa',
`Aufgabe: "${task}"\n\nErstelle Testschritte und prüfe Edge Cases für die implementierte Funktion.`
);
send({ type: 'done' });
}
// ─── HTTP Server ───────────────────────────────────────────────────────────────
const HTML = `<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IT Nexus Dev Team</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', system-ui, sans-serif;
background: #0a0f1e;
color: #e2e8f0;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* Header */
.header {
background: rgba(15,23,42,0.95);
border-bottom: 1px solid rgba(99,102,241,0.3);
padding: 14px 24px;
display: flex;
align-items: center;
gap: 14px;
flex-shrink: 0;
}
.header-logo {
width: 36px; height: 36px;
background: linear-gradient(135deg, #6366f1, #8b5cf6);
border-radius: 10px;
display: flex; align-items: center; justify-content: center;
font-size: 18px;
}
.header h1 { font-size: 18px; font-weight: 700; color: #fff; }
.header p { font-size: 12px; color: #64748b; margin-top: 1px; }
.status-dot {
width: 8px; height: 8px; border-radius: 50%;
background: #10b981; margin-left: auto;
box-shadow: 0 0 8px #10b981;
animation: pulse 2s infinite;
}
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.4} }
/* Layout */
.main { display: flex; flex: 1; overflow: hidden; }
/* Sidebar: Team */
.sidebar {
width: 200px;
background: rgba(15,23,42,0.8);
border-right: 1px solid rgba(255,255,255,0.06);
padding: 16px 12px;
display: flex;
flex-direction: column;
gap: 8px;
flex-shrink: 0;
}
.sidebar-title {
font-size: 10px;
font-weight: 700;
color: #475569;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 4px;
padding: 0 4px;
}
.agent-card {
padding: 8px 10px;
border-radius: 8px;
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
font-weight: 600;
color: #94a3b8;
border: 1px solid transparent;
transition: all 0.2s;
}
.agent-card.active {
background: rgba(255,255,255,0.06);
color: #fff;
border-color: rgba(255,255,255,0.1);
}
.agent-card.thinking {
animation: agent-pulse 0.8s infinite;
}
@keyframes agent-pulse {
0%,100% { opacity: 1; }
50% { opacity: 0.5; }
}
.agent-emoji { font-size: 16px; }
.agent-status {
width: 6px; height: 6px; border-radius: 50%;
background: #1e293b;
margin-left: auto;
flex-shrink: 0;
}
.agent-status.active { background: #10b981; box-shadow: 0 0 6px #10b981; }
.agent-status.done { background: #6366f1; }
/* Chat Area */
.chat { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 16px; }
.chat::-webkit-scrollbar { width: 4px; }
.chat::-webkit-scrollbar-track { background: transparent; }
.chat::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
.msg { display: flex; gap: 12px; }
.msg-avatar {
width: 36px; height: 36px;
border-radius: 10px;
display: flex; align-items: center; justify-content: center;
font-size: 18px;
flex-shrink: 0;
border: 1px solid rgba(255,255,255,0.1);
}
.msg-body { flex: 1; min-width: 0; }
.msg-header {
display: flex; align-items: center; gap: 8px;
margin-bottom: 6px;
}
.msg-name { font-size: 13px; font-weight: 700; }
.msg-badge {
font-size: 10px;
padding: 2px 6px;
border-radius: 4px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.msg-content {
font-size: 13px;
line-height: 1.7;
color: #cbd5e1;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 10px;
padding: 14px 16px;
white-space: pre-wrap;
word-break: break-word;
}
/* Code Blocks */
.msg-content pre {
background: rgba(0,0,0,0.5);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 8px;
padding: 14px;
margin: 10px 0;
overflow-x: auto;
font-family: 'Cascadia Code', 'Fira Code', Consolas, monospace;
font-size: 12px;
line-height: 1.6;
}
.msg-content code {
background: rgba(0,0,0,0.4);
padding: 1px 5px;
border-radius: 3px;
font-family: 'Cascadia Code', Consolas, monospace;
font-size: 12px;
color: #7dd3fc;
}
.msg-content pre code {
background: none; padding: 0; color: #e2e8f0;
}
.msg-content h1, .msg-content h2, .msg-content h3 {
color: #f1f5f9; margin: 14px 0 6px;
}
.msg-content ul, .msg-content ol { padding-left: 20px; margin: 6px 0; }
.msg-content li { margin: 3px 0; }
.msg-content strong { color: #f1f5f9; }
/* User message */
.msg.user .msg-content {
background: rgba(99,102,241,0.1);
border-color: rgba(99,102,241,0.3);
color: #e2e8f0;
}
/* Input Area */
.input-area {
border-top: 1px solid rgba(255,255,255,0.06);
background: rgba(15,23,42,0.95);
padding: 16px 20px;
flex-shrink: 0;
}
.api-key-bar {
display: flex; gap: 8px; margin-bottom: 10px; align-items: center;
}
.api-key-bar label { font-size: 11px; color: #475569; white-space: nowrap; }
.api-key-bar input {
flex: 1;
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 6px;
padding: 6px 10px;
color: #94a3b8;
font-size: 12px;
font-family: monospace;
}
.input-row { display: flex; gap: 10px; }
textarea {
flex: 1;
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 10px;
padding: 12px 14px;
color: #e2e8f0;
font-size: 13px;
resize: none;
font-family: inherit;
line-height: 1.5;
transition: border-color 0.2s;
}
textarea:focus { outline: none; border-color: rgba(99,102,241,0.5); }
textarea::placeholder { color: #334155; }
.send-btn {
background: linear-gradient(135deg, #6366f1, #8b5cf6);
border: none;
border-radius: 10px;
color: #fff;
font-size: 13px;
font-weight: 700;
padding: 0 20px;
cursor: pointer;
transition: opacity 0.2s;
white-space: nowrap;
}
.send-btn:hover { opacity: 0.85; }
.send-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.hint { font-size: 11px; color: #334155; margin-top: 8px; }
/* Welcome */
.welcome {
margin: auto;
text-align: center;
padding: 40px;
max-width: 500px;
}
.welcome .big-emoji { font-size: 60px; margin-bottom: 16px; }
.welcome h2 { font-size: 22px; font-weight: 700; color: #f1f5f9; margin-bottom: 8px; }
.welcome p { font-size: 14px; color: #475569; line-height: 1.6; }
.examples { margin-top: 24px; display: flex; flex-direction: column; gap: 8px; }
.example-btn {
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 8px;
padding: 10px 14px;
color: #94a3b8;
font-size: 12px;
cursor: pointer;
text-align: left;
transition: all 0.2s;
}
.example-btn:hover { background: rgba(255,255,255,0.08); color: #e2e8f0; }
</style>
</head>
<body>
<div class="header">
<div class="header-logo">👨‍💻</div>
<div>
<h1>IT Nexus Dev Team</h1>
<p>6 KI-Agenten · Lokal · Nur für dich</p>
</div>
<div class="status-dot"></div>
</div>
<div class="main">
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-title">Dev Team</div>
<div class="agent-card" id="card-pm">
<span class="agent-emoji">📋</span>
<span>Project Manager</span>
<div class="agent-status" id="status-pm"></div>
</div>
<div class="agent-card" id="card-architect">
<span class="agent-emoji">🏗️</span>
<span>Architect</span>
<div class="agent-status" id="status-architect"></div>
</div>
<div class="agent-card" id="card-backend">
<span class="agent-emoji">⚙️</span>
<span>Backend Dev</span>
<div class="agent-status" id="status-backend"></div>
</div>
<div class="agent-card" id="card-frontend">
<span class="agent-emoji">🎨</span>
<span>Frontend Dev</span>
<div class="agent-status" id="status-frontend"></div>
</div>
<div class="agent-card" id="card-senior">
<span class="agent-emoji">🔍</span>
<span>Senior Dev</span>
<div class="agent-status" id="status-senior"></div>
</div>
<div class="agent-card" id="card-qa">
<span class="agent-emoji">🧪</span>
<span>QA Engineer</span>
<div class="agent-status" id="status-qa"></div>
</div>
</div>
<!-- Chat -->
<div class="chat" id="chat">
<div class="welcome" id="welcome">
<div class="big-emoji">🚀</div>
<h2>Dein persönliches Dev Team</h2>
<p>Beschreibe was du bauen möchtest. Das Team analysiert, plant, schreibt Code und reviewt alles automatisch.</p>
<div class="examples">
<button class="example-btn" onclick="setExample(this)">📦 Neue Seite für Lizenzmanagement (CRUD, DB, API, React)</button>
<button class="example-btn" onclick="setExample(this)">🔔 E-Mail-Benachrichtigung wenn Agent offline ist</button>
<button class="example-btn" onclick="setExample(this)">📊 Dashboard-Widget mit Top 5 offenen Tickets</button>
<button class="example-btn" onclick="setExample(this)">🔐 Zwei-Faktor-Authentifizierung für Admin-Login</button>
</div>
</div>
</div>
</div>
<!-- Input -->
<div class="input-area">
<div class="api-key-bar">
<label>🔑 API Key:</label>
<input type="password" id="apiKey" placeholder="sk-ant-..." oninput="saveKey(this.value)" />
</div>
<div class="input-row">
<textarea id="taskInput" rows="2" placeholder="Was soll das Team bauen? (Enter = Senden, Shift+Enter = Neue Zeile)"></textarea>
<button class="send-btn" id="sendBtn" onclick="sendTask()">Team starten ▶</button>
</div>
<div class="hint">⚡ PM → Architect → Backend Dev → Frontend Dev → Senior Dev → QA Engineer</div>
</div>
<script>
const COLORS = {
pm: '#6366f1', architect: '#f59e0b', backend: '#10b981',
frontend: '#3b82f6', senior: '#ef4444', qa: '#8b5cf6'
};
const NAMES = {
pm: 'Project Manager', architect: 'Architect', backend: 'Backend Dev',
frontend: 'Frontend Dev', senior: 'Senior Dev', qa: 'QA Engineer'
};
const EMOJIS = {
pm: '📋', architect: '🏗️', backend: '⚙️',
frontend: '🎨', senior: '🔍', qa: '🧪'
};
// API Key aus localStorage laden
window.onload = () => {
const saved = localStorage.getItem('devteam_apikey');
if (saved) document.getElementById('apiKey').value = saved;
};
function saveKey(v) { localStorage.setItem('devteam_apikey', v); }
function setExample(btn) {
document.getElementById('taskInput').value = btn.textContent.replace(/^[^\s]+\s/, '').trim();
document.getElementById('taskInput').focus();
}
document.getElementById('taskInput').addEventListener('keydown', e => {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendTask(); }
});
let currentMsgEl = null;
let currentRaw = '';
function addUserMsg(text) {
document.getElementById('welcome')?.remove();
const el = document.createElement('div');
el.className = 'msg user';
el.innerHTML = \`
<div class="msg-avatar" style="background:rgba(99,102,241,0.2)">👤</div>
<div class="msg-body">
<div class="msg-header">
<span class="msg-name" style="color:#6366f1">Du</span>
</div>
<div class="msg-content">\${escHtml(text)}</div>
</div>\`;
document.getElementById('chat').appendChild(el);
scrollChat();
}
function startAgentMsg(agentId) {
// Sidebar aktualisieren
document.querySelectorAll('.agent-card').forEach(c => c.classList.remove('active','thinking'));
document.querySelectorAll('.agent-status').forEach(s => { s.classList.remove('active'); });
const card = document.getElementById('card-' + agentId);
const status = document.getElementById('status-' + agentId);
if (card) { card.classList.add('active','thinking'); }
if (status) { status.classList.add('active'); }
const color = COLORS[agentId];
const name = NAMES[agentId];
const emoji = EMOJIS[agentId];
const el = document.createElement('div');
el.className = 'msg';
el.id = 'msg-' + agentId;
el.innerHTML = \`
<div class="msg-avatar" style="background:\${color}20; border-color:\${color}40">\${emoji}</div>
<div class="msg-body">
<div class="msg-header">
<span class="msg-name" style="color:\${color}">\${name}</span>
<span class="msg-badge" style="background:\${color}20;color:\${color}">schreibt...</span>
</div>
<div class="msg-content" id="content-\${agentId}"><span class="cursor">▌</span></div>
</div>\`;
document.getElementById('chat').appendChild(el);
currentMsgEl = document.getElementById('content-' + agentId);
currentRaw = '';
scrollChat();
}
function appendChunk(agentId, text) {
currentRaw += text;
if (currentMsgEl) {
currentMsgEl.innerHTML = renderMarkdown(currentRaw) + '<span class="cursor">▌</span>';
scrollChat();
}
}
function doneAgentMsg(agentId) {
if (currentMsgEl) {
currentMsgEl.innerHTML = renderMarkdown(currentRaw);
}
const card = document.getElementById('card-' + agentId);
const status = document.getElementById('status-' + agentId);
if (card) card.classList.remove('thinking');
if (status) { status.classList.remove('active'); status.classList.add('done'); }
// Badge aktualisieren
const msg = document.getElementById('msg-' + agentId);
if (msg) {
const badge = msg.querySelector('.msg-badge');
if (badge) { badge.textContent = 'fertig ✓'; badge.style.background = COLORS[agentId]+'30'; }
}
currentMsgEl = null;
}
function renderMarkdown(text) {
return text
.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
.replace(/\`\`\`(\w+)?\n([\s\S]*?)\`\`\`/g, '<pre><code>$2</code></pre>')
.replace(/\`([^\`]+)\`/g, '<code>$1</code>')
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/^- (.+)$/gm, '<li>$1</li>')
.replace(/^(\d+)\. (.+)$/gm, '<li>$1. $2</li>')
.replace(/\n\n/g, '<br><br>')
.replace(/\n/g, '<br>');
}
function escHtml(t) {
return t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
function scrollChat() {
const chat = document.getElementById('chat');
chat.scrollTop = chat.scrollHeight;
}
async function sendTask() {
const task = document.getElementById('taskInput').value.trim();
const apiKey = document.getElementById('apiKey').value.trim();
if (!task) return;
if (!apiKey) { alert('Bitte zuerst den Anthropic API Key eingeben!'); return; }
const btn = document.getElementById('sendBtn');
btn.disabled = true;
btn.textContent = '⏳ Team arbeitet...';
addUserMsg(task);
document.getElementById('taskInput').value = '';
// Alle Status zurücksetzen
document.querySelectorAll('.agent-status').forEach(s => s.classList.remove('active','done'));
document.querySelectorAll('.agent-card').forEach(c => c.classList.remove('active','thinking'));
try {
const res = await fetch('/api/task', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ task, apiKey })
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buf = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
const lines = buf.split('\n\n');
buf = lines.pop();
for (const line of lines) {
if (line.startsWith('data: ')) {
try {
const d = JSON.parse(line.slice(6));
if (d.type === 'agent_start') startAgentMsg(d.agent);
else if (d.type === 'chunk') appendChunk(d.agent, d.text);
else if (d.type === 'agent_done') doneAgentMsg(d.agent);
else if (d.type === 'done') {
document.querySelectorAll('.agent-card').forEach(c => c.classList.remove('active','thinking'));
}
} catch {}
}
}
}
} catch (e) {
const el = document.createElement('div');
el.style.cssText = 'background:rgba(239,68,68,0.1);border:1px solid rgba(239,68,68,0.3);border-radius:8px;padding:12px;color:#fca5a5;font-size:13px;';
el.textContent = '❌ Fehler: ' + e.message;
document.getElementById('chat').appendChild(el);
}
btn.disabled = false;
btn.textContent = 'Team starten ▶';
scrollChat();
}
</script>
</body>
</html>`;
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://localhost:${PORT}`);
// HTML
if (req.method === 'GET' && url.pathname === '/') {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(HTML);
return;
}
// Task API
if (req.method === 'POST' && url.pathname === '/api/task') {
let body = '';
req.on('data', c => body += c);
req.on('end', async () => {
try {
const { task, apiKey } = JSON.parse(body);
// API Key aus Request oder Umgebungsvariable
const key = apiKey || API_KEY;
if (!key) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Kein API Key' }));
return;
}
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
});
const send = (data) => res.write(`data: ${JSON.stringify(data)}\n\n`);
// Agenten mit aktuellem Key aufrufen
const callAgent = (systemPrompt, messages) => new Promise((resolve, reject) => {
const reqBody = JSON.stringify({
model: 'claude-sonnet-4-6',
max_tokens: 8096,
system: systemPrompt,
messages,
stream: true
});
const apiReq = https.request({
hostname: 'api.anthropic.com',
path: '/v1/messages',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': key,
'anthropic-version': '2023-06-01',
'Content-Length': Buffer.byteLength(reqBody)
}
}, (apiRes) => {
let full = '';
let buf = '';
apiRes.on('data', chunk => {
buf += chunk.toString();
const lines = buf.split('\n');
buf = lines.pop();
for (const line of lines) {
if (line.startsWith('data: ')) {
try {
const d = JSON.parse(line.slice(6));
if (d.type === 'content_block_delta' && d.delta?.text) {
full += d.delta.text;
send({ type: 'chunk', agent: currentAgent, text: d.delta.text });
}
} catch {}
}
}
});
apiRes.on('end', () => resolve(full));
apiRes.on('error', reject);
});
apiReq.on('error', reject);
apiReq.write(reqBody);
apiReq.end();
});
let currentAgent = '';
const context = [];
const runAgent = async (agentId, userMsg) => {
currentAgent = agentId;
send({ type: 'agent_start', agent: agentId });
const msgs = [...context, { role: 'user', content: userMsg }];
const full = await callAgent(AGENTS[agentId].system, msgs);
send({ type: 'agent_done', agent: agentId });
context.push({ role: 'user', content: userMsg });
context.push({ role: 'assistant', content: full });
return full;
};
// Team ausführen
const pmResult = await runAgent('pm',
`Neue Aufgabe:\n\n"${task}"\n\nErstelle einen kurzen klaren Implementierungsplan für das IT Nexus Entwicklerteam.`
);
await runAgent('architect',
`Aufgabe: "${task}"\n\nPM-Plan:\n${pmResult}\n\nEntscheide über DB-Schema, API-Endpoints und technische Architektur.`
);
const backendResult = await runAgent('backend',
`Aufgabe: "${task}"\n\nSchreibe jetzt den vollständigen Backend-Code. Alle Dateien komplett und lauffähig. Folge exakt den IT Nexus Konventionen.`
);
const frontendResult = await runAgent('frontend',
`Aufgabe: "${task}"\n\nSchreibe jetzt den vollständigen Frontend-Code. Alle Dateien komplett. Folge exakt den IT Nexus Konventionen.`
);
await runAgent('senior',
`Reviewe diesen Code:\n\nBACKEND:\n${backendResult}\n\nFRONTEND:\n${frontendResult}\n\nFinde alle Bugs, Konventionsverletzungen und fehlende Teile. Gib Fixes.`
);
await runAgent('qa',
`Aufgabe: "${task}"\n\nErstelle konkrete Testschritte, Security-Checks und Edge Cases.`
);
send({ type: 'done' });
res.end();
} catch (e) {
try {
res.write(`data: ${JSON.stringify({ type: 'error', text: e.message })}\n\n`);
res.end();
} catch {}
}
});
return;
}
res.writeHead(404);
res.end();
});
server.listen(PORT, '127.0.0.1', () => {
console.log('');
console.log('╔═══════════════════════════════════════╗');
console.log('║ IT Nexus Dev Team — Bereit! ║');
console.log('╠═══════════════════════════════════════╣');
console.log(`║ → http://localhost:${PORT}`);
console.log('║ ║');
console.log('║ 6 Agenten: PM, Architect, ║');
console.log('║ Backend, Frontend, Senior, QA ║');
console.log('╚═══════════════════════════════════════╝');
console.log('');
if (!API_KEY) {
console.log('⚠️ Kein ANTHROPIC_API_KEY gesetzt — im Browser eingeben');
console.log(' Oder: set ANTHROPIC_API_KEY=sk-ant-... && node devteam.js');
}
console.log('');
});