const express = require('express'); const { spawn } = require('child_process'); const app = express(); app.use(express.json()); // Health check endpoint app.get('/ping', (req, res) => { res.send('pong'); }); // Main inference endpoint (OpenAI-compatible format) app.post('/v1/messages', async (req, res) => { const { messages } = req.body; if (!messages || !Array.isArray(messages) || messages.length === 0) { return res.status(400).json({ error: { message: 'Invalid request: messages array required' } }); } // Extract the last user message as the prompt const lastMessage = messages[messages.length - 1]; const prompt = lastMessage.content; console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log('Request from:', req.ip); console.log('Prompt length:', prompt.length, 'chars'); console.log('Prompt preview:', prompt.substring(0, 150) + '...'); console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); // Spawn Claude CLI process const claude = spawn('claude', [], { stdio: ['pipe', 'pipe', 'pipe'] }); let output = ''; let errorOutput = ''; claude.stdout.on('data', (data) => { output += data.toString(); process.stdout.write('.'); }); claude.stderr.on('data', (data) => { errorOutput += data.toString(); console.error('Claude stderr:', data.toString()); }); claude.on('close', (code) => { console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log('Claude exit code:', code); console.log('Response length:', output.length, 'chars'); console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); if (code !== 0 || !output.trim()) { return res.status(500).json({ error: { message: 'Claude CLI error', details: errorOutput || 'No response from Claude' } }); } // Return OpenAI-compatible response format res.json({ id: 'local-' + Date.now(), object: 'chat.completion', created: Math.floor(Date.now() / 1000), model: 'claude-local', choices: [{ index: 0, message: { role: 'assistant', content: output.trim() }, finish_reason: 'stop' }], usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 } }); }); claude.on('error', (err) => { console.error('Failed to spawn Claude CLI:', err); res.status(500).json({ error: { message: 'Failed to spawn Claude CLI', details: err.message } }); }); // Send prompt to Claude after brief pause setTimeout(() => { claude.stdin.write(prompt + '\n'); claude.stdin.end(); }, 100); }); const PORT = process.env.PORT || 8080; app.listen(PORT, '0.0.0.0', () => { console.log('═══════════════════════════════════════════════════'); console.log('🚀 Agentic Writer Local Backend Started!'); console.log('═══════════════════════════════════════════════════'); console.log(`Local: http://localhost:${PORT}`); console.log(`Network: http://YOUR-IP:${PORT}`); console.log(''); console.log('Plugin Configuration:'); console.log(` Base URL: http://YOUR-IP:${PORT}`); console.log(` API Key: dummy`); console.log(` Model: claude-local`); console.log(''); console.log('Health check: GET /ping'); console.log('Inference: POST /v1/messages'); console.log('═══════════════════════════════════════════════════'); });