const express = require('express'); const { spawn, execSync } = require('child_process'); const path = require('path'); const fs = require('fs'); const os = require('os'); const app = express(); app.use(express.json({ limit: '10mb' })); app.use(express.static(path.join(__dirname, 'public'))); // Resolve claude binary let CLAUDE_BIN = 'claude'; { const local = '/app/claude-bin/node_modules/.bin/claude'; if (fs.existsSync(local)) { CLAUDE_BIN = local; } else { try { CLAUDE_BIN = execSync('which claude 2>/dev/null || echo claude', { encoding: 'utf8' }).trim(); } catch (e) {} } } console.log(`✓ Claude binary: ${CLAUDE_BIN}`); const MODELS = { sonnet: 'claude-sonnet-4-6', haiku: 'claude-haiku-4-5-20251001', }; app.post('/api/chat', (req, res) => { const { text, model = 'sonnet' } = req.body; if (!text?.trim()) return res.status(400).json({ error: 'text required' }); const modelId = MODELS[model] || MODELS.sonnet; const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude'); res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); const env = { ...process.env, CLAUDE_CONFIG_DIR: configDir }; const proc = spawn(CLAUDE_BIN, ['-p', text, '--model', modelId, '--output-format', 'text'], { env, stdio: ['ignore', 'pipe', 'pipe'], }); proc.stdout.on('data', (chunk) => { res.write(`data: ${JSON.stringify({ text: chunk.toString() })}\n\n`); }); let stderrBuf = ''; proc.stderr.on('data', (chunk) => { const s = chunk.toString(); stderrBuf += s; console.error('[claude stderr]', s.substring(0, 500)); }); proc.on('error', (err) => { console.error('[spawn error]', err.message); res.write(`data: ${JSON.stringify({ error: `spawn: ${err.message}` })}\n\n`); }); proc.on('close', (code, signal) => { console.log(`[claude close] code=${code} signal=${signal} stderr=${stderrBuf.substring(0,300)}`); if (code !== 0) { const msg = stderrBuf.trim() || `code=${code} signal=${signal}`; res.write(`data: ${JSON.stringify({ error: msg.substring(0, 500) })}\n\n`); } res.write('data: [DONE]\n\n'); res.end(); }); let finished = false; proc.on('close', () => { finished = true; }); res.on('close', () => { if (!finished) try { proc.kill(); } catch (e) {} }); }); app.listen(8108, () => console.log('sechenov :8108'));