bonzai-tree 1.0.113

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/README.md ADDED
@@ -0,0 +1,4 @@
1
+ # bonzai-tree
2
+
3
+ Code analysis CLI for Claude Code.
4
+
@@ -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:6767');
167
+ console.log('Terminal WebSocket available at ws://localhost:6767/terminal');
168
+ console.log('App available at http://localhost:6767\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 http://localhost:6767/visualize?ref=btools');
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 - use BONZAI_REPO_DIR env var (set by bconfig.js when server starts)
4
+ const ROOT = process.env.BONZAI_REPO_DIR || 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,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,142 @@
1
+ const { ROOT } = require('../config');
2
+
3
+ let pty;
4
+ try {
5
+ pty = require('node-pty');
6
+ } catch (err) {
7
+ pty = null;
8
+ }
9
+
10
+ // Store active terminal sessions
11
+ const terminals = new Map();
12
+
13
+ // Get default shell based on platform
14
+ function getDefaultShell() {
15
+ if (process.platform === 'win32') {
16
+ return process.env.COMSPEC || 'powershell.exe';
17
+ }
18
+ const shell = process.env.SHELL || '/bin/bash';
19
+ if (!shell.startsWith('/')) {
20
+ return '/bin/bash';
21
+ }
22
+ return shell;
23
+ }
24
+
25
+ // Create a new terminal session
26
+ function createTerminal(sessionId, cols = 80, rows = 24) {
27
+ if (!pty) {
28
+ throw new Error('node-pty is not available. Native binaries may not have installed correctly.');
29
+ }
30
+
31
+ const shell = getDefaultShell();
32
+ const cwd = ROOT;
33
+
34
+ let ptyProcess;
35
+ try {
36
+ ptyProcess = pty.spawn(shell, [], {
37
+ name: 'xterm-256color',
38
+ cols,
39
+ rows,
40
+ cwd,
41
+ env: {
42
+ ...process.env,
43
+ TERM: 'xterm-256color',
44
+ PATH: process.env.PATH || '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin'
45
+ }
46
+ });
47
+ } catch (spawnErr) {
48
+ try {
49
+ ptyProcess = pty.spawn('/bin/bash', [], {
50
+ name: 'xterm-256color',
51
+ cols,
52
+ rows,
53
+ cwd,
54
+ env: {
55
+ ...process.env,
56
+ TERM: 'xterm-256color',
57
+ PATH: process.env.PATH || '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin'
58
+ }
59
+ });
60
+ } catch (bashErr) {
61
+ throw spawnErr;
62
+ }
63
+ }
64
+
65
+ terminals.set(sessionId, {
66
+ pty: ptyProcess,
67
+ buffer: ''
68
+ });
69
+
70
+ return ptyProcess;
71
+ }
72
+
73
+ // HTTP handler for terminal info
74
+ function terminalHandler(req, res) {
75
+ res.json({
76
+ message: 'Terminal WebSocket API',
77
+ usage: {
78
+ websocket: 'ws://localhost:6767/terminal',
79
+ events: {
80
+ 'input': 'Send terminal input (data: string)',
81
+ 'resize': 'Resize terminal (cols: number, rows: number)',
82
+ 'output': 'Receive terminal output'
83
+ }
84
+ }
85
+ });
86
+ }
87
+
88
+ // WebSocket handler for terminal sessions
89
+ function setupTerminalWebSocket(wss) {
90
+ wss.on('connection', (ws) => {
91
+ const sessionId = Date.now().toString();
92
+ let ptyProcess;
93
+
94
+ try {
95
+ ptyProcess = createTerminal(sessionId);
96
+ } catch (err) {
97
+ ws.send(JSON.stringify({ type: 'error', message: 'Failed to create terminal: ' + err.message }));
98
+ ws.close();
99
+ return;
100
+ }
101
+
102
+ // Send terminal output to WebSocket client
103
+ ptyProcess.onData((data) => {
104
+ try {
105
+ ws.send(JSON.stringify({ type: 'output', data }));
106
+ } catch (e) {
107
+ // Client disconnected
108
+ }
109
+ });
110
+
111
+ ptyProcess.onExit(({ exitCode }) => {
112
+ ws.send(JSON.stringify({ type: 'exit', exitCode }));
113
+ ws.close();
114
+ });
115
+
116
+ // Handle incoming messages from client
117
+ ws.on('message', (message) => {
118
+ try {
119
+ const msg = JSON.parse(message);
120
+
121
+ switch (msg.type) {
122
+ case 'input':
123
+ ptyProcess.write(msg.data);
124
+ break;
125
+ case 'resize':
126
+ ptyProcess.resize(msg.cols, msg.rows);
127
+ break;
128
+ }
129
+ } catch (e) {
130
+ // Parse error
131
+ }
132
+ });
133
+
134
+ // Cleanup on disconnect
135
+ ws.on('close', () => {
136
+ ptyProcess.kill();
137
+ terminals.delete(sessionId);
138
+ });
139
+ });
140
+ }
141
+
142
+ 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,18 @@
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 relativeFiles = listAllFiles(ROOT);
8
+ const repoName = path.basename(ROOT);
9
+ // Prefix paths with repo name: repoName/src/file.js
10
+ const files = relativeFiles.map(f => path.join(repoName, f));
11
+ res.json({ files, root: ROOT });
12
+ } catch (e) {
13
+ res.status(500).send(e.message);
14
+ }
15
+ }
16
+
17
+ module.exports = listHandler;
18
+
@@ -0,0 +1,120 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { ROOT } = require('../config');
4
+ const { extractPythonFunctions, extractJavaScriptFunctions, extractVueFunctions } = require('../utils/parsers');
5
+
6
+ function readHandler(req, res) {
7
+ try {
8
+ const requestedPath = req.query.path || '';
9
+ const filePath = path.join(ROOT, requestedPath);
10
+
11
+ if (!filePath.startsWith(ROOT)) {
12
+ return res.status(400).send('Invalid path');
13
+ }
14
+
15
+ // Helper function to find and return content from parse result
16
+ const findAndReturn = (parseResult, name, type) => {
17
+ if (type === 'function') {
18
+ const target = parseResult.functions.find(f => f.name === name);
19
+ if (target) return target.content;
20
+ } else if (type === 'method') {
21
+ // Method name format: ClassName.methodName
22
+ for (const cls of parseResult.classes) {
23
+ const method = cls.methods.find(m => m.name === name);
24
+ if (method) return method.content;
25
+ }
26
+ } else if (type === 'class') {
27
+ const target = parseResult.classes.find(c => c.name === name);
28
+ if (target) return target.content;
29
+ }
30
+ return null;
31
+ };
32
+
33
+ // Check if this is a virtual file request (.function, .method, or .class)
34
+ if (requestedPath.endsWith('.function') || requestedPath.endsWith('.method') || requestedPath.endsWith('.class')) {
35
+ // Traverse up the path to find the actual source file
36
+ let currentPath = filePath;
37
+ let sourceFilePath = null;
38
+ let parser = null;
39
+
40
+ // Keep going up until we find a source file (.py, .js, .jsx, .ts, .tsx, .vue)
41
+ while (currentPath !== ROOT && currentPath !== path.dirname(currentPath)) {
42
+ const stat = fs.existsSync(currentPath) ? fs.statSync(currentPath) : null;
43
+
44
+ // Check if current path is a file with a supported extension
45
+ if (stat && stat.isFile()) {
46
+ if (currentPath.endsWith('.py')) {
47
+ parser = extractPythonFunctions;
48
+ sourceFilePath = currentPath;
49
+ break;
50
+ } else if (currentPath.endsWith('.js') || currentPath.endsWith('.jsx') ||
51
+ currentPath.endsWith('.ts') || currentPath.endsWith('.tsx')) {
52
+ parser = extractJavaScriptFunctions;
53
+ sourceFilePath = currentPath;
54
+ break;
55
+ } else if (currentPath.endsWith('.vue')) {
56
+ parser = extractVueFunctions;
57
+ sourceFilePath = currentPath;
58
+ break;
59
+ }
60
+ }
61
+
62
+ // Move up one level
63
+ const parentPath = path.dirname(currentPath);
64
+ if (parentPath === currentPath) break; // Reached root
65
+ currentPath = parentPath;
66
+ }
67
+
68
+ if (!sourceFilePath || !parser) {
69
+ return res.status(404).send('Source file not found for virtual file');
70
+ }
71
+
72
+ // Extract the requested item name from the requested path
73
+ let itemName = '';
74
+ let itemType = '';
75
+
76
+ if (requestedPath.endsWith('.function')) {
77
+ itemName = path.basename(requestedPath, '.function');
78
+ itemType = 'function';
79
+ } else if (requestedPath.endsWith('.method')) {
80
+ itemName = path.basename(requestedPath, '.method');
81
+ itemType = 'method';
82
+ } else if (requestedPath.endsWith('.class')) {
83
+ itemName = path.basename(requestedPath, '.class');
84
+ itemType = 'class';
85
+ }
86
+
87
+ // Check if the source file exists
88
+ try {
89
+ if (!fs.existsSync(sourceFilePath)) {
90
+ return res.status(404).send('Source file not found');
91
+ }
92
+
93
+ // Parse the file
94
+ const parseResult = parser(sourceFilePath);
95
+
96
+ // Find and return the content
97
+ const content = findAndReturn(parseResult, itemName, itemType);
98
+
99
+ if (!content) {
100
+ return res.status(404).send(`${itemType} '${itemName}' not found in file`);
101
+ }
102
+
103
+ return res.json({ content });
104
+ } catch (e) {
105
+ const errorType = requestedPath.endsWith('.function') ? 'function' :
106
+ requestedPath.endsWith('.method') ? 'method' : 'class';
107
+ return res.status(500).send('Error reading ' + errorType + ': ' + e.message);
108
+ }
109
+ }
110
+
111
+ // Regular file read
112
+ const content = fs.readFileSync(filePath, 'utf8');
113
+ res.json({ content });
114
+ } catch (e) {
115
+ res.status(500).send(e.message);
116
+ }
117
+ }
118
+
119
+ module.exports = readHandler;
120
+