devbonzai 2.2.306 → 2.2.308
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 +10 -2
- package/package.json +4 -4
- package/templates/handlers/index.js +1 -1
- package/templates/handlers/terminal.js +83 -74
- package/templates/receiver.js +13 -29
- package/templates/terminal-test.html +104 -0
package/cli.js
CHANGED
|
@@ -71,7 +71,15 @@ async function main() {
|
|
|
71
71
|
const utilsSrc = path.join(__dirname, 'templates', 'utils');
|
|
72
72
|
const utilsDest = path.join(bonzaiDir, 'utils');
|
|
73
73
|
copyDirectory(utilsSrc, utilsDest);
|
|
74
|
-
|
|
74
|
+
|
|
75
|
+
// Copy terminal-test.html
|
|
76
|
+
console.log('📝 Copying terminal-test.html...');
|
|
77
|
+
const terminalTestSrc = path.join(__dirname, 'templates', 'terminal-test.html');
|
|
78
|
+
const terminalTestDest = path.join(bonzaiDir, 'terminal-test.html');
|
|
79
|
+
if (fs.existsSync(terminalTestSrc)) {
|
|
80
|
+
fs.copyFileSync(terminalTestSrc, terminalTestDest);
|
|
81
|
+
}
|
|
82
|
+
|
|
75
83
|
// Write .ignore file in bonzai directory
|
|
76
84
|
const ignoreTargetPath = path.join(bonzaiDir, '.ignore');
|
|
77
85
|
|
|
@@ -109,8 +117,8 @@ async function main() {
|
|
|
109
117
|
packageJson.dependencies.express = "^4.18.2";
|
|
110
118
|
packageJson.dependencies.cors = "^2.8.5";
|
|
111
119
|
packageJson.dependencies["@babel/parser"] = "^7.23.0";
|
|
112
|
-
packageJson.dependencies["node-pty"] = "^1.0.0";
|
|
113
120
|
packageJson.dependencies.ws = "^8.16.0";
|
|
121
|
+
packageJson.dependencies["node-pty"] = "^1.1.0";
|
|
114
122
|
|
|
115
123
|
// Add script to run receiver
|
|
116
124
|
if (!packageJson.scripts) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devbonzai",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.308",
|
|
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": {
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
"author": "",
|
|
23
23
|
"license": "ISC",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"express": "^4.18.2",
|
|
26
|
-
"cors": "^2.8.5",
|
|
27
25
|
"@babel/parser": "^7.23.0",
|
|
28
|
-
"
|
|
26
|
+
"cors": "^2.8.5",
|
|
27
|
+
"express": "^4.18.2",
|
|
28
|
+
"node-pty": "^1.1.0",
|
|
29
29
|
"ws": "^8.16.0"
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -9,7 +9,7 @@ function indexHandler(req, res) {
|
|
|
9
9
|
'POST /open-cursor': 'Open Cursor (body: {path, line?})',
|
|
10
10
|
'POST /shutdown': 'Gracefully shutdown the server',
|
|
11
11
|
'POST /scan_code_quality': 'Scan code quality (body: {projectPath})',
|
|
12
|
-
'WS /terminal
|
|
12
|
+
'WS /terminal': 'Interactive terminal via WebSocket'
|
|
13
13
|
},
|
|
14
14
|
example: 'Try: /list or /read?path=README.md'
|
|
15
15
|
});
|
|
@@ -1,97 +1,106 @@
|
|
|
1
1
|
const pty = require('node-pty');
|
|
2
|
-
const fs = require('fs');
|
|
3
2
|
|
|
4
3
|
// Store active terminal sessions
|
|
5
4
|
const terminals = new Map();
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
// Get default shell based on platform
|
|
7
|
+
function getDefaultShell() {
|
|
8
|
+
if (process.platform === 'win32') {
|
|
9
|
+
return process.env.COMSPEC || 'powershell.exe';
|
|
10
|
+
}
|
|
11
|
+
return process.env.SHELL || '/bin/bash';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Create a new terminal session
|
|
15
|
+
function createTerminal(sessionId, cols = 80, rows = 24) {
|
|
16
|
+
const shell = getDefaultShell();
|
|
17
|
+
|
|
17
18
|
const ptyProcess = pty.spawn(shell, [], {
|
|
18
19
|
name: 'xterm-256color',
|
|
19
|
-
cols
|
|
20
|
-
rows
|
|
21
|
-
cwd: cwd,
|
|
22
|
-
env: {
|
|
23
|
-
...process.env,
|
|
24
|
-
TERM: 'xterm-256color',
|
|
25
|
-
COLORTERM: 'truecolor',
|
|
26
|
-
},
|
|
20
|
+
cols,
|
|
21
|
+
rows,
|
|
22
|
+
cwd: process.env.HOME || process.cwd(),
|
|
23
|
+
env: { ...process.env, TERM: 'xterm-256color' }
|
|
27
24
|
});
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
terminals.set(sessionId, {
|
|
27
|
+
pty: ptyProcess,
|
|
28
|
+
buffer: ''
|
|
29
|
+
});
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
return ptyProcess;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// HTTP handler for terminal info
|
|
35
|
+
function terminalHandler(req, res) {
|
|
36
|
+
res.json({
|
|
37
|
+
message: 'Terminal WebSocket API',
|
|
38
|
+
usage: {
|
|
39
|
+
websocket: 'ws://localhost:3001/terminal',
|
|
40
|
+
events: {
|
|
41
|
+
'input': 'Send terminal input (data: string)',
|
|
42
|
+
'resize': 'Resize terminal (cols: number, rows: number)',
|
|
43
|
+
'output': 'Receive terminal output'
|
|
37
44
|
}
|
|
38
|
-
} catch (err) {
|
|
39
|
-
console.error('Error sending to WebSocket:', err);
|
|
40
45
|
}
|
|
41
46
|
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// WebSocket handler for terminal sessions
|
|
50
|
+
function setupTerminalWebSocket(wss) {
|
|
51
|
+
wss.on('connection', (ws) => {
|
|
52
|
+
const sessionId = Date.now().toString();
|
|
53
|
+
let ptyProcess;
|
|
42
54
|
|
|
43
|
-
// Handle incoming messages from WebSocket
|
|
44
|
-
ws.on('message', (msg) => {
|
|
45
55
|
try {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// Check if it's a resize command
|
|
49
|
-
if (message.startsWith('\x1b[8;')) {
|
|
50
|
-
// Parse resize: ESC[8;rows;colst
|
|
51
|
-
const match = message.match(/\x1b\[8;(\d+);(\d+)t/);
|
|
52
|
-
if (match) {
|
|
53
|
-
const rows = parseInt(match[1], 10);
|
|
54
|
-
const cols = parseInt(match[2], 10);
|
|
55
|
-
ptyProcess.resize(cols, rows);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Check for JSON resize command
|
|
61
|
-
if (message.startsWith('{')) {
|
|
62
|
-
try {
|
|
63
|
-
const json = JSON.parse(message);
|
|
64
|
-
if (json.type === 'resize' && json.cols && json.rows) {
|
|
65
|
-
ptyProcess.resize(json.cols, json.rows);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
} catch (e) {
|
|
69
|
-
// Not JSON, treat as regular input
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Regular terminal input
|
|
74
|
-
ptyProcess.write(message);
|
|
56
|
+
ptyProcess = createTerminal(sessionId);
|
|
57
|
+
console.log(`Terminal session ${sessionId} started`);
|
|
75
58
|
} catch (err) {
|
|
76
|
-
console.error('
|
|
59
|
+
console.error('Failed to create terminal:', err.message);
|
|
60
|
+
ws.send(JSON.stringify({ type: 'error', message: 'Failed to create terminal: ' + err.message }));
|
|
61
|
+
ws.close();
|
|
62
|
+
return;
|
|
77
63
|
}
|
|
78
|
-
});
|
|
79
64
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
65
|
+
// Send terminal output to WebSocket client
|
|
66
|
+
ptyProcess.onData((data) => {
|
|
67
|
+
try {
|
|
68
|
+
ws.send(JSON.stringify({ type: 'output', data }));
|
|
69
|
+
} catch (e) {
|
|
70
|
+
// Client disconnected
|
|
71
|
+
}
|
|
72
|
+
});
|
|
86
73
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
console.log(`🖥️ Terminal process exited (code: ${exitCode}, signal: ${signal})`);
|
|
90
|
-
terminals.delete(terminalId);
|
|
91
|
-
if (ws.readyState === 1) {
|
|
74
|
+
ptyProcess.onExit(({ exitCode }) => {
|
|
75
|
+
ws.send(JSON.stringify({ type: 'exit', exitCode }));
|
|
92
76
|
ws.close();
|
|
93
|
-
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Handle incoming messages from client
|
|
80
|
+
ws.on('message', (message) => {
|
|
81
|
+
try {
|
|
82
|
+
const msg = JSON.parse(message);
|
|
83
|
+
|
|
84
|
+
switch (msg.type) {
|
|
85
|
+
case 'input':
|
|
86
|
+
ptyProcess.write(msg.data);
|
|
87
|
+
break;
|
|
88
|
+
case 'resize':
|
|
89
|
+
ptyProcess.resize(msg.cols, msg.rows);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.error('Terminal message error:', e);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Cleanup on disconnect
|
|
98
|
+
ws.on('close', () => {
|
|
99
|
+
console.log(`Terminal session ${sessionId} closed`);
|
|
100
|
+
ptyProcess.kill();
|
|
101
|
+
terminals.delete(sessionId);
|
|
102
|
+
});
|
|
94
103
|
});
|
|
95
104
|
}
|
|
96
105
|
|
|
97
|
-
module.exports = {
|
|
106
|
+
module.exports = { terminalHandler, setupTerminalWebSocket };
|
package/templates/receiver.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const express = require('
|
|
4
|
-
const cors = require('
|
|
5
|
-
const
|
|
6
|
-
const {
|
|
3
|
+
const express = require('express');
|
|
4
|
+
const cors = require('cors');
|
|
5
|
+
const http = require('http');
|
|
6
|
+
const { WebSocketServer } = require('ws');
|
|
7
7
|
|
|
8
8
|
// Import handlers
|
|
9
9
|
const indexHandler = require('./handlers/index');
|
|
@@ -13,9 +13,14 @@ const deleteHandler = require('./handlers/delete');
|
|
|
13
13
|
const openCursorHandler = require('./handlers/open-cursor');
|
|
14
14
|
const shutdownHandler = require('./handlers/shutdown');
|
|
15
15
|
const scanCodeQualityHandler = require('./handlers/scan_code_quality');
|
|
16
|
-
const {
|
|
16
|
+
const { terminalHandler, setupTerminalWebSocket } = require('./handlers/terminal');
|
|
17
17
|
|
|
18
18
|
const app = express();
|
|
19
|
+
const server = http.createServer(app);
|
|
20
|
+
|
|
21
|
+
// WebSocket server for terminal
|
|
22
|
+
const wss = new WebSocketServer({ server, path: '/terminal' });
|
|
23
|
+
setupTerminalWebSocket(wss);
|
|
19
24
|
|
|
20
25
|
app.use(cors());
|
|
21
26
|
app.use(express.json());
|
|
@@ -28,32 +33,11 @@ app.post('/delete', deleteHandler);
|
|
|
28
33
|
app.post('/open-cursor', openCursorHandler);
|
|
29
34
|
app.post('/shutdown', shutdownHandler);
|
|
30
35
|
app.post('/scan_code_quality', scanCodeQualityHandler);
|
|
36
|
+
app.get('/terminal', terminalHandler);
|
|
31
37
|
|
|
32
38
|
const port = 3001;
|
|
33
|
-
|
|
39
|
+
server.listen(port, () => {
|
|
34
40
|
console.log('📂 File server running on http://localhost:' + port);
|
|
41
|
+
console.log('🖥️ Terminal WebSocket available at ws://localhost:' + port + '/terminal');
|
|
35
42
|
});
|
|
36
43
|
|
|
37
|
-
// WebSocket server for terminal
|
|
38
|
-
const wss = new WebSocket.Server({
|
|
39
|
-
server,
|
|
40
|
-
path: '/terminal'
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
wss.on('connection', (ws, req) => {
|
|
44
|
-
// Extract working directory from query string if provided
|
|
45
|
-
let workingDirectory = ROOT;
|
|
46
|
-
|
|
47
|
-
if (req.url) {
|
|
48
|
-
const urlMatch = req.url.match(/[?&]cwd=([^&]+)/);
|
|
49
|
-
if (urlMatch) {
|
|
50
|
-
try {
|
|
51
|
-
workingDirectory = decodeURIComponent(urlMatch[1]);
|
|
52
|
-
} catch (e) {
|
|
53
|
-
console.warn('Invalid cwd parameter, using default:', e.message);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
handleTerminalConnection(ws, workingDirectory);
|
|
59
|
-
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Terminal Test</title>
|
|
5
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" />
|
|
6
|
+
<style>
|
|
7
|
+
body {
|
|
8
|
+
margin: 0;
|
|
9
|
+
padding: 20px;
|
|
10
|
+
background: #1e1e1e;
|
|
11
|
+
font-family: sans-serif;
|
|
12
|
+
}
|
|
13
|
+
h1 {
|
|
14
|
+
color: #fff;
|
|
15
|
+
margin-bottom: 10px;
|
|
16
|
+
}
|
|
17
|
+
#status {
|
|
18
|
+
color: #888;
|
|
19
|
+
margin-bottom: 10px;
|
|
20
|
+
}
|
|
21
|
+
#terminal {
|
|
22
|
+
height: 400px;
|
|
23
|
+
}
|
|
24
|
+
</style>
|
|
25
|
+
</head>
|
|
26
|
+
<body>
|
|
27
|
+
<h1>Terminal Test</h1>
|
|
28
|
+
<div id="status">Connecting...</div>
|
|
29
|
+
<div id="terminal"></div>
|
|
30
|
+
|
|
31
|
+
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script>
|
|
32
|
+
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
|
|
33
|
+
<script>
|
|
34
|
+
const term = new Terminal({
|
|
35
|
+
cursorBlink: true,
|
|
36
|
+
fontSize: 14,
|
|
37
|
+
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
|
38
|
+
theme: {
|
|
39
|
+
background: '#1e1e1e',
|
|
40
|
+
foreground: '#d4d4d4'
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const fitAddon = new FitAddon.FitAddon();
|
|
45
|
+
term.loadAddon(fitAddon);
|
|
46
|
+
|
|
47
|
+
const terminalEl = document.getElementById('terminal');
|
|
48
|
+
const statusEl = document.getElementById('status');
|
|
49
|
+
|
|
50
|
+
term.open(terminalEl);
|
|
51
|
+
fitAddon.fit();
|
|
52
|
+
|
|
53
|
+
// Connect to WebSocket
|
|
54
|
+
const ws = new WebSocket('ws://localhost:3001/terminal');
|
|
55
|
+
|
|
56
|
+
ws.onopen = () => {
|
|
57
|
+
statusEl.textContent = 'Connected';
|
|
58
|
+
statusEl.style.color = '#4ec9b0';
|
|
59
|
+
|
|
60
|
+
// Send initial size
|
|
61
|
+
ws.send(JSON.stringify({
|
|
62
|
+
type: 'resize',
|
|
63
|
+
cols: term.cols,
|
|
64
|
+
rows: term.rows
|
|
65
|
+
}));
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
ws.onmessage = (event) => {
|
|
69
|
+
const msg = JSON.parse(event.data);
|
|
70
|
+
if (msg.type === 'output') {
|
|
71
|
+
term.write(msg.data);
|
|
72
|
+
} else if (msg.type === 'exit') {
|
|
73
|
+
statusEl.textContent = 'Session ended (exit code: ' + msg.exitCode + ')';
|
|
74
|
+
statusEl.style.color = '#f48771';
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
ws.onclose = () => {
|
|
79
|
+
statusEl.textContent = 'Disconnected';
|
|
80
|
+
statusEl.style.color = '#f48771';
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
ws.onerror = (err) => {
|
|
84
|
+
statusEl.textContent = 'Connection error';
|
|
85
|
+
statusEl.style.color = '#f48771';
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Send input to server
|
|
89
|
+
term.onData((data) => {
|
|
90
|
+
ws.send(JSON.stringify({ type: 'input', data }));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Handle resize
|
|
94
|
+
window.addEventListener('resize', () => {
|
|
95
|
+
fitAddon.fit();
|
|
96
|
+
ws.send(JSON.stringify({
|
|
97
|
+
type: 'resize',
|
|
98
|
+
cols: term.cols,
|
|
99
|
+
rows: term.rows
|
|
100
|
+
}));
|
|
101
|
+
});
|
|
102
|
+
</script>
|
|
103
|
+
</body>
|
|
104
|
+
</html>
|