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 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
- // Helper to copy directory recursively
8
- function copyDirSync(src, dest) {
9
- if (!fs.existsSync(dest)) {
10
- fs.mkdirSync(dest, { recursive: true });
11
- }
12
-
13
- const entries = fs.readdirSync(src, { withFileTypes: true });
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 templatesDir = path.join(__dirname, 'templates');
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
- // Copy all template files to bonzai directory
41
- console.log('📝 Copying server files...');
42
- copyDirSync(templatesDir, bonzaiDir);
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 receiver.js executable
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devbonzai",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
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": {
@@ -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
-
@@ -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
- });
@@ -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
-