devbonzai 2.1.2 → 2.1.3
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/ignore.txt +0 -53
- package/templates/receiver.js +0 -26
- 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/cli.js
CHANGED
|
@@ -4,30 +4,18 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { spawn, exec } = require('child_process');
|
|
6
6
|
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
for (const entry of entries) {
|
|
16
|
-
const srcPath = path.join(src, entry.name);
|
|
17
|
-
const destPath = path.join(dest, entry.name);
|
|
18
|
-
|
|
19
|
-
if (entry.isDirectory()) {
|
|
20
|
-
copyDirSync(srcPath, destPath);
|
|
21
|
-
} else {
|
|
22
|
-
fs.copyFileSync(srcPath, destPath);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
7
|
+
// Read templates at runtime from the templates directory
|
|
8
|
+
const IGNORE_FILE_CONTENT = fs.readFileSync(
|
|
9
|
+
path.join(__dirname, 'templates', 'ignore.txt'), 'utf8'
|
|
10
|
+
);
|
|
11
|
+
const RECEIVER_JS_CONTENT = fs.readFileSync(
|
|
12
|
+
path.join(__dirname, 'templates', 'receiver.js'), 'utf8'
|
|
13
|
+
);
|
|
26
14
|
|
|
27
15
|
async function main() {
|
|
28
16
|
const currentDir = process.cwd();
|
|
29
17
|
const bonzaiDir = path.join(currentDir, 'bonzai');
|
|
30
|
-
const
|
|
18
|
+
const receiverPath = path.join(bonzaiDir, 'receiver.js');
|
|
31
19
|
|
|
32
20
|
console.log('🚀 Setting up local file server...');
|
|
33
21
|
|
|
@@ -37,14 +25,21 @@ async function main() {
|
|
|
37
25
|
fs.mkdirSync(bonzaiDir);
|
|
38
26
|
}
|
|
39
27
|
|
|
40
|
-
//
|
|
41
|
-
console.log('📝
|
|
42
|
-
|
|
28
|
+
// Write receiver.js to current directory (root level)
|
|
29
|
+
console.log('📝 Writing receiver.js...');
|
|
30
|
+
fs.writeFileSync(receiverPath, RECEIVER_JS_CONTENT);
|
|
43
31
|
|
|
44
|
-
// Make
|
|
45
|
-
const receiverPath = path.join(bonzaiDir, 'receiver.js');
|
|
32
|
+
// Make it executable
|
|
46
33
|
fs.chmodSync(receiverPath, '755');
|
|
47
34
|
|
|
35
|
+
// Write .ignore file in bonzai directory
|
|
36
|
+
const ignoreTargetPath = path.join(bonzaiDir, '.ignore');
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(ignoreTargetPath)) {
|
|
39
|
+
console.log('📝 Writing .ignore file...');
|
|
40
|
+
fs.writeFileSync(ignoreTargetPath, IGNORE_FILE_CONTENT);
|
|
41
|
+
}
|
|
42
|
+
|
|
48
43
|
console.log('📦 Installing dependencies...');
|
|
49
44
|
|
|
50
45
|
// Check if package.json exists in bonzai directory
|
|
@@ -58,7 +53,7 @@ async function main() {
|
|
|
58
53
|
name: "bonzai-server",
|
|
59
54
|
version: "1.0.0",
|
|
60
55
|
description: "Dependencies for devbonzai file server",
|
|
61
|
-
main: "receiver.js",
|
|
56
|
+
main: "../receiver.js",
|
|
62
57
|
scripts: {
|
|
63
58
|
test: "echo \"Error: no test specified\" && exit 1"
|
|
64
59
|
},
|
package/package.json
CHANGED
package/templates/ignore.txt
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# Ignore patterns for file listing
|
|
2
|
-
# Lines starting with # are comments
|
|
3
|
-
# Use * for wildcards and ** for recursive patterns
|
|
4
|
-
|
|
5
|
-
# Dependencies
|
|
6
|
-
node_modules/
|
|
7
|
-
package.json
|
|
8
|
-
package-lock.json
|
|
9
|
-
|
|
10
|
-
# IDE and editor files
|
|
11
|
-
.vscode/
|
|
12
|
-
.idea/
|
|
13
|
-
*.swp
|
|
14
|
-
*.swo
|
|
15
|
-
|
|
16
|
-
# OS generated files
|
|
17
|
-
.DS_Store
|
|
18
|
-
.DS_Store?
|
|
19
|
-
._*
|
|
20
|
-
.Spotlight-V100
|
|
21
|
-
.Trashes
|
|
22
|
-
ehthumbs.db
|
|
23
|
-
Thumbs.db
|
|
24
|
-
|
|
25
|
-
# Environment and config files
|
|
26
|
-
.env
|
|
27
|
-
.env.local
|
|
28
|
-
.env.production
|
|
29
|
-
.env.staging
|
|
30
|
-
|
|
31
|
-
# Version control
|
|
32
|
-
.git/
|
|
33
|
-
.gitignore
|
|
34
|
-
.ignore
|
|
35
|
-
|
|
36
|
-
# Logs
|
|
37
|
-
*.log
|
|
38
|
-
logs/
|
|
39
|
-
|
|
40
|
-
# Build outputs
|
|
41
|
-
dist/
|
|
42
|
-
build/
|
|
43
|
-
*.min.js
|
|
44
|
-
*.min.css
|
|
45
|
-
|
|
46
|
-
# Temporary files
|
|
47
|
-
*.tmp
|
|
48
|
-
*.temp
|
|
49
|
-
|
|
50
|
-
# Project-specific files
|
|
51
|
-
receiver.js
|
|
52
|
-
bonzai/
|
|
53
|
-
|
package/templates/receiver.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const express = require('./node_modules/express');
|
|
4
|
-
const cors = require('./node_modules/cors');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
|
|
7
|
-
// Import route handlers
|
|
8
|
-
const { setupCrudRoutes } = require('./routes/crud');
|
|
9
|
-
const { setupInfraRoutes } = require('./routes/infra');
|
|
10
|
-
const { setupAiRoutes } = require('./routes/ai');
|
|
11
|
-
|
|
12
|
-
const app = express();
|
|
13
|
-
const ROOT = path.join(__dirname, '..');
|
|
14
|
-
|
|
15
|
-
app.use(cors());
|
|
16
|
-
app.use(express.json());
|
|
17
|
-
|
|
18
|
-
// Setup all routes
|
|
19
|
-
setupInfraRoutes(app, ROOT);
|
|
20
|
-
setupCrudRoutes(app, ROOT);
|
|
21
|
-
setupAiRoutes(app, ROOT);
|
|
22
|
-
|
|
23
|
-
const port = 3001;
|
|
24
|
-
app.listen(port, () => {
|
|
25
|
-
console.log('📂 File server running on http://localhost:' + port);
|
|
26
|
-
});
|
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
|
-
|