bonzai-burn 1.0.39 → 1.0.40
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/dist/bconfig.js +224 -0
- package/dist/graph-templates/config.js +18 -0
- package/dist/graph-templates/ignore.txt +58 -0
- package/dist/graph-templates/loops/backend/delete.js +20 -0
- package/dist/graph-templates/loops/backend/git.js +72 -0
- package/dist/graph-templates/loops/backend/open-cursor.js +108 -0
- package/dist/graph-templates/loops/backend/shutdown.js +16 -0
- package/dist/graph-templates/loops/backend/terminal.js +140 -0
- package/dist/graph-templates/loops/backend/write.js +18 -0
- package/dist/graph-templates/loops/visualization/list.js +16 -0
- package/dist/graph-templates/loops/visualization/read.js +120 -0
- package/dist/graph-templates/loops/visualization/scan_code_quality.js +144 -0
- package/dist/graph-templates/receiver.js +66 -0
- package/dist/graph-templates/utils/fileList.js +96 -0
- package/dist/graph-templates/utils/ignore.js +53 -0
- package/dist/graph-templates/utils/parsers.js +720 -0
- package/dist/index.js +36 -12
- package/package.json +5 -2
package/dist/bconfig.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { spawn, exec } from 'child_process';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { ENABLED_LOOPS } from './loops.config.js';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
// Template folder in the package
|
|
12
|
+
const TEMPLATE_DIR = path.join(__dirname, 'graph-templates');
|
|
13
|
+
|
|
14
|
+
// Helper function to recursively copy directory
|
|
15
|
+
function copyDirectory(src, dest) {
|
|
16
|
+
if (!fs.existsSync(dest)) {
|
|
17
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
20
|
+
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
const srcPath = path.join(src, entry.name);
|
|
23
|
+
const destPath = path.join(dest, entry.name);
|
|
24
|
+
|
|
25
|
+
if (entry.isDirectory()) {
|
|
26
|
+
copyDirectory(srcPath, destPath);
|
|
27
|
+
} else {
|
|
28
|
+
fs.copyFileSync(srcPath, destPath);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function main() {
|
|
34
|
+
const currentDir = process.cwd();
|
|
35
|
+
const bonzaiDir = path.join(currentDir, 'bonzai');
|
|
36
|
+
const receiverPath = path.join(bonzaiDir, 'receiver.js');
|
|
37
|
+
|
|
38
|
+
console.log('Setting up local file server...');
|
|
39
|
+
|
|
40
|
+
// Create bonzai directory
|
|
41
|
+
if (!fs.existsSync(bonzaiDir)) {
|
|
42
|
+
console.log('Creating bonzai directory...');
|
|
43
|
+
fs.mkdirSync(bonzaiDir);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Write receiver.js
|
|
47
|
+
console.log('Writing receiver.js...');
|
|
48
|
+
const receiverContent = fs.readFileSync(path.join(TEMPLATE_DIR, 'receiver.js'), 'utf8');
|
|
49
|
+
fs.writeFileSync(receiverPath, receiverContent);
|
|
50
|
+
fs.chmodSync(receiverPath, '755');
|
|
51
|
+
|
|
52
|
+
// Write config.js
|
|
53
|
+
console.log('Writing config.js...');
|
|
54
|
+
const configContent = fs.readFileSync(path.join(TEMPLATE_DIR, 'config.js'), 'utf8');
|
|
55
|
+
fs.writeFileSync(path.join(bonzaiDir, 'config.js'), configContent);
|
|
56
|
+
|
|
57
|
+
// Copy handlers from enabled loops
|
|
58
|
+
console.log('Copying handlers...');
|
|
59
|
+
const handlersDest = path.join(bonzaiDir, 'handlers');
|
|
60
|
+
if (!fs.existsSync(handlersDest)) {
|
|
61
|
+
fs.mkdirSync(handlersDest, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Copy visualization loop handlers
|
|
65
|
+
if (ENABLED_LOOPS.includes('visualization')) {
|
|
66
|
+
const vizSrc = path.join(TEMPLATE_DIR, 'loops', 'visualization');
|
|
67
|
+
if (fs.existsSync(vizSrc)) {
|
|
68
|
+
for (const file of fs.readdirSync(vizSrc)) {
|
|
69
|
+
fs.copyFileSync(path.join(vizSrc, file), path.join(handlersDest, file));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Copy backend loop handlers
|
|
75
|
+
if (ENABLED_LOOPS.includes('backend')) {
|
|
76
|
+
const backendSrc = path.join(TEMPLATE_DIR, 'loops', 'backend');
|
|
77
|
+
if (fs.existsSync(backendSrc)) {
|
|
78
|
+
for (const file of fs.readdirSync(backendSrc)) {
|
|
79
|
+
fs.copyFileSync(path.join(backendSrc, file), path.join(handlersDest, file));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Copy utils directory
|
|
85
|
+
console.log('Copying utils...');
|
|
86
|
+
const utilsSrc = path.join(TEMPLATE_DIR, 'utils');
|
|
87
|
+
const utilsDest = path.join(bonzaiDir, 'utils');
|
|
88
|
+
copyDirectory(utilsSrc, utilsDest);
|
|
89
|
+
|
|
90
|
+
// Write .ignore file in bonzai directory
|
|
91
|
+
const ignoreTargetPath = path.join(bonzaiDir, '.ignore');
|
|
92
|
+
if (!fs.existsSync(ignoreTargetPath)) {
|
|
93
|
+
console.log('Writing .ignore file...');
|
|
94
|
+
const ignoreContent = fs.readFileSync(path.join(TEMPLATE_DIR, 'ignore.txt'), 'utf8');
|
|
95
|
+
fs.writeFileSync(ignoreTargetPath, ignoreContent);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Setup package.json in bonzai directory
|
|
99
|
+
const packageJsonPath = path.join(bonzaiDir, 'package.json');
|
|
100
|
+
let packageJson = {};
|
|
101
|
+
|
|
102
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
103
|
+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
104
|
+
} else {
|
|
105
|
+
packageJson = {
|
|
106
|
+
name: "bonzai-server",
|
|
107
|
+
version: "1.0.0",
|
|
108
|
+
description: "Dependencies for bonzai graph server",
|
|
109
|
+
main: "receiver.js",
|
|
110
|
+
scripts: {
|
|
111
|
+
test: "echo \"Error: no test specified\" && exit 1"
|
|
112
|
+
},
|
|
113
|
+
author: "",
|
|
114
|
+
license: "ISC"
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Add dependencies
|
|
119
|
+
if (!packageJson.dependencies) {
|
|
120
|
+
packageJson.dependencies = {};
|
|
121
|
+
}
|
|
122
|
+
packageJson.dependencies.express = "^4.18.2";
|
|
123
|
+
packageJson.dependencies.cors = "^2.8.5";
|
|
124
|
+
packageJson.dependencies["@babel/parser"] = "^7.23.0";
|
|
125
|
+
packageJson.dependencies.ws = "^8.14.2";
|
|
126
|
+
packageJson.dependencies["node-pty"] = "^1.0.0";
|
|
127
|
+
|
|
128
|
+
// Add script to run receiver
|
|
129
|
+
if (!packageJson.scripts) {
|
|
130
|
+
packageJson.scripts = {};
|
|
131
|
+
}
|
|
132
|
+
packageJson.scripts["file-server"] = "node receiver.js";
|
|
133
|
+
|
|
134
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
135
|
+
|
|
136
|
+
console.log('Installing dependencies...');
|
|
137
|
+
|
|
138
|
+
// Install dependencies in bonzai directory
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
const npm = spawn('npm', ['install'], {
|
|
141
|
+
stdio: 'inherit',
|
|
142
|
+
cwd: bonzaiDir
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
npm.on('close', (code) => {
|
|
146
|
+
if (code === 0) {
|
|
147
|
+
// Fix node-pty spawn-helper permissions (npm doesn't preserve execute bits)
|
|
148
|
+
const nodePtyPrebuilds = path.join(bonzaiDir, 'node_modules', 'node-pty', 'prebuilds');
|
|
149
|
+
if (fs.existsSync(nodePtyPrebuilds)) {
|
|
150
|
+
const archDirs = ['darwin-arm64', 'darwin-x64', 'linux-x64', 'linux-arm64'];
|
|
151
|
+
for (const arch of archDirs) {
|
|
152
|
+
const spawnHelperPath = path.join(nodePtyPrebuilds, arch, 'spawn-helper');
|
|
153
|
+
if (fs.existsSync(spawnHelperPath)) {
|
|
154
|
+
try {
|
|
155
|
+
fs.chmodSync(spawnHelperPath, '755');
|
|
156
|
+
console.log(`Fixed node-pty spawn-helper permissions (${arch})`);
|
|
157
|
+
} catch (e) {
|
|
158
|
+
console.warn(`Warning: Could not fix spawn-helper permissions for ${arch}:`, e.message);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log('\nListener endpoints successfully deployed');
|
|
165
|
+
console.log('All code stays on your machine\n');
|
|
166
|
+
console.log('Relay server running on localhost:3001');
|
|
167
|
+
console.log('Terminal WebSocket available at ws://localhost:3001/terminal');
|
|
168
|
+
console.log('Diagram available at https://bonzai.dev/\n');
|
|
169
|
+
|
|
170
|
+
// Start the server automatically
|
|
171
|
+
const server = spawn('node', ['receiver.js'], {
|
|
172
|
+
stdio: 'inherit',
|
|
173
|
+
cwd: bonzaiDir,
|
|
174
|
+
env: {
|
|
175
|
+
...process.env,
|
|
176
|
+
BONZAI_REPO_DIR: currentDir
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Open browser automatically
|
|
181
|
+
exec('open https://bonzai.dev/');
|
|
182
|
+
|
|
183
|
+
// Handle server process
|
|
184
|
+
server.on('close', (serverCode) => {
|
|
185
|
+
console.log(`\nServer stopped with code ${serverCode}`);
|
|
186
|
+
process.exit(serverCode);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
server.on('error', (err) => {
|
|
190
|
+
console.error('Error starting server:', err.message);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Handle cleanup on exit
|
|
195
|
+
process.on('SIGINT', () => {
|
|
196
|
+
console.log('\nShutting down server...');
|
|
197
|
+
server.kill('SIGINT');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
process.on('SIGTERM', () => {
|
|
201
|
+
console.log('\nShutting down server...');
|
|
202
|
+
server.kill('SIGTERM');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
resolve();
|
|
206
|
+
} else {
|
|
207
|
+
reject(new Error('npm install failed with code ' + code));
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
npm.on('error', (err) => {
|
|
212
|
+
reject(err);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Export for use via index.js flags
|
|
218
|
+
export { main };
|
|
219
|
+
|
|
220
|
+
// Run directly if called as standalone command
|
|
221
|
+
const isDirectRun = process.argv[1]?.endsWith('bconfig.js');
|
|
222
|
+
if (isDirectRun) {
|
|
223
|
+
main().catch(console.error);
|
|
224
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
// Root directory (one level up from templates/)
|
|
4
|
+
const ROOT = path.join(__dirname, '..');
|
|
5
|
+
|
|
6
|
+
// Initialize babelParser (optional dependency)
|
|
7
|
+
let babelParser = null;
|
|
8
|
+
try {
|
|
9
|
+
babelParser = require('./node_modules/@babel/parser');
|
|
10
|
+
} catch (e) {
|
|
11
|
+
// Babel parser not available, will fall back gracefully
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
ROOT,
|
|
16
|
+
babelParser
|
|
17
|
+
};
|
|
18
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
static/
|
|
44
|
+
out/
|
|
45
|
+
.next/
|
|
46
|
+
*.min.js
|
|
47
|
+
*.min.css
|
|
48
|
+
*.bundle.js
|
|
49
|
+
*.chunk.js
|
|
50
|
+
|
|
51
|
+
# Temporary files
|
|
52
|
+
*.tmp
|
|
53
|
+
*.temp
|
|
54
|
+
|
|
55
|
+
# Project-specific files
|
|
56
|
+
receiver.js
|
|
57
|
+
bonzai/
|
|
58
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { ROOT } = require('../config');
|
|
4
|
+
|
|
5
|
+
function deleteHandler(req, res) {
|
|
6
|
+
try {
|
|
7
|
+
const targetPath = path.join(ROOT, req.body.path || '');
|
|
8
|
+
if (!targetPath.startsWith(ROOT)) {
|
|
9
|
+
return res.status(400).send('Invalid path');
|
|
10
|
+
}
|
|
11
|
+
// Delete file or directory recursively
|
|
12
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
13
|
+
res.json({ status: 'ok' });
|
|
14
|
+
} catch (e) {
|
|
15
|
+
res.status(500).send(e.message);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = deleteHandler;
|
|
20
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
|
|
3
|
+
// GET /git/burns - List all bonzai-burn branches
|
|
4
|
+
function listBurns(req, res) {
|
|
5
|
+
try {
|
|
6
|
+
// Get all bonzai-burn branches
|
|
7
|
+
let branchOutput;
|
|
8
|
+
try {
|
|
9
|
+
branchOutput = execSync('git branch --list "bonzai-burn*"', { encoding: 'utf-8' });
|
|
10
|
+
} catch (e) {
|
|
11
|
+
// No matching branches
|
|
12
|
+
branchOutput = '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const branches = branchOutput
|
|
16
|
+
.split('\n')
|
|
17
|
+
.filter(b => b.trim())
|
|
18
|
+
.map(b => b.trim().replace('* ', ''));
|
|
19
|
+
|
|
20
|
+
if (branches.length === 0) {
|
|
21
|
+
return res.json({ burns: [] });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Get timestamp for each branch
|
|
25
|
+
const burns = branches.map(branch => {
|
|
26
|
+
const timestamp = execSync(`git log -1 --format=%at ${branch}`, { encoding: 'utf-8' }).trim();
|
|
27
|
+
return {
|
|
28
|
+
name: branch,
|
|
29
|
+
timestamp: parseInt(timestamp) * 1000
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Sort chronologically (oldest first)
|
|
34
|
+
burns.sort((a, b) => a.timestamp - b.timestamp);
|
|
35
|
+
|
|
36
|
+
// Get current branch
|
|
37
|
+
const currentBranch = execSync('git branch --show-current', { encoding: 'utf-8' }).trim();
|
|
38
|
+
|
|
39
|
+
res.json({
|
|
40
|
+
burns: burns.map(b => ({
|
|
41
|
+
...b,
|
|
42
|
+
current: b.name === currentBranch
|
|
43
|
+
}))
|
|
44
|
+
});
|
|
45
|
+
} catch (error) {
|
|
46
|
+
res.status(500).json({ error: error.message });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// POST /git/checkout - Checkout a branch
|
|
51
|
+
function checkoutBranch(req, res) {
|
|
52
|
+
try {
|
|
53
|
+
const { branchName } = req.body;
|
|
54
|
+
|
|
55
|
+
if (!branchName) {
|
|
56
|
+
return res.status(400).json({ error: 'branchName is required' });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Validate branch name to prevent command injection
|
|
60
|
+
if (!/^[a-zA-Z0-9_\-\.\/]+$/.test(branchName)) {
|
|
61
|
+
return res.status(400).json({ error: 'Invalid branch name' });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
execSync(`git checkout ${branchName}`, { encoding: 'utf-8' });
|
|
65
|
+
|
|
66
|
+
res.json({ checkedOut: branchName });
|
|
67
|
+
} catch (error) {
|
|
68
|
+
res.status(500).json({ error: error.message });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { listBurns, checkoutBranch };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { exec } = require('child_process');
|
|
3
|
+
const { ROOT } = require('../config');
|
|
4
|
+
|
|
5
|
+
function openCursorHandler(req, res) {
|
|
6
|
+
try {
|
|
7
|
+
const requestedPath = req.body.path || '';
|
|
8
|
+
|
|
9
|
+
// Resolve path relative to ROOT (similar to other endpoints)
|
|
10
|
+
// If path is absolute and within ROOT, use it directly
|
|
11
|
+
// Otherwise, resolve it relative to ROOT
|
|
12
|
+
let filePath;
|
|
13
|
+
if (path.isAbsolute(requestedPath)) {
|
|
14
|
+
// If absolute path, check if it's within ROOT
|
|
15
|
+
if (requestedPath.startsWith(ROOT)) {
|
|
16
|
+
filePath = requestedPath;
|
|
17
|
+
} else {
|
|
18
|
+
// Path might contain incorrect segments (like "codemaps")
|
|
19
|
+
// Try to find ROOT in the path and extract the relative part
|
|
20
|
+
const rootIndex = requestedPath.indexOf(ROOT);
|
|
21
|
+
if (rootIndex !== -1) {
|
|
22
|
+
// Extract the part after ROOT and remove leading slashes
|
|
23
|
+
let relativePart = requestedPath.substring(rootIndex + ROOT.length);
|
|
24
|
+
while (relativePart.startsWith('/')) {
|
|
25
|
+
relativePart = relativePart.substring(1);
|
|
26
|
+
}
|
|
27
|
+
filePath = path.join(ROOT, relativePart);
|
|
28
|
+
} else {
|
|
29
|
+
return res.status(400).json({ error: 'Invalid path: path must be within project root' });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
// Relative path - resolve relative to ROOT
|
|
34
|
+
// Remove root directory name prefix if present (from /list endpoint format)
|
|
35
|
+
const rootName = path.basename(ROOT);
|
|
36
|
+
let relativePath = requestedPath;
|
|
37
|
+
if (relativePath.startsWith(rootName + '/')) {
|
|
38
|
+
relativePath = relativePath.substring(rootName.length + 1);
|
|
39
|
+
}
|
|
40
|
+
filePath = path.join(ROOT, relativePath);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Validate the resolved path is within ROOT
|
|
44
|
+
if (!filePath.startsWith(ROOT)) {
|
|
45
|
+
return res.status(400).json({ error: 'Invalid path' });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { line } = req.body;
|
|
49
|
+
|
|
50
|
+
// Always use cursor CLI command first (it handles line numbers correctly)
|
|
51
|
+
const cursorCommands = [
|
|
52
|
+
'cursor',
|
|
53
|
+
'/Applications/Cursor.app/Contents/Resources/app/bin/cursor',
|
|
54
|
+
'/usr/local/bin/cursor',
|
|
55
|
+
'code'
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const tryCommand = (commandIndex = 0) => {
|
|
59
|
+
if (commandIndex >= cursorCommands.length) {
|
|
60
|
+
return res.status(500).json({
|
|
61
|
+
error: 'Cursor not found. Please install Cursor CLI or check Cursor installation.'
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Use proper Cursor CLI syntax for line numbers
|
|
66
|
+
const command = line
|
|
67
|
+
? `${cursorCommands[commandIndex]} --goto "${filePath}:${line}"`
|
|
68
|
+
: `${cursorCommands[commandIndex]} "${filePath}"`;
|
|
69
|
+
|
|
70
|
+
exec(command, (error, stdout, stderr) => {
|
|
71
|
+
if (error && error.code === 127) {
|
|
72
|
+
// Command not found, try next one
|
|
73
|
+
tryCommand(commandIndex + 1);
|
|
74
|
+
} else if (error) {
|
|
75
|
+
console.error('Error opening Cursor:', error);
|
|
76
|
+
return res.status(500).json({ error: error.message });
|
|
77
|
+
} else {
|
|
78
|
+
// File opened successfully, now bring Cursor to front
|
|
79
|
+
const isMac = process.platform === 'darwin';
|
|
80
|
+
if (isMac) {
|
|
81
|
+
// Use AppleScript to bring Cursor to the front
|
|
82
|
+
exec('osascript -e "tell application \\"Cursor\\" to activate"', (activateError) => {
|
|
83
|
+
if (activateError) {
|
|
84
|
+
console.log('Could not activate Cursor, but file opened successfully');
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Additional command to ensure it's really in front
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
exec('osascript -e "tell application \\"System Events\\" to set frontmost of process \\"Cursor\\" to true"', () => {
|
|
91
|
+
// Don't worry if this fails
|
|
92
|
+
});
|
|
93
|
+
}, 500);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
res.json({ success: true, message: 'Cursor opened and focused successfully' });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
tryCommand();
|
|
102
|
+
} catch (e) {
|
|
103
|
+
res.status(500).json({ error: e.message });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = openCursorHandler;
|
|
108
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function shutdownHandler(req, res) {
|
|
2
|
+
console.log('🛑 Shutdown endpoint called - terminating server...');
|
|
3
|
+
|
|
4
|
+
res.json({
|
|
5
|
+
success: true,
|
|
6
|
+
message: 'Server shutting down...'
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// Close the server gracefully
|
|
10
|
+
setTimeout(() => {
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}, 100); // Small delay to ensure response is sent
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = shutdownHandler;
|
|
16
|
+
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
let pty;
|
|
2
|
+
try {
|
|
3
|
+
pty = require('node-pty');
|
|
4
|
+
} catch (err) {
|
|
5
|
+
pty = null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Store active terminal sessions
|
|
9
|
+
const terminals = new Map();
|
|
10
|
+
|
|
11
|
+
// Get default shell based on platform
|
|
12
|
+
function getDefaultShell() {
|
|
13
|
+
if (process.platform === 'win32') {
|
|
14
|
+
return process.env.COMSPEC || 'powershell.exe';
|
|
15
|
+
}
|
|
16
|
+
const shell = process.env.SHELL || '/bin/bash';
|
|
17
|
+
if (!shell.startsWith('/')) {
|
|
18
|
+
return '/bin/bash';
|
|
19
|
+
}
|
|
20
|
+
return shell;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Create a new terminal session
|
|
24
|
+
function createTerminal(sessionId, cols = 80, rows = 24) {
|
|
25
|
+
if (!pty) {
|
|
26
|
+
throw new Error('node-pty is not available. Native binaries may not have installed correctly.');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const shell = getDefaultShell();
|
|
30
|
+
const cwd = process.env.BONZAI_REPO_DIR || process.cwd();
|
|
31
|
+
|
|
32
|
+
let ptyProcess;
|
|
33
|
+
try {
|
|
34
|
+
ptyProcess = pty.spawn(shell, [], {
|
|
35
|
+
name: 'xterm-256color',
|
|
36
|
+
cols,
|
|
37
|
+
rows,
|
|
38
|
+
cwd,
|
|
39
|
+
env: {
|
|
40
|
+
...process.env,
|
|
41
|
+
TERM: 'xterm-256color',
|
|
42
|
+
PATH: process.env.PATH || '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin'
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
} catch (spawnErr) {
|
|
46
|
+
try {
|
|
47
|
+
ptyProcess = pty.spawn('/bin/bash', [], {
|
|
48
|
+
name: 'xterm-256color',
|
|
49
|
+
cols,
|
|
50
|
+
rows,
|
|
51
|
+
cwd,
|
|
52
|
+
env: {
|
|
53
|
+
...process.env,
|
|
54
|
+
TERM: 'xterm-256color',
|
|
55
|
+
PATH: process.env.PATH || '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin'
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
} catch (bashErr) {
|
|
59
|
+
throw spawnErr;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
terminals.set(sessionId, {
|
|
64
|
+
pty: ptyProcess,
|
|
65
|
+
buffer: ''
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return ptyProcess;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// HTTP handler for terminal info
|
|
72
|
+
function terminalHandler(req, res) {
|
|
73
|
+
res.json({
|
|
74
|
+
message: 'Terminal WebSocket API',
|
|
75
|
+
usage: {
|
|
76
|
+
websocket: 'ws://localhost:3001/terminal',
|
|
77
|
+
events: {
|
|
78
|
+
'input': 'Send terminal input (data: string)',
|
|
79
|
+
'resize': 'Resize terminal (cols: number, rows: number)',
|
|
80
|
+
'output': 'Receive terminal output'
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// WebSocket handler for terminal sessions
|
|
87
|
+
function setupTerminalWebSocket(wss) {
|
|
88
|
+
wss.on('connection', (ws) => {
|
|
89
|
+
const sessionId = Date.now().toString();
|
|
90
|
+
let ptyProcess;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
ptyProcess = createTerminal(sessionId);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
ws.send(JSON.stringify({ type: 'error', message: 'Failed to create terminal: ' + err.message }));
|
|
96
|
+
ws.close();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Send terminal output to WebSocket client
|
|
101
|
+
ptyProcess.onData((data) => {
|
|
102
|
+
try {
|
|
103
|
+
ws.send(JSON.stringify({ type: 'output', data }));
|
|
104
|
+
} catch (e) {
|
|
105
|
+
// Client disconnected
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
ptyProcess.onExit(({ exitCode }) => {
|
|
110
|
+
ws.send(JSON.stringify({ type: 'exit', exitCode }));
|
|
111
|
+
ws.close();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Handle incoming messages from client
|
|
115
|
+
ws.on('message', (message) => {
|
|
116
|
+
try {
|
|
117
|
+
const msg = JSON.parse(message);
|
|
118
|
+
|
|
119
|
+
switch (msg.type) {
|
|
120
|
+
case 'input':
|
|
121
|
+
ptyProcess.write(msg.data);
|
|
122
|
+
break;
|
|
123
|
+
case 'resize':
|
|
124
|
+
ptyProcess.resize(msg.cols, msg.rows);
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
} catch (e) {
|
|
128
|
+
// Parse error
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Cleanup on disconnect
|
|
133
|
+
ws.on('close', () => {
|
|
134
|
+
ptyProcess.kill();
|
|
135
|
+
terminals.delete(sessionId);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = { terminalHandler, setupTerminalWebSocket };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { ROOT } = require('../config');
|
|
4
|
+
|
|
5
|
+
function writeHandler(req, res) {
|
|
6
|
+
try {
|
|
7
|
+
const filePath = path.join(ROOT, req.body.path || '');
|
|
8
|
+
if (!filePath.startsWith(ROOT)) {
|
|
9
|
+
return res.status(400).send('Invalid path');
|
|
10
|
+
}
|
|
11
|
+
fs.writeFileSync(filePath, req.body.content, 'utf8');
|
|
12
|
+
res.json({ status: 'ok' });
|
|
13
|
+
} catch (e) {
|
|
14
|
+
res.status(500).send(e.message);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = writeHandler;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { ROOT } = require('../config');
|
|
3
|
+
const { listAllFiles } = require('../utils/fileList');
|
|
4
|
+
|
|
5
|
+
function listHandler(req, res) {
|
|
6
|
+
try {
|
|
7
|
+
const rootName = path.basename(ROOT);
|
|
8
|
+
const files = listAllFiles(ROOT, rootName);
|
|
9
|
+
res.json({ files });
|
|
10
|
+
} catch (e) {
|
|
11
|
+
res.status(500).send(e.message);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = listHandler;
|
|
16
|
+
|