devbonzai 2.1.2 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +21 -26
- package/package.json +1 -1
- package/templates/receiver.js +1712 -9
- package/templates/routes/ai.js +0 -497
- package/templates/routes/crud.js +0 -208
- package/templates/routes/infra.js +0 -147
- package/templates/utils/file-list.js +0 -96
- package/templates/utils/ignore-patterns.js +0 -53
- package/templates/utils/parsers.js +0 -726
package/templates/routes/ai.js
DELETED
|
@@ -1,497 +0,0 @@
|
|
|
1
|
-
const { spawn, execSync } = require('child_process');
|
|
2
|
-
|
|
3
|
-
function setupAiRoutes(app, ROOT) {
|
|
4
|
-
// POST /analyze_prompt - Analyze what files would be modified
|
|
5
|
-
app.post('/analyze_prompt', (req, res) => {
|
|
6
|
-
console.log('🔵 [analyze_prompt] Endpoint hit');
|
|
7
|
-
const { prompt } = req.body;
|
|
8
|
-
console.log('🔵 [analyze_prompt] Received prompt:', prompt ? `${prompt.substring(0, 50)}...` : 'none');
|
|
9
|
-
|
|
10
|
-
if (!prompt || typeof prompt !== 'string') {
|
|
11
|
-
console.log('❌ [analyze_prompt] Error: prompt required');
|
|
12
|
-
return res.status(400).json({ error: 'prompt required' });
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Configurable timeout (default 2 minutes for analysis)
|
|
16
|
-
const timeoutMs = parseInt(req.body.timeout) || 2 * 60 * 1000;
|
|
17
|
-
let timeoutId = null;
|
|
18
|
-
let responseSent = false;
|
|
19
|
-
|
|
20
|
-
// Build analysis prompt - ask agent to list files without making changes
|
|
21
|
-
const analysisPrompt = `You are analyzing a coding task. Do NOT make any changes to any files. Only analyze and list the files you would need to modify to complete this task.
|
|
22
|
-
|
|
23
|
-
Respond ONLY with valid JSON in this exact format (no other text):
|
|
24
|
-
{"files": [{"path": "path/to/file.ext", "reason": "brief reason for modification"}]}
|
|
25
|
-
|
|
26
|
-
If no files need modification, respond with: {"files": []}
|
|
27
|
-
|
|
28
|
-
Task to analyze: ${prompt}`;
|
|
29
|
-
|
|
30
|
-
const args = ['--print', '--force', '--workspace', '.', analysisPrompt];
|
|
31
|
-
|
|
32
|
-
console.log('🔵 [analyze_prompt] Spawning cursor-agent process...');
|
|
33
|
-
const proc = spawn(
|
|
34
|
-
'cursor-agent',
|
|
35
|
-
args,
|
|
36
|
-
{
|
|
37
|
-
cwd: ROOT,
|
|
38
|
-
env: process.env,
|
|
39
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
40
|
-
}
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
console.log('🔵 [analyze_prompt] Process spawned, PID:', proc.pid);
|
|
44
|
-
|
|
45
|
-
let stdout = '';
|
|
46
|
-
let stderr = '';
|
|
47
|
-
|
|
48
|
-
timeoutId = setTimeout(() => {
|
|
49
|
-
if (!responseSent && proc && !proc.killed) {
|
|
50
|
-
console.log('⏱️ [analyze_prompt] Timeout reached, killing process...');
|
|
51
|
-
proc.kill('SIGTERM');
|
|
52
|
-
setTimeout(() => {
|
|
53
|
-
if (!proc.killed) proc.kill('SIGKILL');
|
|
54
|
-
}, 5000);
|
|
55
|
-
|
|
56
|
-
if (!responseSent) {
|
|
57
|
-
responseSent = true;
|
|
58
|
-
res.status(500).json({
|
|
59
|
-
error: 'Process timeout',
|
|
60
|
-
message: `Analysis exceeded timeout of ${timeoutMs / 1000} seconds`
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}, timeoutMs);
|
|
65
|
-
|
|
66
|
-
proc.stdout.on('data', (d) => {
|
|
67
|
-
stdout += d.toString();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
proc.stderr.on('data', (d) => {
|
|
71
|
-
stderr += d.toString();
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
proc.on('error', (error) => {
|
|
75
|
-
console.log('❌ [analyze_prompt] Process error:', error.message);
|
|
76
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
77
|
-
if (!responseSent) {
|
|
78
|
-
responseSent = true;
|
|
79
|
-
return res.status(500).json({ error: error.message });
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
proc.on('close', (code, signal) => {
|
|
84
|
-
console.log('🔵 [analyze_prompt] Process closed with code:', code);
|
|
85
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
86
|
-
|
|
87
|
-
if (!responseSent) {
|
|
88
|
-
responseSent = true;
|
|
89
|
-
|
|
90
|
-
// Try to parse JSON from the output
|
|
91
|
-
try {
|
|
92
|
-
// Look for JSON in the output - it might be wrapped in other text
|
|
93
|
-
const jsonMatch = stdout.match(/\{[\s\S]*"files"[\s\S]*\}/);
|
|
94
|
-
if (jsonMatch) {
|
|
95
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
96
|
-
console.log('✅ [analyze_prompt] Parsed files:', parsed.files);
|
|
97
|
-
res.json({ files: parsed.files || [] });
|
|
98
|
-
} else {
|
|
99
|
-
console.log('⚠️ [analyze_prompt] No JSON found in output, returning raw');
|
|
100
|
-
res.json({
|
|
101
|
-
files: [],
|
|
102
|
-
raw: stdout,
|
|
103
|
-
warning: 'Could not parse structured response'
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
} catch (parseError) {
|
|
107
|
-
console.log('⚠️ [analyze_prompt] JSON parse error:', parseError.message);
|
|
108
|
-
res.json({
|
|
109
|
-
files: [],
|
|
110
|
-
raw: stdout,
|
|
111
|
-
warning: 'Could not parse JSON: ' + parseError.message
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// POST /prompt_agent - Execute cursor-agent command
|
|
119
|
-
app.post('/prompt_agent', (req, res) => {
|
|
120
|
-
console.log('🔵 [prompt_agent] Endpoint hit');
|
|
121
|
-
const { prompt } = req.body;
|
|
122
|
-
console.log('🔵 [prompt_agent] Received prompt:', prompt ? `${prompt.substring(0, 50)}...` : 'none');
|
|
123
|
-
|
|
124
|
-
if (!prompt || typeof prompt !== 'string') {
|
|
125
|
-
console.log('❌ [prompt_agent] Error: prompt required');
|
|
126
|
-
return res.status(400).json({ error: 'prompt required' });
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Capture beforeCommit
|
|
130
|
-
let beforeCommit = '';
|
|
131
|
-
try {
|
|
132
|
-
beforeCommit = execSync('git rev-parse HEAD', { cwd: ROOT }).toString().trim();
|
|
133
|
-
console.log('🔵 [prompt_agent] beforeCommit:', beforeCommit);
|
|
134
|
-
} catch (e) {
|
|
135
|
-
console.log('⚠️ [prompt_agent] Could not get beforeCommit:', e.message);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Capture initial state of modified files (files already dirty before job starts)
|
|
139
|
-
const initiallyModifiedFiles = new Set();
|
|
140
|
-
try {
|
|
141
|
-
const initialStatus = execSync('git status --short', { cwd: ROOT }).toString();
|
|
142
|
-
initialStatus.split('\n').filter(Boolean).forEach(line => {
|
|
143
|
-
const filePath = line.substring(3).trim();
|
|
144
|
-
if (filePath) initiallyModifiedFiles.add(filePath);
|
|
145
|
-
});
|
|
146
|
-
console.log('🔵 [prompt_agent] Initially modified files:', Array.from(initiallyModifiedFiles));
|
|
147
|
-
} catch (e) {
|
|
148
|
-
console.log('⚠️ [prompt_agent] Could not get initial status:', e.message);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Set up file change tracking - only track NEW changes during job
|
|
152
|
-
const changedFiles = new Set();
|
|
153
|
-
const pollInterval = setInterval(() => {
|
|
154
|
-
try {
|
|
155
|
-
const status = execSync('git status --short', { cwd: ROOT }).toString();
|
|
156
|
-
status.split('\n').filter(Boolean).forEach(line => {
|
|
157
|
-
const filePath = line.substring(3).trim(); // Remove status prefix (XY + space)
|
|
158
|
-
// Only add if this file was NOT already modified before the job started
|
|
159
|
-
if (filePath && !initiallyModifiedFiles.has(filePath)) {
|
|
160
|
-
const wasNew = !changedFiles.has(filePath);
|
|
161
|
-
changedFiles.add(filePath);
|
|
162
|
-
if (wasNew) {
|
|
163
|
-
console.log('📁 [prompt_agent] New file changed:', filePath);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
} catch (e) {
|
|
168
|
-
// Ignore git status errors
|
|
169
|
-
}
|
|
170
|
-
}, 500);
|
|
171
|
-
|
|
172
|
-
// Configurable timeout (default 5 minutes)
|
|
173
|
-
const timeoutMs = parseInt(req.body.timeout) || 5 * 60 * 1000;
|
|
174
|
-
let timeoutId = null;
|
|
175
|
-
let responseSent = false;
|
|
176
|
-
|
|
177
|
-
// Build command arguments
|
|
178
|
-
const args = ['--print', '--force', '--workspace', '.', prompt];
|
|
179
|
-
|
|
180
|
-
console.log('🔵 [prompt_agent] Spawning cursor-agent process...');
|
|
181
|
-
const proc = spawn(
|
|
182
|
-
'cursor-agent',
|
|
183
|
-
args,
|
|
184
|
-
{
|
|
185
|
-
cwd: ROOT,
|
|
186
|
-
env: process.env,
|
|
187
|
-
stdio: ['ignore', 'pipe', 'pipe'] // Ignore stdin, pipe stdout/stderr
|
|
188
|
-
}
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
console.log('🔵 [prompt_agent] Process spawned, PID:', proc.pid);
|
|
192
|
-
|
|
193
|
-
let stdout = '';
|
|
194
|
-
let stderr = '';
|
|
195
|
-
|
|
196
|
-
// Set up timeout to kill process if it takes too long
|
|
197
|
-
timeoutId = setTimeout(() => {
|
|
198
|
-
if (!responseSent && proc && !proc.killed) {
|
|
199
|
-
console.log('⏱️ [prompt_agent] Timeout reached, killing process...');
|
|
200
|
-
clearInterval(pollInterval);
|
|
201
|
-
proc.kill('SIGTERM');
|
|
202
|
-
|
|
203
|
-
// Force kill after a short grace period if SIGTERM doesn't work
|
|
204
|
-
setTimeout(() => {
|
|
205
|
-
if (!proc.killed) {
|
|
206
|
-
console.log('💀 [prompt_agent] Force killing process...');
|
|
207
|
-
proc.kill('SIGKILL');
|
|
208
|
-
}
|
|
209
|
-
}, 5000);
|
|
210
|
-
|
|
211
|
-
if (!responseSent) {
|
|
212
|
-
responseSent = true;
|
|
213
|
-
res.status(500).json({
|
|
214
|
-
error: 'Process timeout',
|
|
215
|
-
message: `cursor-agent exceeded timeout of ${timeoutMs / 1000} seconds`,
|
|
216
|
-
code: -1,
|
|
217
|
-
stdout,
|
|
218
|
-
stderr,
|
|
219
|
-
changedFiles: Array.from(changedFiles),
|
|
220
|
-
beforeCommit,
|
|
221
|
-
afterCommit: ''
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}, timeoutMs);
|
|
226
|
-
|
|
227
|
-
proc.stdout.on('data', (d) => {
|
|
228
|
-
const data = d.toString();
|
|
229
|
-
console.log('📤 [prompt_agent] stdout data received:', data.length, 'bytes');
|
|
230
|
-
stdout += data;
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
proc.stderr.on('data', (d) => {
|
|
234
|
-
const data = d.toString();
|
|
235
|
-
console.log('⚠️ [prompt_agent] stderr data received:', data.length, 'bytes');
|
|
236
|
-
stderr += data;
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
proc.on('error', (error) => {
|
|
240
|
-
console.log('❌ [prompt_agent] Process error:', error.message);
|
|
241
|
-
clearInterval(pollInterval);
|
|
242
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
243
|
-
if (!responseSent) {
|
|
244
|
-
responseSent = true;
|
|
245
|
-
return res.status(500).json({ error: error.message });
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
proc.on('close', (code, signal) => {
|
|
250
|
-
console.log('🔵 [prompt_agent] Process closed with code:', code, 'signal:', signal);
|
|
251
|
-
console.log('🔵 [prompt_agent] stdout length:', stdout.length);
|
|
252
|
-
console.log('🔵 [prompt_agent] stderr length:', stderr.length);
|
|
253
|
-
|
|
254
|
-
// Stop polling for file changes
|
|
255
|
-
clearInterval(pollInterval);
|
|
256
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
257
|
-
|
|
258
|
-
// Capture afterCommit
|
|
259
|
-
let afterCommit = '';
|
|
260
|
-
try {
|
|
261
|
-
afterCommit = execSync('git rev-parse HEAD', { cwd: ROOT }).toString().trim();
|
|
262
|
-
console.log('🔵 [prompt_agent] afterCommit:', afterCommit);
|
|
263
|
-
} catch (e) {
|
|
264
|
-
console.log('⚠️ [prompt_agent] Could not get afterCommit:', e.message);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (!responseSent) {
|
|
268
|
-
responseSent = true;
|
|
269
|
-
// Check if process was killed due to timeout
|
|
270
|
-
if (signal === 'SIGTERM' || signal === 'SIGKILL') {
|
|
271
|
-
res.status(500).json({
|
|
272
|
-
error: 'Process terminated',
|
|
273
|
-
message: signal === 'SIGTERM' ? 'Process was terminated due to timeout' : 'Process was force killed',
|
|
274
|
-
code: code || -1,
|
|
275
|
-
stdout,
|
|
276
|
-
stderr,
|
|
277
|
-
changedFiles: Array.from(changedFiles),
|
|
278
|
-
beforeCommit,
|
|
279
|
-
afterCommit
|
|
280
|
-
});
|
|
281
|
-
} else {
|
|
282
|
-
res.json({
|
|
283
|
-
code,
|
|
284
|
-
stdout,
|
|
285
|
-
stderr,
|
|
286
|
-
changedFiles: Array.from(changedFiles),
|
|
287
|
-
beforeCommit,
|
|
288
|
-
afterCommit
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
// POST /prompt_agent_stream - Execute cursor-agent with SSE streaming
|
|
296
|
-
app.post('/prompt_agent_stream', (req, res) => {
|
|
297
|
-
console.log('🔵 [prompt_agent_stream] Endpoint hit');
|
|
298
|
-
const { prompt } = req.body;
|
|
299
|
-
console.log('🔵 [prompt_agent_stream] Received prompt:', prompt ? `${prompt.substring(0, 50)}...` : 'none');
|
|
300
|
-
|
|
301
|
-
if (!prompt || typeof prompt !== 'string') {
|
|
302
|
-
console.log('❌ [prompt_agent_stream] Error: prompt required');
|
|
303
|
-
return res.status(400).json({ error: 'prompt required' });
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Set up SSE headers
|
|
307
|
-
res.setHeader('Content-Type', 'text/event-stream');
|
|
308
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
309
|
-
res.setHeader('Connection', 'keep-alive');
|
|
310
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
311
|
-
res.flushHeaders();
|
|
312
|
-
|
|
313
|
-
// Helper to send SSE events
|
|
314
|
-
const sendEvent = (type, data) => {
|
|
315
|
-
res.write(`data: ${JSON.stringify({ type, ...data })}\n\n`);
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
// Capture beforeCommit
|
|
319
|
-
let beforeCommit = '';
|
|
320
|
-
try {
|
|
321
|
-
beforeCommit = execSync('git rev-parse HEAD', { cwd: ROOT }).toString().trim();
|
|
322
|
-
console.log('🔵 [prompt_agent_stream] beforeCommit:', beforeCommit);
|
|
323
|
-
} catch (e) {
|
|
324
|
-
console.log('⚠️ [prompt_agent_stream] Could not get beforeCommit:', e.message);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Capture initial state of modified files
|
|
328
|
-
const initiallyModifiedFiles = new Set();
|
|
329
|
-
try {
|
|
330
|
-
const initialStatus = execSync('git status --short', { cwd: ROOT }).toString();
|
|
331
|
-
initialStatus.split('\n').filter(Boolean).forEach(line => {
|
|
332
|
-
const filePath = line.substring(3).trim();
|
|
333
|
-
if (filePath) initiallyModifiedFiles.add(filePath);
|
|
334
|
-
});
|
|
335
|
-
} catch (e) {
|
|
336
|
-
// Ignore
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Send starting event
|
|
340
|
-
sendEvent('start', { beforeCommit });
|
|
341
|
-
|
|
342
|
-
// Set up file change tracking with real-time updates
|
|
343
|
-
const changedFiles = new Set();
|
|
344
|
-
const pollInterval = setInterval(() => {
|
|
345
|
-
try {
|
|
346
|
-
const status = execSync('git status --short', { cwd: ROOT }).toString();
|
|
347
|
-
status.split('\n').filter(Boolean).forEach(line => {
|
|
348
|
-
const filePath = line.substring(3).trim();
|
|
349
|
-
if (filePath && !initiallyModifiedFiles.has(filePath)) {
|
|
350
|
-
if (!changedFiles.has(filePath)) {
|
|
351
|
-
changedFiles.add(filePath);
|
|
352
|
-
console.log('📁 [prompt_agent_stream] File changed:', filePath);
|
|
353
|
-
// Send real-time update to client
|
|
354
|
-
sendEvent('file_changed', { path: filePath });
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
} catch (e) {
|
|
359
|
-
// Ignore git status errors
|
|
360
|
-
}
|
|
361
|
-
}, 500);
|
|
362
|
-
|
|
363
|
-
const timeoutMs = parseInt(req.body.timeout) || 5 * 60 * 1000;
|
|
364
|
-
let timeoutId = null;
|
|
365
|
-
let responseSent = false;
|
|
366
|
-
|
|
367
|
-
const args = ['--print', '--force', '--workspace', '.', prompt];
|
|
368
|
-
|
|
369
|
-
console.log('🔵 [prompt_agent_stream] Spawning cursor-agent process...');
|
|
370
|
-
const proc = spawn(
|
|
371
|
-
'cursor-agent',
|
|
372
|
-
args,
|
|
373
|
-
{
|
|
374
|
-
cwd: ROOT,
|
|
375
|
-
env: process.env,
|
|
376
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
377
|
-
}
|
|
378
|
-
);
|
|
379
|
-
|
|
380
|
-
console.log('🔵 [prompt_agent_stream] Process spawned, PID:', proc.pid);
|
|
381
|
-
|
|
382
|
-
let stdout = '';
|
|
383
|
-
let stderr = '';
|
|
384
|
-
|
|
385
|
-
timeoutId = setTimeout(() => {
|
|
386
|
-
if (!responseSent && proc && !proc.killed) {
|
|
387
|
-
console.log('⏱️ [prompt_agent_stream] Timeout reached');
|
|
388
|
-
clearInterval(pollInterval);
|
|
389
|
-
proc.kill('SIGTERM');
|
|
390
|
-
|
|
391
|
-
setTimeout(() => {
|
|
392
|
-
if (!proc.killed) proc.kill('SIGKILL');
|
|
393
|
-
}, 5000);
|
|
394
|
-
|
|
395
|
-
if (!responseSent) {
|
|
396
|
-
responseSent = true;
|
|
397
|
-
sendEvent('error', {
|
|
398
|
-
error: 'Process timeout',
|
|
399
|
-
message: `cursor-agent exceeded timeout of ${timeoutMs / 1000} seconds`
|
|
400
|
-
});
|
|
401
|
-
sendEvent('complete', {
|
|
402
|
-
code: -1,
|
|
403
|
-
stdout,
|
|
404
|
-
stderr,
|
|
405
|
-
changedFiles: Array.from(changedFiles),
|
|
406
|
-
beforeCommit,
|
|
407
|
-
afterCommit: ''
|
|
408
|
-
});
|
|
409
|
-
res.end();
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}, timeoutMs);
|
|
413
|
-
|
|
414
|
-
proc.stdout.on('data', (d) => {
|
|
415
|
-
stdout += d.toString();
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
proc.stderr.on('data', (d) => {
|
|
419
|
-
stderr += d.toString();
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
proc.on('error', (error) => {
|
|
423
|
-
console.log('❌ [prompt_agent_stream] Process error:', error.message);
|
|
424
|
-
clearInterval(pollInterval);
|
|
425
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
426
|
-
if (!responseSent) {
|
|
427
|
-
responseSent = true;
|
|
428
|
-
sendEvent('error', { error: error.message });
|
|
429
|
-
res.end();
|
|
430
|
-
}
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
proc.on('close', (code, signal) => {
|
|
434
|
-
console.log('🔵 [prompt_agent_stream] Process closed with code:', code);
|
|
435
|
-
clearInterval(pollInterval);
|
|
436
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
437
|
-
|
|
438
|
-
let afterCommit = '';
|
|
439
|
-
try {
|
|
440
|
-
afterCommit = execSync('git rev-parse HEAD', { cwd: ROOT }).toString().trim();
|
|
441
|
-
} catch (e) {
|
|
442
|
-
// Ignore
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
if (!responseSent) {
|
|
446
|
-
responseSent = true;
|
|
447
|
-
sendEvent('complete', {
|
|
448
|
-
code,
|
|
449
|
-
stdout,
|
|
450
|
-
stderr,
|
|
451
|
-
changedFiles: Array.from(changedFiles),
|
|
452
|
-
beforeCommit,
|
|
453
|
-
afterCommit
|
|
454
|
-
});
|
|
455
|
-
res.end();
|
|
456
|
-
}
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
// Handle client disconnect - DON'T kill the process, let it complete
|
|
460
|
-
req.on('close', () => {
|
|
461
|
-
console.log('🔵 [prompt_agent_stream] Client disconnected (process continues in background)');
|
|
462
|
-
// Don't kill the process - let it complete
|
|
463
|
-
// Just mark that we shouldn't try to send more events
|
|
464
|
-
responseSent = true;
|
|
465
|
-
});
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
// POST /revert_job - Revert to a previous commit
|
|
469
|
-
app.post('/revert_job', (req, res) => {
|
|
470
|
-
console.log('🔵 [revert_job] Endpoint hit');
|
|
471
|
-
const { beforeCommit } = req.body;
|
|
472
|
-
|
|
473
|
-
if (!beforeCommit || typeof beforeCommit !== 'string') {
|
|
474
|
-
console.log('❌ [revert_job] Error: beforeCommit required');
|
|
475
|
-
return res.status(400).json({ error: 'beforeCommit required' });
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// Validate commit hash format (basic sanitization to prevent command injection)
|
|
479
|
-
if (!/^[a-f0-9]{7,40}$/i.test(beforeCommit)) {
|
|
480
|
-
console.log('❌ [revert_job] Error: invalid commit hash format');
|
|
481
|
-
return res.status(400).json({ error: 'Invalid commit hash format' });
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
try {
|
|
485
|
-
console.log('🔵 [revert_job] Resetting to commit:', beforeCommit);
|
|
486
|
-
execSync(`git reset --hard ${beforeCommit}`, { cwd: ROOT });
|
|
487
|
-
console.log('✅ [revert_job] Successfully reverted to commit:', beforeCommit);
|
|
488
|
-
res.json({ success: true });
|
|
489
|
-
} catch (e) {
|
|
490
|
-
console.log('❌ [revert_job] Error:', e.message);
|
|
491
|
-
res.status(500).json({ error: e.message });
|
|
492
|
-
}
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
module.exports = { setupAiRoutes };
|
|
497
|
-
|
package/templates/routes/crud.js
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const { listAllFiles } = require('../utils/file-list');
|
|
4
|
-
const { extractPythonFunctions, extractJavaScriptFunctions, extractVueFunctions } = require('../utils/parsers');
|
|
5
|
-
|
|
6
|
-
function setupCrudRoutes(app, ROOT) {
|
|
7
|
-
// GET /list - List all files
|
|
8
|
-
app.get('/list', (req, res) => {
|
|
9
|
-
try {
|
|
10
|
-
const rootName = path.basename(ROOT);
|
|
11
|
-
const files = listAllFiles(ROOT, rootName);
|
|
12
|
-
res.json({ files });
|
|
13
|
-
} catch (e) {
|
|
14
|
-
res.status(500).send(e.message);
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
// GET /read - Read file content
|
|
19
|
-
app.get('/read', (req, res) => {
|
|
20
|
-
try {
|
|
21
|
-
const requestedPath = req.query.path || '';
|
|
22
|
-
const filePath = path.join(ROOT, requestedPath);
|
|
23
|
-
|
|
24
|
-
if (!filePath.startsWith(ROOT)) {
|
|
25
|
-
return res.status(400).send('Invalid path');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Helper function to find and return content from parse result
|
|
29
|
-
const findAndReturn = (parseResult, name, type) => {
|
|
30
|
-
if (type === 'function') {
|
|
31
|
-
const target = parseResult.functions.find(f => f.name === name);
|
|
32
|
-
if (target) return target.content;
|
|
33
|
-
} else if (type === 'method') {
|
|
34
|
-
// Method name format: ClassName.methodName
|
|
35
|
-
for (const cls of parseResult.classes) {
|
|
36
|
-
const method = cls.methods.find(m => m.name === name);
|
|
37
|
-
if (method) return method.content;
|
|
38
|
-
}
|
|
39
|
-
} else if (type === 'class') {
|
|
40
|
-
const target = parseResult.classes.find(c => c.name === name);
|
|
41
|
-
if (target) return target.content;
|
|
42
|
-
}
|
|
43
|
-
return null;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// Check if this is a virtual file request (.function, .method, or .class)
|
|
47
|
-
if (requestedPath.endsWith('.function') || requestedPath.endsWith('.method') || requestedPath.endsWith('.class')) {
|
|
48
|
-
// Traverse up the path to find the actual source file
|
|
49
|
-
let currentPath = filePath;
|
|
50
|
-
let sourceFilePath = null;
|
|
51
|
-
let parser = null;
|
|
52
|
-
|
|
53
|
-
// Keep going up until we find a source file (.py, .js, .jsx, .ts, .tsx, .vue)
|
|
54
|
-
while (currentPath !== ROOT && currentPath !== path.dirname(currentPath)) {
|
|
55
|
-
const stat = fs.existsSync(currentPath) ? fs.statSync(currentPath) : null;
|
|
56
|
-
|
|
57
|
-
// Check if current path is a file with a supported extension
|
|
58
|
-
if (stat && stat.isFile()) {
|
|
59
|
-
if (currentPath.endsWith('.py')) {
|
|
60
|
-
parser = extractPythonFunctions;
|
|
61
|
-
sourceFilePath = currentPath;
|
|
62
|
-
break;
|
|
63
|
-
} else if (currentPath.endsWith('.js') || currentPath.endsWith('.jsx') ||
|
|
64
|
-
currentPath.endsWith('.ts') || currentPath.endsWith('.tsx')) {
|
|
65
|
-
parser = extractJavaScriptFunctions;
|
|
66
|
-
sourceFilePath = currentPath;
|
|
67
|
-
break;
|
|
68
|
-
} else if (currentPath.endsWith('.vue')) {
|
|
69
|
-
parser = extractVueFunctions;
|
|
70
|
-
sourceFilePath = currentPath;
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Move up one level
|
|
76
|
-
const parentPath = path.dirname(currentPath);
|
|
77
|
-
if (parentPath === currentPath) break; // Reached root
|
|
78
|
-
currentPath = parentPath;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (!sourceFilePath || !parser) {
|
|
82
|
-
return res.status(404).send('Source file not found for virtual file');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Extract the requested item name from the requested path
|
|
86
|
-
let itemName = '';
|
|
87
|
-
let itemType = '';
|
|
88
|
-
|
|
89
|
-
if (requestedPath.endsWith('.function')) {
|
|
90
|
-
itemName = path.basename(requestedPath, '.function');
|
|
91
|
-
itemType = 'function';
|
|
92
|
-
} else if (requestedPath.endsWith('.method')) {
|
|
93
|
-
itemName = path.basename(requestedPath, '.method');
|
|
94
|
-
itemType = 'method';
|
|
95
|
-
} else if (requestedPath.endsWith('.class')) {
|
|
96
|
-
itemName = path.basename(requestedPath, '.class');
|
|
97
|
-
itemType = 'class';
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Check if the source file exists
|
|
101
|
-
try {
|
|
102
|
-
if (!fs.existsSync(sourceFilePath)) {
|
|
103
|
-
return res.status(404).send('Source file not found');
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Parse the file
|
|
107
|
-
const parseResult = parser(sourceFilePath);
|
|
108
|
-
|
|
109
|
-
// Find and return the content
|
|
110
|
-
const content = findAndReturn(parseResult, itemName, itemType);
|
|
111
|
-
|
|
112
|
-
if (!content) {
|
|
113
|
-
return res.status(404).send(`${itemType} '${itemName}' not found in file`);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return res.json({ content });
|
|
117
|
-
} catch (e) {
|
|
118
|
-
const errorType = requestedPath.endsWith('.function') ? 'function' :
|
|
119
|
-
requestedPath.endsWith('.method') ? 'method' : 'class';
|
|
120
|
-
return res.status(500).send('Error reading ' + errorType + ': ' + e.message);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Regular file read
|
|
125
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
126
|
-
res.json({ content });
|
|
127
|
-
} catch (e) {
|
|
128
|
-
res.status(500).send(e.message);
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// POST /write - Write file content
|
|
133
|
-
app.post('/write', (req, res) => {
|
|
134
|
-
try {
|
|
135
|
-
const filePath = path.join(ROOT, req.body.path || '');
|
|
136
|
-
if (!filePath.startsWith(ROOT)) {
|
|
137
|
-
return res.status(400).send('Invalid path');
|
|
138
|
-
}
|
|
139
|
-
fs.writeFileSync(filePath, req.body.content, 'utf8');
|
|
140
|
-
res.json({ status: 'ok' });
|
|
141
|
-
} catch (e) {
|
|
142
|
-
res.status(500).send(e.message);
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// POST /write_dir - Create directory
|
|
147
|
-
app.post('/write_dir', (req, res) => {
|
|
148
|
-
try {
|
|
149
|
-
const dirPath = path.join(ROOT, req.body.path || '');
|
|
150
|
-
if (!dirPath.startsWith(ROOT)) {
|
|
151
|
-
return res.status(400).send('Invalid path');
|
|
152
|
-
}
|
|
153
|
-
// Create directory recursively (creates parent directories if they don't exist)
|
|
154
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
155
|
-
res.json({ status: 'ok' });
|
|
156
|
-
} catch (e) {
|
|
157
|
-
res.status(500).send(e.message);
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// POST /delete - Delete file or directory
|
|
162
|
-
app.post('/delete', (req, res) => {
|
|
163
|
-
try {
|
|
164
|
-
const targetPath = path.join(ROOT, req.body.path || '');
|
|
165
|
-
if (!targetPath.startsWith(ROOT)) {
|
|
166
|
-
return res.status(400).send('Invalid path');
|
|
167
|
-
}
|
|
168
|
-
// Delete file or directory recursively
|
|
169
|
-
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
170
|
-
res.json({ status: 'ok' });
|
|
171
|
-
} catch (e) {
|
|
172
|
-
res.status(500).send(e.message);
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// POST /move - Move file or folder
|
|
177
|
-
app.post('/move', (req, res) => {
|
|
178
|
-
try {
|
|
179
|
-
const sourcePath = path.join(ROOT, req.body.source || '');
|
|
180
|
-
const destinationPath = path.join(ROOT, req.body.destination || '');
|
|
181
|
-
|
|
182
|
-
// Validate both paths are within ROOT directory
|
|
183
|
-
if (!sourcePath.startsWith(ROOT) || !destinationPath.startsWith(ROOT)) {
|
|
184
|
-
return res.status(400).send('Invalid path');
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Check if source exists
|
|
188
|
-
if (!fs.existsSync(sourcePath)) {
|
|
189
|
-
return res.status(400).send('Source path does not exist');
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Ensure destination directory exists
|
|
193
|
-
const destinationDir = path.dirname(destinationPath);
|
|
194
|
-
if (!fs.existsSync(destinationDir)) {
|
|
195
|
-
fs.mkdirSync(destinationDir, { recursive: true });
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Move the file or folder
|
|
199
|
-
fs.renameSync(sourcePath, destinationPath);
|
|
200
|
-
res.json({ status: 'ok' });
|
|
201
|
-
} catch (e) {
|
|
202
|
-
res.status(500).send(e.message);
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
module.exports = { setupCrudRoutes };
|
|
208
|
-
|