#!/usr/bin/env node // Claude Desktop — Bridge zu Claude Code SDK // Kommuniziert mit Rust-Backend über stdin/stdout (JSON) const { spawn } = require('child_process'); const readline = require('readline'); // State let claudeProcess = null; let abortController = null; // Event an Rust senden function emit(event, payload) { const msg = JSON.stringify({ type: 'event', event, payload }); process.stdout.write(msg + '\n'); } // Antwort an Rust senden function respond(id, result, error = null) { const msg = JSON.stringify({ type: 'response', id, result, error }); process.stdout.write(msg + '\n'); } // Claude Code als Subprocess starten async function startClaude(message, requestId) { abortController = new AbortController(); emit('agent-started', { id: 'main', type: 'Main Agent', task: message.substring(0, 100) }); try { // Claude Code CLI aufrufen claudeProcess = spawn('claude', [ '--output-format', 'stream-json', '-p', message ], { signal: abortController.signal, env: { ...process.env, FORCE_COLOR: '0' } }); let fullResponse = ''; let buffer = ''; claudeProcess.stdout.on('data', (data) => { buffer += data.toString(); const lines = buffer.split('\n'); buffer = lines.pop(); // Unvollständige Zeile behalten for (const line of lines) { if (!line.trim()) continue; try { const event = JSON.parse(line); handleClaudeEvent(event); if (event.type === 'assistant' && event.message?.content) { for (const block of event.message.content) { if (block.type === 'text') { fullResponse += block.text; } } } } catch (e) { // Kein JSON, ignorieren } } }); claudeProcess.stderr.on('data', (data) => { emit('log', { level: 'error', message: data.toString() }); }); claudeProcess.on('close', (code) => { emit('agent-stopped', { id: 'main', code }); respond(requestId, fullResponse || 'Keine Antwort erhalten'); claudeProcess = null; abortController = null; }); claudeProcess.on('error', (err) => { if (err.name === 'AbortError') { respond(requestId, null, 'Abgebrochen durch Benutzer'); } else { respond(requestId, null, err.message); } }); } catch (err) { respond(requestId, null, err.message); } } // Claude SDK Events verarbeiten function handleClaudeEvent(event) { switch (event.type) { case 'tool_use': emit('tool-start', { id: event.tool_use_id, tool: event.name, input: event.input }); break; case 'tool_result': emit('tool-end', { id: event.tool_use_id, success: !event.is_error, output: typeof event.content === 'string' ? event.content.substring(0, 500) : JSON.stringify(event.content).substring(0, 500) }); break; case 'subagent_start': emit('subagent-start', { id: event.subagent_id, type: event.subagent_type, task: event.prompt?.substring(0, 100) }); break; case 'subagent_stop': emit('subagent-stop', { id: event.subagent_id }); break; case 'assistant': if (event.message?.content) { for (const block of event.message.content) { if (block.type === 'text') { emit('text', { text: block.text }); } } } break; case 'result': emit('result', { cost: event.cost_usd, tokens: { input: event.input_tokens, output: event.output_tokens } }); break; } } // Alle Prozesse stoppen function stopAll() { if (abortController) { abortController.abort(); } if (claudeProcess) { claudeProcess.kill('SIGTERM'); } emit('all-stopped', {}); } // Stdin lesen const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false }); rl.on('line', (line) => { try { const msg = JSON.parse(line); switch (msg.command) { case 'message': startClaude(msg.message, msg.id); break; case 'stop': stopAll(); respond(msg.id, 'stopped'); break; case 'ping': respond(msg.id, 'pong'); break; default: respond(msg.id, null, `Unbekannter Befehl: ${msg.command}`); } } catch (e) { emit('error', { message: e.message }); } }); // Startup emit('ready', { version: '0.1.0' });