-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.js
More file actions
123 lines (98 loc) · 4.34 KB
/
server.js
File metadata and controls
123 lines (98 loc) · 4.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
const express = require('express');
const { execFile, spawn } = require('child_process');
const { resolve } = require('path');
const path = require('path');
const app = express();
const PORT = process.env.PORT || 3456;
const CLAUDE_BIN = process.env.CLAUDE_BIN || 'claude';
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
// Startup: verify claude binary
console.log(`[BOOT] Claude binary configured: "${CLAUDE_BIN}"`);
execFile(CLAUDE_BIN, ['--version'], (err, stdout) => {
if (err) {
console.error(`[BOOT][FATAL] Cannot find claude CLI: ${err.message}`);
console.error(`[BOOT] PATH: ${process.env.PATH}`);
} else {
console.log(`[BOOT] Claude CLI found: ${stdout.trim()}`);
}
});
app.post('/api/generate', (req, res) => {
const reqId = Math.random().toString(36).substring(2, 8);
const log = (level, msg) => console.log(`[${new Date().toISOString()}][${reqId}][${level}] ${msg}`);
log('INFO', `Request received - body keys: ${Object.keys(req.body)}`);
const { prompt } = req.body;
if (!prompt || typeof prompt !== 'string' || prompt.trim().length === 0) {
log('WARN', 'Empty or invalid prompt');
return res.status(400).json({ error: 'Prompt is required' });
}
log('INFO', `Prompt (${prompt.length} chars): "${prompt.substring(0, 100)}${prompt.length > 100 ? '...' : ''}"`);
const args = ['-p', prompt, '--output-format', 'json'];
log('INFO', `Spawning: ${CLAUDE_BIN} ${args.map(a => a === prompt ? `"<prompt ${prompt.length}ch>"` : a).join(' ')}`);
const startTime = Date.now();
let stdoutChunks = [];
let stderrChunks = [];
const child = spawn(CLAUDE_BIN, args, {
timeout: 120000,
env: process.env,
stdio: ['ignore', 'pipe', 'pipe'] // stdin closed, capture stdout+stderr
});
log('INFO', `Process spawned - PID: ${child.pid}, stdin: ignored`);
// Watchdog: log if no output after 10s
const watchdog = setTimeout(() => {
log('WARN', `No output after 10s - process still running (PID: ${child.pid})`);
}, 10000);
child.stdout.on('data', (chunk) => {
stdoutChunks.push(chunk);
log('STDOUT', `+${chunk.length} bytes (total: ${stdoutChunks.reduce((s, c) => s + c.length, 0)})`);
});
child.stderr.on('data', (chunk) => {
stderrChunks.push(chunk);
log('STDERR', chunk.toString().trim());
});
child.on('error', (err) => {
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
log('ERROR', `Process error after ${elapsed}s: ${err.message}`);
log('ERROR', `Error code: ${err.code || 'N/A'}`);
if (!res.headersSent) {
res.status(500).json({ error: `Spawn error: ${err.message}` });
}
});
child.on('close', (code, signal) => {
clearTimeout(watchdog);
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
const stdout = Buffer.concat(stdoutChunks).toString();
const stderr = Buffer.concat(stderrChunks).toString();
log('INFO', `Process exited - code: ${code}, signal: ${signal}, elapsed: ${elapsed}s`);
log('INFO', `stdout: ${stdout.length} bytes, stderr: ${stderr.length} bytes`);
if (code !== 0) {
log('ERROR', `Non-zero exit code: ${code}`);
if (stderr) log('ERROR', `stderr content: ${stderr.substring(0, 500)}`);
if (stdout) log('ERROR', `stdout content: ${stdout.substring(0, 500)}`);
if (!res.headersSent) {
res.status(500).json({ error: `CLI exited with code ${code}: ${stderr || stdout || 'No output'}` });
}
return;
}
log('INFO', `Parsing JSON response...`);
try {
const parsed = JSON.parse(stdout);
log('INFO', `JSON parsed OK - keys: ${Object.keys(parsed).join(', ')}`);
const result = parsed.result || parsed;
const resultPreview = typeof result === 'string' ? result.substring(0, 100) : JSON.stringify(result).substring(0, 100);
log('INFO', `Result preview: "${resultPreview}..."`);
res.json({ result });
} catch (parseErr) {
log('WARN', `JSON parse failed: ${parseErr.message}`);
log('WARN', `Raw stdout (first 300): ${stdout.substring(0, 300)}`);
res.json({ result: stdout.trim() });
}
});
});
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
app.listen(PORT, () => {
console.log(`[BOOT] CLInterface running on http://localhost:${PORT}`);
console.log(`[BOOT] Static files: ${path.join(__dirname, 'public')}`);
});