devbonzai 2.0.5 → 2.0.7
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 +291 -0
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -91,7 +91,9 @@ app.get('/', (req, res) => {
|
|
|
91
91
|
'POST /delete': 'Delete file or directory (body: {path})',
|
|
92
92
|
'POST /move': 'Move file or folder (body: {source, destination})',
|
|
93
93
|
'POST /open-cursor': 'Open Cursor (body: {path, line?})',
|
|
94
|
+
'POST /analyze_prompt': 'Analyze what files would be modified (body: {prompt})',
|
|
94
95
|
'POST /prompt_agent': 'Execute cursor-agent command (body: {prompt})',
|
|
96
|
+
'POST /prompt_agent_stream': 'Execute cursor-agent with SSE streaming updates (body: {prompt})',
|
|
95
97
|
'POST /revert_job': 'Revert to a previous commit (body: {beforeCommit})',
|
|
96
98
|
'POST /shutdown': 'Gracefully shutdown the server'
|
|
97
99
|
},
|
|
@@ -1275,6 +1277,120 @@ app.post('/open-cursor', (req, res) => {
|
|
|
1275
1277
|
}
|
|
1276
1278
|
});
|
|
1277
1279
|
|
|
1280
|
+
// Analyze prompt endpoint - asks agent what files it would modify without making changes
|
|
1281
|
+
app.post('/analyze_prompt', (req, res) => {
|
|
1282
|
+
console.log('🔵 [analyze_prompt] Endpoint hit');
|
|
1283
|
+
const { prompt } = req.body;
|
|
1284
|
+
console.log('🔵 [analyze_prompt] Received prompt:', prompt ? \`\${prompt.substring(0, 50)}...\` : 'none');
|
|
1285
|
+
|
|
1286
|
+
if (!prompt || typeof prompt !== 'string') {
|
|
1287
|
+
console.log('❌ [analyze_prompt] Error: prompt required');
|
|
1288
|
+
return res.status(400).json({ error: 'prompt required' });
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
// Configurable timeout (default 2 minutes for analysis)
|
|
1292
|
+
const timeoutMs = parseInt(req.body.timeout) || 2 * 60 * 1000;
|
|
1293
|
+
let timeoutId = null;
|
|
1294
|
+
let responseSent = false;
|
|
1295
|
+
|
|
1296
|
+
// Build analysis prompt - ask agent to list files without making changes
|
|
1297
|
+
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.
|
|
1298
|
+
|
|
1299
|
+
Respond ONLY with valid JSON in this exact format (no other text):
|
|
1300
|
+
{"files": [{"path": "path/to/file.ext", "reason": "brief reason for modification"}]}
|
|
1301
|
+
|
|
1302
|
+
If no files need modification, respond with: {"files": []}
|
|
1303
|
+
|
|
1304
|
+
Task to analyze: \${prompt}\`;
|
|
1305
|
+
|
|
1306
|
+
const args = ['--print', '--force', '--workspace', '.', analysisPrompt];
|
|
1307
|
+
|
|
1308
|
+
console.log('🔵 [analyze_prompt] Spawning cursor-agent process...');
|
|
1309
|
+
const proc = spawn(
|
|
1310
|
+
'cursor-agent',
|
|
1311
|
+
args,
|
|
1312
|
+
{
|
|
1313
|
+
cwd: ROOT,
|
|
1314
|
+
env: process.env,
|
|
1315
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
1316
|
+
}
|
|
1317
|
+
);
|
|
1318
|
+
|
|
1319
|
+
console.log('🔵 [analyze_prompt] Process spawned, PID:', proc.pid);
|
|
1320
|
+
|
|
1321
|
+
let stdout = '';
|
|
1322
|
+
let stderr = '';
|
|
1323
|
+
|
|
1324
|
+
timeoutId = setTimeout(() => {
|
|
1325
|
+
if (!responseSent && proc && !proc.killed) {
|
|
1326
|
+
console.log('⏱️ [analyze_prompt] Timeout reached, killing process...');
|
|
1327
|
+
proc.kill('SIGTERM');
|
|
1328
|
+
setTimeout(() => {
|
|
1329
|
+
if (!proc.killed) proc.kill('SIGKILL');
|
|
1330
|
+
}, 5000);
|
|
1331
|
+
|
|
1332
|
+
if (!responseSent) {
|
|
1333
|
+
responseSent = true;
|
|
1334
|
+
res.status(500).json({
|
|
1335
|
+
error: 'Process timeout',
|
|
1336
|
+
message: \`Analysis exceeded timeout of \${timeoutMs / 1000} seconds\`
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}, timeoutMs);
|
|
1341
|
+
|
|
1342
|
+
proc.stdout.on('data', (d) => {
|
|
1343
|
+
stdout += d.toString();
|
|
1344
|
+
});
|
|
1345
|
+
|
|
1346
|
+
proc.stderr.on('data', (d) => {
|
|
1347
|
+
stderr += d.toString();
|
|
1348
|
+
});
|
|
1349
|
+
|
|
1350
|
+
proc.on('error', (error) => {
|
|
1351
|
+
console.log('❌ [analyze_prompt] Process error:', error.message);
|
|
1352
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1353
|
+
if (!responseSent) {
|
|
1354
|
+
responseSent = true;
|
|
1355
|
+
return res.status(500).json({ error: error.message });
|
|
1356
|
+
}
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1359
|
+
proc.on('close', (code, signal) => {
|
|
1360
|
+
console.log('🔵 [analyze_prompt] Process closed with code:', code);
|
|
1361
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1362
|
+
|
|
1363
|
+
if (!responseSent) {
|
|
1364
|
+
responseSent = true;
|
|
1365
|
+
|
|
1366
|
+
// Try to parse JSON from the output
|
|
1367
|
+
try {
|
|
1368
|
+
// Look for JSON in the output - it might be wrapped in other text
|
|
1369
|
+
const jsonMatch = stdout.match(/\\{[\\s\\S]*"files"[\\s\\S]*\\}/);
|
|
1370
|
+
if (jsonMatch) {
|
|
1371
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
1372
|
+
console.log('✅ [analyze_prompt] Parsed files:', parsed.files);
|
|
1373
|
+
res.json({ files: parsed.files || [] });
|
|
1374
|
+
} else {
|
|
1375
|
+
console.log('⚠️ [analyze_prompt] No JSON found in output, returning raw');
|
|
1376
|
+
res.json({
|
|
1377
|
+
files: [],
|
|
1378
|
+
raw: stdout,
|
|
1379
|
+
warning: 'Could not parse structured response'
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
} catch (parseError) {
|
|
1383
|
+
console.log('⚠️ [analyze_prompt] JSON parse error:', parseError.message);
|
|
1384
|
+
res.json({
|
|
1385
|
+
files: [],
|
|
1386
|
+
raw: stdout,
|
|
1387
|
+
warning: 'Could not parse JSON: ' + parseError.message
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
});
|
|
1393
|
+
|
|
1278
1394
|
app.post('/prompt_agent', (req, res) => {
|
|
1279
1395
|
console.log('🔵 [prompt_agent] Endpoint hit');
|
|
1280
1396
|
const { prompt } = req.body;
|
|
@@ -1451,6 +1567,181 @@ app.post('/prompt_agent', (req, res) => {
|
|
|
1451
1567
|
});
|
|
1452
1568
|
});
|
|
1453
1569
|
|
|
1570
|
+
// Streaming version of prompt_agent using Server-Sent Events
|
|
1571
|
+
app.post('/prompt_agent_stream', (req, res) => {
|
|
1572
|
+
console.log('🔵 [prompt_agent_stream] Endpoint hit');
|
|
1573
|
+
const { prompt } = req.body;
|
|
1574
|
+
console.log('🔵 [prompt_agent_stream] Received prompt:', prompt ? \`\${prompt.substring(0, 50)}...\` : 'none');
|
|
1575
|
+
|
|
1576
|
+
if (!prompt || typeof prompt !== 'string') {
|
|
1577
|
+
console.log('❌ [prompt_agent_stream] Error: prompt required');
|
|
1578
|
+
return res.status(400).json({ error: 'prompt required' });
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
// Set up SSE headers
|
|
1582
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
1583
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
1584
|
+
res.setHeader('Connection', 'keep-alive');
|
|
1585
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
1586
|
+
res.flushHeaders();
|
|
1587
|
+
|
|
1588
|
+
// Helper to send SSE events
|
|
1589
|
+
const sendEvent = (type, data) => {
|
|
1590
|
+
res.write(\`data: \${JSON.stringify({ type, ...data })}\\n\\n\`);
|
|
1591
|
+
};
|
|
1592
|
+
|
|
1593
|
+
// Capture beforeCommit
|
|
1594
|
+
let beforeCommit = '';
|
|
1595
|
+
try {
|
|
1596
|
+
beforeCommit = execSync('git rev-parse HEAD', { cwd: ROOT }).toString().trim();
|
|
1597
|
+
console.log('🔵 [prompt_agent_stream] beforeCommit:', beforeCommit);
|
|
1598
|
+
} catch (e) {
|
|
1599
|
+
console.log('⚠️ [prompt_agent_stream] Could not get beforeCommit:', e.message);
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
// Capture initial state of modified files
|
|
1603
|
+
const initiallyModifiedFiles = new Set();
|
|
1604
|
+
try {
|
|
1605
|
+
const initialStatus = execSync('git status --short', { cwd: ROOT }).toString();
|
|
1606
|
+
initialStatus.split('\\n').filter(Boolean).forEach(line => {
|
|
1607
|
+
const filePath = line.substring(3).trim();
|
|
1608
|
+
if (filePath) initiallyModifiedFiles.add(filePath);
|
|
1609
|
+
});
|
|
1610
|
+
} catch (e) {
|
|
1611
|
+
// Ignore
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
// Send starting event
|
|
1615
|
+
sendEvent('start', { beforeCommit });
|
|
1616
|
+
|
|
1617
|
+
// Set up file change tracking with real-time updates
|
|
1618
|
+
const changedFiles = new Set();
|
|
1619
|
+
const pollInterval = setInterval(() => {
|
|
1620
|
+
try {
|
|
1621
|
+
const status = execSync('git status --short', { cwd: ROOT }).toString();
|
|
1622
|
+
status.split('\\n').filter(Boolean).forEach(line => {
|
|
1623
|
+
const filePath = line.substring(3).trim();
|
|
1624
|
+
if (filePath && !initiallyModifiedFiles.has(filePath)) {
|
|
1625
|
+
if (!changedFiles.has(filePath)) {
|
|
1626
|
+
changedFiles.add(filePath);
|
|
1627
|
+
console.log('📁 [prompt_agent_stream] File changed:', filePath);
|
|
1628
|
+
// Send real-time update to client
|
|
1629
|
+
sendEvent('file_changed', { path: filePath });
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
});
|
|
1633
|
+
} catch (e) {
|
|
1634
|
+
// Ignore git status errors
|
|
1635
|
+
}
|
|
1636
|
+
}, 500);
|
|
1637
|
+
|
|
1638
|
+
const timeoutMs = parseInt(req.body.timeout) || 5 * 60 * 1000;
|
|
1639
|
+
let timeoutId = null;
|
|
1640
|
+
let responseSent = false;
|
|
1641
|
+
|
|
1642
|
+
const args = ['--print', '--force', '--workspace', '.', prompt];
|
|
1643
|
+
|
|
1644
|
+
console.log('🔵 [prompt_agent_stream] Spawning cursor-agent process...');
|
|
1645
|
+
const proc = spawn(
|
|
1646
|
+
'cursor-agent',
|
|
1647
|
+
args,
|
|
1648
|
+
{
|
|
1649
|
+
cwd: ROOT,
|
|
1650
|
+
env: process.env,
|
|
1651
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
1652
|
+
}
|
|
1653
|
+
);
|
|
1654
|
+
|
|
1655
|
+
console.log('🔵 [prompt_agent_stream] Process spawned, PID:', proc.pid);
|
|
1656
|
+
|
|
1657
|
+
let stdout = '';
|
|
1658
|
+
let stderr = '';
|
|
1659
|
+
|
|
1660
|
+
timeoutId = setTimeout(() => {
|
|
1661
|
+
if (!responseSent && proc && !proc.killed) {
|
|
1662
|
+
console.log('⏱️ [prompt_agent_stream] Timeout reached');
|
|
1663
|
+
clearInterval(pollInterval);
|
|
1664
|
+
proc.kill('SIGTERM');
|
|
1665
|
+
|
|
1666
|
+
setTimeout(() => {
|
|
1667
|
+
if (!proc.killed) proc.kill('SIGKILL');
|
|
1668
|
+
}, 5000);
|
|
1669
|
+
|
|
1670
|
+
if (!responseSent) {
|
|
1671
|
+
responseSent = true;
|
|
1672
|
+
sendEvent('error', {
|
|
1673
|
+
error: 'Process timeout',
|
|
1674
|
+
message: \`cursor-agent exceeded timeout of \${timeoutMs / 1000} seconds\`
|
|
1675
|
+
});
|
|
1676
|
+
sendEvent('complete', {
|
|
1677
|
+
code: -1,
|
|
1678
|
+
stdout,
|
|
1679
|
+
stderr,
|
|
1680
|
+
changedFiles: Array.from(changedFiles),
|
|
1681
|
+
beforeCommit,
|
|
1682
|
+
afterCommit: ''
|
|
1683
|
+
});
|
|
1684
|
+
res.end();
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}, timeoutMs);
|
|
1688
|
+
|
|
1689
|
+
proc.stdout.on('data', (d) => {
|
|
1690
|
+
stdout += d.toString();
|
|
1691
|
+
});
|
|
1692
|
+
|
|
1693
|
+
proc.stderr.on('data', (d) => {
|
|
1694
|
+
stderr += d.toString();
|
|
1695
|
+
});
|
|
1696
|
+
|
|
1697
|
+
proc.on('error', (error) => {
|
|
1698
|
+
console.log('❌ [prompt_agent_stream] Process error:', error.message);
|
|
1699
|
+
clearInterval(pollInterval);
|
|
1700
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1701
|
+
if (!responseSent) {
|
|
1702
|
+
responseSent = true;
|
|
1703
|
+
sendEvent('error', { error: error.message });
|
|
1704
|
+
res.end();
|
|
1705
|
+
}
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1708
|
+
proc.on('close', (code, signal) => {
|
|
1709
|
+
console.log('🔵 [prompt_agent_stream] Process closed with code:', code);
|
|
1710
|
+
clearInterval(pollInterval);
|
|
1711
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1712
|
+
|
|
1713
|
+
let afterCommit = '';
|
|
1714
|
+
try {
|
|
1715
|
+
afterCommit = execSync('git rev-parse HEAD', { cwd: ROOT }).toString().trim();
|
|
1716
|
+
} catch (e) {
|
|
1717
|
+
// Ignore
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
if (!responseSent) {
|
|
1721
|
+
responseSent = true;
|
|
1722
|
+
sendEvent('complete', {
|
|
1723
|
+
code,
|
|
1724
|
+
stdout,
|
|
1725
|
+
stderr,
|
|
1726
|
+
changedFiles: Array.from(changedFiles),
|
|
1727
|
+
beforeCommit,
|
|
1728
|
+
afterCommit
|
|
1729
|
+
});
|
|
1730
|
+
res.end();
|
|
1731
|
+
}
|
|
1732
|
+
});
|
|
1733
|
+
|
|
1734
|
+
// Handle client disconnect
|
|
1735
|
+
req.on('close', () => {
|
|
1736
|
+
console.log('🔵 [prompt_agent_stream] Client disconnected');
|
|
1737
|
+
clearInterval(pollInterval);
|
|
1738
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1739
|
+
if (proc && !proc.killed) {
|
|
1740
|
+
proc.kill('SIGTERM');
|
|
1741
|
+
}
|
|
1742
|
+
});
|
|
1743
|
+
});
|
|
1744
|
+
|
|
1454
1745
|
// Revert job endpoint to reset to a previous commit
|
|
1455
1746
|
app.post('/revert_job', (req, res) => {
|
|
1456
1747
|
console.log('🔵 [revert_job] Endpoint hit');
|