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.
@@ -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
-
@@ -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
-