termites 1.0.30 → 1.0.32
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 +25 -42
- package/package.json +1 -1
- package/server.js +85 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Termites
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Local multi-terminal manager with web interface.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install -g termites
|
|
@@ -9,58 +9,41 @@ npm install -g termites
|
|
|
9
9
|
## Architecture
|
|
10
10
|
|
|
11
11
|
```
|
|
12
|
-
|
|
13
|
-
│
|
|
14
|
-
│
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
│
|
|
19
|
-
│
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
npm install -g termites
|
|
12
|
+
┌─────────────────────────────────────────────────────┐
|
|
13
|
+
│ Server │
|
|
14
|
+
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
|
15
|
+
│ │ Terminal│ │ Terminal│ │ Terminal│ ... │
|
|
16
|
+
│ │ (PTY) │ │ (PTY) │ │ (PTY) │ │
|
|
17
|
+
│ └─────────┘ └─────────┘ └─────────┘ │
|
|
18
|
+
│ │ │
|
|
19
|
+
│ WebSocket + HTTP │
|
|
20
|
+
└─────────────────────┼───────────────────────────────┘
|
|
21
|
+
│
|
|
22
|
+
▼
|
|
23
|
+
Browser
|
|
27
24
|
```
|
|
28
25
|
|
|
29
26
|
## Usage
|
|
30
27
|
|
|
31
|
-
### Start Server
|
|
32
|
-
|
|
33
28
|
```bash
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
# Start server (default port 6789)
|
|
30
|
+
termites
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
To use a different port:
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
PORT=8080 termites server
|
|
32
|
+
# Or with custom port
|
|
33
|
+
PORT=8080 termites
|
|
43
34
|
```
|
|
44
35
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
# Connect to localhost:6789
|
|
49
|
-
termites client
|
|
50
|
-
|
|
51
|
-
# Connect to a remote server
|
|
52
|
-
termites client 192.168.1.100:6789
|
|
53
|
-
termites client ws://example.com:6789
|
|
54
|
-
```
|
|
36
|
+
Open browser at `http://localhost:6789`
|
|
55
37
|
|
|
56
38
|
## Features
|
|
57
39
|
|
|
58
|
-
- Multi-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
40
|
+
- **Multi-terminal** - Create and manage multiple terminals from one web interface
|
|
41
|
+
- **Auto hostname detection** - Panel updates automatically when SSH to different machines
|
|
42
|
+
- **Mobile-friendly** - Touch support, virtual keyboard buttons, long-press to select
|
|
43
|
+
- **History scrollback** - iTerm-like history overlay (scroll up or tap Hist button)
|
|
44
|
+
- **Themes** - Solarized Light/Dark, Monokai, Dracula, Nord, GitHub Dark
|
|
45
|
+
- **Customizable** - Font family, font size, toolbar visibility
|
|
46
|
+
- **Password protection** - Set password on first visit
|
|
64
47
|
|
|
65
48
|
## License
|
|
66
49
|
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -132,6 +132,53 @@ class TermitesServer {
|
|
|
132
132
|
if (terminal.outputBuffer.length > 1000) {
|
|
133
133
|
terminal.outputBuffer = terminal.outputBuffer.slice(-500);
|
|
134
134
|
}
|
|
135
|
+
|
|
136
|
+
// Parse terminal output to detect user@host changes (e.g., after SSH)
|
|
137
|
+
// Method 1: OSC 0 sequences (window title)
|
|
138
|
+
const oscMatch = data.match(/\x1b\]0;([^\x07\x1b]+)[\x07\x1b]/);
|
|
139
|
+
if (oscMatch) {
|
|
140
|
+
const title = oscMatch[1];
|
|
141
|
+
const match = title.match(/^([^@]+)@([^:]+)(?::(.*))?$/);
|
|
142
|
+
if (match) {
|
|
143
|
+
this.updateTerminalInfo(terminalId, terminal, match[1], match[2], match[3]);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Method 2: Parse shell prompt patterns (works without any config)
|
|
148
|
+
// Look for the LAST (most recent) prompt pattern in output
|
|
149
|
+
const lines = data.split(/\r?\n/);
|
|
150
|
+
let lastMatch = null;
|
|
151
|
+
for (const line of lines) {
|
|
152
|
+
// Strip ANSI escape codes for matching
|
|
153
|
+
const cleanLine = line.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '').replace(/\x1b\][^\x07]*\x07/g, '');
|
|
154
|
+
|
|
155
|
+
// Pattern 1: user@host format (e.g., "user@host:path$", "[user@host path]$")
|
|
156
|
+
const userHostMatch = cleanLine.match(/[\[\s]?([a-zA-Z0-9_-]+)@([a-zA-Z0-9_.-]+)[\s:\]]/);
|
|
157
|
+
if (userHostMatch) {
|
|
158
|
+
const [, username, hostname] = userHostMatch;
|
|
159
|
+
if (hostname && !hostname.includes('/') && hostname.length < 64) {
|
|
160
|
+
lastMatch = { username, hostname };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Pattern 2: hostname:path format (e.g., "seis10:/gds/zhfu[22] > ")
|
|
165
|
+
const hostPathMatch = cleanLine.match(/^([a-zA-Z0-9_-]+):([\/~][^\s\[]*)/);
|
|
166
|
+
if (hostPathMatch) {
|
|
167
|
+
const [, hostname, cwd] = hostPathMatch;
|
|
168
|
+
if (hostname && hostname.length < 64) {
|
|
169
|
+
lastMatch = { hostname, cwd, keepUsername: true };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Use only the last (most recent) match
|
|
174
|
+
if (lastMatch) {
|
|
175
|
+
if (lastMatch.keepUsername) {
|
|
176
|
+
this.updateTerminalInfo(terminalId, terminal, null, lastMatch.hostname, lastMatch.cwd);
|
|
177
|
+
} else {
|
|
178
|
+
this.updateTerminalInfo(terminalId, terminal, lastMatch.username, lastMatch.hostname);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
135
182
|
this.broadcastToBrowsers({
|
|
136
183
|
type: 'output',
|
|
137
184
|
clientId: terminalId,
|
|
@@ -168,6 +215,32 @@ class TermitesServer {
|
|
|
168
215
|
}
|
|
169
216
|
}
|
|
170
217
|
|
|
218
|
+
// Update terminal info (user@host) when detected from output
|
|
219
|
+
updateTerminalInfo(terminalId, terminal, username, hostname, cwd) {
|
|
220
|
+
let infoChanged = false;
|
|
221
|
+
if (username && terminal.info.username !== username) {
|
|
222
|
+
terminal.info.username = username;
|
|
223
|
+
infoChanged = true;
|
|
224
|
+
}
|
|
225
|
+
if (hostname && terminal.info.hostname !== hostname) {
|
|
226
|
+
terminal.info.hostname = hostname;
|
|
227
|
+
infoChanged = true;
|
|
228
|
+
}
|
|
229
|
+
if (cwd !== undefined && terminal.info.cwd !== cwd) {
|
|
230
|
+
terminal.info.cwd = cwd || '~';
|
|
231
|
+
infoChanged = true;
|
|
232
|
+
}
|
|
233
|
+
if (infoChanged) {
|
|
234
|
+
console.log(`Terminal ${terminalId} info updated: ${username}@${hostname}`);
|
|
235
|
+
this.broadcastToBrowsers({
|
|
236
|
+
type: 'client-info-updated',
|
|
237
|
+
clientId: terminalId,
|
|
238
|
+
info: terminal.info
|
|
239
|
+
});
|
|
240
|
+
this.broadcastTerminalList();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
171
244
|
// Handle browser connections
|
|
172
245
|
handleBrowserConnection(ws) {
|
|
173
246
|
console.log('New browser connected');
|
|
@@ -1567,6 +1640,18 @@ class TermitesServer {
|
|
|
1567
1640
|
if (clients.length > 0) selectClient(clients[0].id);
|
|
1568
1641
|
}
|
|
1569
1642
|
break;
|
|
1643
|
+
case 'client-info-updated':
|
|
1644
|
+
const clientToUpdate = clients.find(c => c.id === d.clientId);
|
|
1645
|
+
if (clientToUpdate && d.info) {
|
|
1646
|
+
Object.assign(clientToUpdate, d.info);
|
|
1647
|
+
updateClientList();
|
|
1648
|
+
if (selectedClientId === d.clientId) {
|
|
1649
|
+
document.getElementById('header-title').innerHTML =
|
|
1650
|
+
'<span class="user">' + d.info.username + '</span>' +
|
|
1651
|
+
'<span class="sep">@</span><span class="host">' + d.info.hostname + '</span>';
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
break;
|
|
1570
1655
|
case 'output':
|
|
1571
1656
|
if (d.clientId === selectedClientId) {
|
|
1572
1657
|
addToHistory(d.data);
|