termites 1.0.0
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/.claude/settings.local.json +13 -0
- package/README.md +63 -0
- package/bin/termites.js +43 -0
- package/client.js +146 -0
- package/index.js +472 -0
- package/package.json +27 -0
- package/server.js +625 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(npm view shellgate)",
|
|
5
|
+
"Bash(npm view termbridge)",
|
|
6
|
+
"Bash(npm view shellcast)",
|
|
7
|
+
"Bash(npm view ptyshare)",
|
|
8
|
+
"Bash(npm view turtleshell)",
|
|
9
|
+
"Bash(npm view snailshell)",
|
|
10
|
+
"Bash(npm view crabshell)"
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Termites
|
|
2
|
+
|
|
3
|
+
A web-based terminal with server-client architecture for remote shell access.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────┐ WebSocket ┌─────────────────┐
|
|
9
|
+
│ Client 1 │◄──────────────────►│ │
|
|
10
|
+
│ (node-pty) │ │ │
|
|
11
|
+
└─────────────────┘ │ Server │
|
|
12
|
+
│ (Web UI) │◄──► Browser
|
|
13
|
+
┌─────────────────┐ WebSocket │ │
|
|
14
|
+
│ Client 2 │◄──────────────────►│ │
|
|
15
|
+
│ (node-pty) │ └─────────────────┘
|
|
16
|
+
└─────────────────┘
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g termites
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### Start Server
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
termites server
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The server starts on port 3456 by default. Access the web UI at `http://localhost:3456`.
|
|
34
|
+
|
|
35
|
+
To use a different port:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
PORT=8080 termites server
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Connect Client
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Connect to localhost:3456
|
|
45
|
+
termites client
|
|
46
|
+
|
|
47
|
+
# Connect to a remote server
|
|
48
|
+
termites client 192.168.1.100:3456
|
|
49
|
+
termites client ws://example.com:3456
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
- Multi-client support - manage multiple shell sessions from one web interface
|
|
55
|
+
- Mobile-friendly UI with collapsible drawer menu
|
|
56
|
+
- Multiple color themes (Solarized, Monokai, Dracula, Nord, GitHub Dark)
|
|
57
|
+
- Customizable font family and size
|
|
58
|
+
- Auto-reconnect for clients
|
|
59
|
+
- Output buffering for terminal history
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
MIT
|
package/bin/termites.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
const command = args[0];
|
|
6
|
+
|
|
7
|
+
function showHelp() {
|
|
8
|
+
console.log(`
|
|
9
|
+
Termites - Web terminal with server-client architecture
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
termites server Start the server
|
|
13
|
+
termites client [url] Connect to a server
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
PORT=<port> Set server port (default: 3456)
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
termites server Start server on port 3456
|
|
20
|
+
PORT=8080 termites server Start server on port 8080
|
|
21
|
+
termites client Connect to ws://localhost:3456
|
|
22
|
+
termites client 192.168.1.100:3456
|
|
23
|
+
`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!command || command === '-h' || command === '--help' || command === 'help') {
|
|
27
|
+
showHelp();
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const rootDir = path.join(__dirname, '..');
|
|
32
|
+
|
|
33
|
+
if (command === 'server') {
|
|
34
|
+
require(path.join(rootDir, 'server.js'));
|
|
35
|
+
} else if (command === 'client') {
|
|
36
|
+
const serverUrl = args[1] || 'ws://localhost:3456';
|
|
37
|
+
process.argv = [process.argv[0], path.join(rootDir, 'client.js'), serverUrl];
|
|
38
|
+
require(path.join(rootDir, 'client.js'));
|
|
39
|
+
} else {
|
|
40
|
+
console.error(`Unknown command: ${command}`);
|
|
41
|
+
showHelp();
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
package/client.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const WebSocket = require('ws');
|
|
4
|
+
|
|
5
|
+
const SHELL = process.env.SHELL || '/bin/bash';
|
|
6
|
+
const SERVER_URL = process.argv[2] || 'ws://localhost:3456/client';
|
|
7
|
+
|
|
8
|
+
function getSystemInfo() {
|
|
9
|
+
const username = os.userInfo().username;
|
|
10
|
+
const hostname = os.hostname();
|
|
11
|
+
const cwd = process.cwd().replace(os.homedir(), '~');
|
|
12
|
+
const platform = os.platform();
|
|
13
|
+
return { username, hostname, cwd, platform };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class WebShellClient {
|
|
17
|
+
constructor(serverUrl) {
|
|
18
|
+
this.serverUrl = serverUrl.endsWith('/client') ? serverUrl : serverUrl + '/client';
|
|
19
|
+
this.ws = null;
|
|
20
|
+
this.pty = null;
|
|
21
|
+
this.reconnectDelay = 1000;
|
|
22
|
+
this.maxReconnectDelay = 30000;
|
|
23
|
+
this.systemInfo = getSystemInfo();
|
|
24
|
+
|
|
25
|
+
this.connect();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
connect() {
|
|
29
|
+
console.log(`连接服务器: ${this.serverUrl}`);
|
|
30
|
+
|
|
31
|
+
this.ws = new WebSocket(this.serverUrl);
|
|
32
|
+
|
|
33
|
+
this.ws.on('open', () => {
|
|
34
|
+
console.log('已连接到服务器');
|
|
35
|
+
this.reconnectDelay = 1000;
|
|
36
|
+
|
|
37
|
+
// Register with server
|
|
38
|
+
this.ws.send(JSON.stringify({
|
|
39
|
+
type: 'register',
|
|
40
|
+
info: this.systemInfo
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
// Start shell
|
|
44
|
+
this.startShell();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
this.ws.on('message', (message) => {
|
|
48
|
+
try {
|
|
49
|
+
const data = JSON.parse(message);
|
|
50
|
+
this.handleServerMessage(data);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.error('解析服务器消息失败:', e);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
this.ws.on('close', () => {
|
|
57
|
+
console.log('与服务器断开连接');
|
|
58
|
+
this.stopShell();
|
|
59
|
+
this.scheduleReconnect();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
this.ws.on('error', (err) => {
|
|
63
|
+
console.error('WebSocket 错误:', err.message);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
scheduleReconnect() {
|
|
68
|
+
console.log(`${this.reconnectDelay / 1000} 秒后重连...`);
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);
|
|
71
|
+
this.connect();
|
|
72
|
+
}, this.reconnectDelay);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
handleServerMessage(data) {
|
|
76
|
+
switch (data.type) {
|
|
77
|
+
case 'input':
|
|
78
|
+
if (this.pty) {
|
|
79
|
+
this.pty.write(data.text);
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
|
|
83
|
+
case 'resize':
|
|
84
|
+
if (this.pty) {
|
|
85
|
+
this.pty.resize(data.cols, data.rows);
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
startShell() {
|
|
92
|
+
if (this.pty) return;
|
|
93
|
+
|
|
94
|
+
const pty = require('node-pty');
|
|
95
|
+
this.pty = pty.spawn(SHELL, ['-l'], {
|
|
96
|
+
name: 'xterm-256color',
|
|
97
|
+
cols: 120,
|
|
98
|
+
rows: 40,
|
|
99
|
+
cwd: process.cwd(),
|
|
100
|
+
env: process.env
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
console.log(`Shell 已启动 (PID: ${this.pty.pid})`);
|
|
104
|
+
|
|
105
|
+
this.pty.onData((data) => {
|
|
106
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
107
|
+
this.ws.send(JSON.stringify({
|
|
108
|
+
type: 'output',
|
|
109
|
+
data
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
this.pty.onExit(() => {
|
|
115
|
+
console.log('Shell 已退出');
|
|
116
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
117
|
+
this.ws.send(JSON.stringify({ type: 'exit' }));
|
|
118
|
+
}
|
|
119
|
+
this.pty = null;
|
|
120
|
+
// Restart shell
|
|
121
|
+
setTimeout(() => this.startShell(), 1000);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
stopShell() {
|
|
126
|
+
if (this.pty) {
|
|
127
|
+
this.pty.kill();
|
|
128
|
+
this.pty = null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Parse server URL from command line
|
|
134
|
+
let serverUrl = SERVER_URL;
|
|
135
|
+
if (!serverUrl.startsWith('ws://') && !serverUrl.startsWith('wss://')) {
|
|
136
|
+
serverUrl = 'ws://' + serverUrl;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log('WebShell Client');
|
|
140
|
+
console.log(`系统信息: ${getSystemInfo().username}@${getSystemInfo().hostname}`);
|
|
141
|
+
console.log(`Shell: ${SHELL}`);
|
|
142
|
+
|
|
143
|
+
new WebShellClient(serverUrl);
|
|
144
|
+
|
|
145
|
+
process.on('SIGINT', () => process.exit(0));
|
|
146
|
+
process.on('SIGTERM', () => process.exit(0));
|