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.
Files changed (2) hide show
  1. package/cli.js +291 -0
  2. 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');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devbonzai",
3
- "version": "2.0.5",
3
+ "version": "2.0.7",
4
4
  "description": "Quickly set up a local file server in any repository for browser-based file access",
5
5
  "main": "cli.js",
6
6
  "bin": {