lit-shell.js 1.1.0 → 1.2.1
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/CHANGELOG.md +36 -0
- package/README.md +438 -9
- package/dist/client/browser-bundle.js +61 -1
- package/dist/client/browser-bundle.js.map +3 -3
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/terminal-client.d.ts +19 -0
- package/dist/client/terminal-client.d.ts.map +1 -1
- package/dist/client/terminal-client.js +61 -0
- package/dist/client/terminal-client.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/session-manager.d.ts +6 -0
- package/dist/server/session-manager.d.ts.map +1 -1
- package/dist/server/session-manager.js +12 -2
- package/dist/server/session-manager.js.map +1 -1
- package/dist/server/terminal-server.d.ts.map +1 -1
- package/dist/server/terminal-server.js +23 -4
- package/dist/server/terminal-server.js.map +1 -1
- package/dist/shared/types.d.ts +6 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/ui/browser-bundle.js +1625 -96
- package/dist/ui/browser-bundle.js.map +4 -4
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/lit-shell-terminal.d.ts +225 -6
- package/dist/ui/lit-shell-terminal.d.ts.map +1 -1
- package/dist/ui/lit-shell-terminal.js +1605 -60
- package/dist/ui/lit-shell-terminal.js.map +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/styles.js +22 -0
- package/dist/ui/styles.js.map +1 -1
- package/dist/version.d.ts +6 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +6 -0
- package/dist/version.js.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,42 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.2.1] - 2025-02-01
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Comprehensive README update with full documentation
|
|
13
|
+
- Added Tabbed Terminals section with Tab API and use cases
|
|
14
|
+
- Added Built-in Connection Panel section
|
|
15
|
+
- Added Session Multiplexing diagram
|
|
16
|
+
- Added Docker Compose quick start instructions
|
|
17
|
+
- Added Examples section with local development guide
|
|
18
|
+
- Added Client-Side CDN loading instructions
|
|
19
|
+
|
|
20
|
+
## [1.2.0] - 2025-02-01
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **Mobile Support**: Auto-detect mobile devices and show touch keyboard
|
|
25
|
+
- Termux-style layout: ESC, arrows, HOME/END, PGUP/PGDN, TAB
|
|
26
|
+
- Sticky CTRL and ALT modifiers
|
|
27
|
+
- Collapsible extra row with common control sequences (^C, ^D, ^Z, ^L, ^A, ^E, ^R)
|
|
28
|
+
- Hide/show toggle for entire keyboard
|
|
29
|
+
- Responsive viewport detection with media query listener
|
|
30
|
+
- **Reconnect Dialog**: After WebSocket reconnection, shows dialog to rejoin previous session
|
|
31
|
+
- Saves session ID on disconnect
|
|
32
|
+
- Checks if session still exists after reconnect
|
|
33
|
+
- Option to rejoin with history or start fresh
|
|
34
|
+
- **Session Persistence Options**: Connection panel now includes
|
|
35
|
+
- Configurable orphan timeout (1 min to 1 week)
|
|
36
|
+
- Tmux integration checkbox for permanent persistence
|
|
37
|
+
- **Updated Font Stack**: Terminal now uses Cascadia Mono as primary font for better cross-platform consistency
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- Improved README documentation with all new features
|
|
42
|
+
- Updated feature list to include Docker, multiplexing, mobile, and persistence
|
|
43
|
+
|
|
8
44
|
## [1.1.0] - 2025-01-09
|
|
9
45
|
|
|
10
46
|
### Added
|
package/README.md
CHANGED
|
@@ -9,17 +9,65 @@ A plug-and-play terminal solution for web applications. Includes a server compon
|
|
|
9
9
|
- **Server**: WebSocket server with node-pty for real shell sessions
|
|
10
10
|
- **Client**: Lightweight WebSocket client with auto-reconnection
|
|
11
11
|
- **UI**: `<lit-shell-terminal>` Lit web component with xterm.js
|
|
12
|
+
- **Tabbed Terminals**: Multiple terminal tabs in a single component
|
|
13
|
+
- **Docker Exec**: Connect to Docker containers via `docker exec`
|
|
14
|
+
- **Docker Attach**: Connect to a container's main process (PID 1)
|
|
15
|
+
- **Session Multiplexing**: Multiple clients can share the same terminal session
|
|
16
|
+
- **Session Persistence**: Sessions survive client disconnects with configurable timeout
|
|
17
|
+
- **History Replay**: New clients receive recent terminal output when joining
|
|
18
|
+
- **Mobile Support**: Touch keyboard with Termux-style layout for mobile devices
|
|
12
19
|
- **Themes**: Built-in dark/light/auto theme support
|
|
13
|
-
- **Security**: Configurable shell and
|
|
20
|
+
- **Security**: Configurable shell, path, and container allowlists
|
|
14
21
|
- **Framework Agnostic**: Works with React, Vue, Angular, Svelte, or vanilla JS
|
|
15
22
|
|
|
16
23
|
## Installation
|
|
17
24
|
|
|
18
25
|
```bash
|
|
19
|
-
npm install lit-shell.js
|
|
26
|
+
npm install lit-shell.js
|
|
20
27
|
```
|
|
21
28
|
|
|
22
|
-
|
|
29
|
+
### Server-Side Requirements (node-pty)
|
|
30
|
+
|
|
31
|
+
The server component requires `node-pty` for spawning terminal processes. Install it as a dev dependency:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install node-pty --save-dev
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Important:** `node-pty` requires native compilation. If you encounter installation issues:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Linux - install build essentials
|
|
41
|
+
sudo apt-get install build-essential python3
|
|
42
|
+
|
|
43
|
+
# macOS - install Xcode command line tools
|
|
44
|
+
xcode-select --install
|
|
45
|
+
|
|
46
|
+
# If npm install fails, try:
|
|
47
|
+
npm install node-pty --save-dev --legacy-peer-deps
|
|
48
|
+
|
|
49
|
+
# Or rebuild native modules:
|
|
50
|
+
npm rebuild node-pty
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
See [node-pty docs](https://github.com/microsoft/node-pty) for platform-specific requirements.
|
|
54
|
+
|
|
55
|
+
### Client-Side (Browser)
|
|
56
|
+
|
|
57
|
+
The UI component can be loaded directly from a CDN - no build step required:
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<!-- Using unpkg -->
|
|
61
|
+
<script type="module" src="https://unpkg.com/lit-shell.js/dist/ui/browser-bundle.js"></script>
|
|
62
|
+
|
|
63
|
+
<!-- Or using jsDelivr -->
|
|
64
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/lit-shell.js/dist/ui/browser-bundle.js"></script>
|
|
65
|
+
|
|
66
|
+
<!-- Pin to a specific version -->
|
|
67
|
+
<script type="module" src="https://unpkg.com/lit-shell.js@1.2.0/dist/ui/browser-bundle.js"></script>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The bundle includes the `<lit-shell-terminal>` web component with xterm.js built-in.
|
|
23
71
|
|
|
24
72
|
## Quick Start
|
|
25
73
|
|
|
@@ -51,9 +99,6 @@ server.listen(3000, () => {
|
|
|
51
99
|
### Client Usage (Web Component)
|
|
52
100
|
|
|
53
101
|
```html
|
|
54
|
-
<!-- Load xterm.js CSS -->
|
|
55
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css">
|
|
56
|
-
|
|
57
102
|
<!-- Load lit-shell.js UI bundle -->
|
|
58
103
|
<script type="module" src="https://unpkg.com/lit-shell.js/dist/ui/browser-bundle.js"></script>
|
|
59
104
|
|
|
@@ -94,6 +139,94 @@ client.write('ls -la\n');
|
|
|
94
139
|
client.resize(120, 40);
|
|
95
140
|
```
|
|
96
141
|
|
|
142
|
+
## Tabbed Terminals
|
|
143
|
+
|
|
144
|
+
Enable multiple terminal tabs within a single component using the `show-tabs` attribute:
|
|
145
|
+
|
|
146
|
+
```html
|
|
147
|
+
<lit-shell-terminal
|
|
148
|
+
url="ws://localhost:3000/terminal"
|
|
149
|
+
show-tabs
|
|
150
|
+
show-connection-panel
|
|
151
|
+
show-settings
|
|
152
|
+
show-status-bar
|
|
153
|
+
></lit-shell-terminal>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Features
|
|
157
|
+
|
|
158
|
+
- **Independent Sessions**: Each tab has its own WebSocket connection and terminal session
|
|
159
|
+
- **Tab Bar**: Shows all open tabs with status indicators
|
|
160
|
+
- **Dynamic Labels**: Tabs automatically update their label to show the shell or container name
|
|
161
|
+
- **Session Joining**: Create a tab and join an existing session from another tab
|
|
162
|
+
- **Easy Management**: Click "+" to add tabs, "×" to close, click tab to switch
|
|
163
|
+
|
|
164
|
+
### Tab API
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
const terminal = document.querySelector('lit-shell-terminal');
|
|
168
|
+
|
|
169
|
+
// Create a new tab
|
|
170
|
+
const tab = terminal.createTab('My Terminal');
|
|
171
|
+
// Returns: { id: 'tab-1', label: 'My Terminal', ... }
|
|
172
|
+
|
|
173
|
+
// Switch to a specific tab
|
|
174
|
+
terminal.switchTab('tab-1');
|
|
175
|
+
|
|
176
|
+
// Close a tab (resources are cleaned up automatically)
|
|
177
|
+
terminal.closeTab('tab-1');
|
|
178
|
+
|
|
179
|
+
// Access tab state
|
|
180
|
+
// Each tab maintains its own: client, terminal, sessionInfo, etc.
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Use Cases
|
|
184
|
+
|
|
185
|
+
**1. Multi-Environment Development**
|
|
186
|
+
```html
|
|
187
|
+
<!-- Open tabs for different containers -->
|
|
188
|
+
<lit-shell-terminal show-tabs show-connection-panel></lit-shell-terminal>
|
|
189
|
+
```
|
|
190
|
+
- Tab 1: Local shell for git operations
|
|
191
|
+
- Tab 2: Docker container for backend
|
|
192
|
+
- Tab 3: Docker container for frontend
|
|
193
|
+
|
|
194
|
+
**2. Session Sharing**
|
|
195
|
+
- Create a session in Tab 1
|
|
196
|
+
- Create Tab 2, select "Join Existing Session"
|
|
197
|
+
- Both tabs now mirror the same terminal
|
|
198
|
+
|
|
199
|
+
**3. Monitoring Multiple Processes**
|
|
200
|
+
- Open multiple tabs
|
|
201
|
+
- Each tab connects to a different running session
|
|
202
|
+
- Monitor all processes from a single interface
|
|
203
|
+
|
|
204
|
+
## Built-in Connection Panel
|
|
205
|
+
|
|
206
|
+
When `show-connection-panel` is enabled, the terminal component provides a built-in UI for:
|
|
207
|
+
|
|
208
|
+
- **Mode Selection**: Switch between local shell, Docker exec, Docker attach, and join existing session modes
|
|
209
|
+
- **Container Picker**: Dropdown of running containers (when Docker is enabled on server)
|
|
210
|
+
- **Shell Selection**: Choose from server-allowed shells
|
|
211
|
+
- **Session Timeout**: Configure orphan timeout (1 min to 1 week)
|
|
212
|
+
- **Tmux Integration**: Option for permanent session persistence
|
|
213
|
+
- **Connect/Disconnect**: One-click session management
|
|
214
|
+
|
|
215
|
+
The connection panel automatically queries the server for:
|
|
216
|
+
- Docker availability and allowed containers
|
|
217
|
+
- Allowed shells and default configuration
|
|
218
|
+
- Available sessions for joining
|
|
219
|
+
|
|
220
|
+
```html
|
|
221
|
+
<!-- Full-featured terminal with all UI panels -->
|
|
222
|
+
<lit-shell-terminal
|
|
223
|
+
url="ws://localhost:3000/terminal"
|
|
224
|
+
show-connection-panel
|
|
225
|
+
show-settings
|
|
226
|
+
show-status-bar
|
|
227
|
+
></lit-shell-terminal>
|
|
228
|
+
```
|
|
229
|
+
|
|
97
230
|
## API Reference
|
|
98
231
|
|
|
99
232
|
### Server
|
|
@@ -127,6 +260,18 @@ const server = new TerminalServer({
|
|
|
127
260
|
|
|
128
261
|
// Enable verbose logging
|
|
129
262
|
verbose: false,
|
|
263
|
+
|
|
264
|
+
// Session multiplexing options
|
|
265
|
+
maxClientsPerSession: 10, // Max clients per session (default: 10)
|
|
266
|
+
orphanTimeout: 60000, // Ms before orphaned sessions close (default: 60000)
|
|
267
|
+
historySize: 50000, // History buffer size in chars (default: 50000)
|
|
268
|
+
historyEnabled: true, // Enable history replay (default: true)
|
|
269
|
+
maxSessionsTotal: 100, // Max concurrent sessions (default: 100)
|
|
270
|
+
|
|
271
|
+
// Docker configuration
|
|
272
|
+
allowDockerExec: false,
|
|
273
|
+
allowedContainerPatterns: ['.*'],
|
|
274
|
+
defaultContainerShell: '/bin/sh',
|
|
130
275
|
});
|
|
131
276
|
|
|
132
277
|
// Attach to HTTP server
|
|
@@ -138,6 +283,10 @@ server.listen(3001);
|
|
|
138
283
|
// Get active sessions
|
|
139
284
|
const sessions = server.getSessions();
|
|
140
285
|
|
|
286
|
+
// Get session statistics
|
|
287
|
+
const stats = server.getStats();
|
|
288
|
+
// { sessionCount: 5, clientCount: 12, orphanedCount: 1 }
|
|
289
|
+
|
|
141
290
|
// Close server
|
|
142
291
|
server.close();
|
|
143
292
|
```
|
|
@@ -166,6 +315,11 @@ const sessionInfo = await client.spawn({
|
|
|
166
315
|
env: { TERM: 'xterm-256color' },
|
|
167
316
|
cols: 80,
|
|
168
317
|
rows: 24,
|
|
318
|
+
container: 'optional-container-name',
|
|
319
|
+
orphanTimeout: 3600000,
|
|
320
|
+
useTmux: false,
|
|
321
|
+
label: 'my-session', // Optional label for identification
|
|
322
|
+
allowJoin: true, // Allow others to join (default: true)
|
|
169
323
|
});
|
|
170
324
|
|
|
171
325
|
// Write to terminal
|
|
@@ -193,6 +347,26 @@ client.isConnected(); // boolean
|
|
|
193
347
|
client.hasActiveSession(); // boolean
|
|
194
348
|
client.getSessionId(); // string | null
|
|
195
349
|
client.getSessionInfo(); // SessionInfo | null
|
|
350
|
+
|
|
351
|
+
// Session multiplexing
|
|
352
|
+
const sessions = await client.listSessions(); // List all sessions
|
|
353
|
+
const session = await client.join({ // Join existing session
|
|
354
|
+
sessionId: 'term-123...',
|
|
355
|
+
requestHistory: true,
|
|
356
|
+
historyLimit: 50000,
|
|
357
|
+
});
|
|
358
|
+
client.leave(sessionId); // Leave without killing
|
|
359
|
+
|
|
360
|
+
// Multiplexing event handlers
|
|
361
|
+
client.onClientJoined((sessionId, count) => console.log(`${count} clients`));
|
|
362
|
+
client.onClientLeft((sessionId, count) => console.log(`${count} clients`));
|
|
363
|
+
client.onSessionClosed((sessionId, reason) => console.log(reason));
|
|
364
|
+
// reason: 'orphan_timeout' | 'owner_closed' | 'process_exit' | 'error'
|
|
365
|
+
|
|
366
|
+
// Reconnection with session recovery
|
|
367
|
+
client.onReconnectWithSession((sessionId) => {
|
|
368
|
+
// Previous session is still available after reconnect
|
|
369
|
+
});
|
|
196
370
|
```
|
|
197
371
|
|
|
198
372
|
### UI Component
|
|
@@ -206,12 +380,16 @@ client.getSessionInfo(); // SessionInfo | null
|
|
|
206
380
|
cwd="/home/user"
|
|
207
381
|
theme="dark"
|
|
208
382
|
font-size="14"
|
|
209
|
-
font-family="
|
|
383
|
+
font-family="Cascadia Mono, Consolas, monospace"
|
|
210
384
|
cols="80"
|
|
211
385
|
rows="24"
|
|
212
386
|
auto-connect
|
|
213
387
|
auto-spawn
|
|
214
388
|
no-header
|
|
389
|
+
show-connection-panel
|
|
390
|
+
show-settings
|
|
391
|
+
show-status-bar
|
|
392
|
+
show-tabs
|
|
215
393
|
></lit-shell-terminal>
|
|
216
394
|
```
|
|
217
395
|
|
|
@@ -222,14 +400,22 @@ client.getSessionInfo(); // SessionInfo | null
|
|
|
222
400
|
| `url` | string | `''` | WebSocket URL |
|
|
223
401
|
| `shell` | string | `''` | Shell to use |
|
|
224
402
|
| `cwd` | string | `''` | Working directory |
|
|
403
|
+
| `container` | string | `''` | Docker container name |
|
|
404
|
+
| `container-shell` | string | `''` | Shell inside container |
|
|
405
|
+
| `container-user` | string | `''` | User in container |
|
|
406
|
+
| `container-cwd` | string | `''` | Working directory in container |
|
|
225
407
|
| `theme` | `'dark'` \| `'light'` \| `'auto'` | `'dark'` | Color theme |
|
|
226
408
|
| `font-size` | number | `14` | Terminal font size |
|
|
227
|
-
| `font-family` | string | `'
|
|
409
|
+
| `font-family` | string | `'Cascadia Mono, ...'` | Terminal font |
|
|
228
410
|
| `cols` | number | `80` | Initial columns |
|
|
229
411
|
| `rows` | number | `24` | Initial rows |
|
|
230
412
|
| `auto-connect` | boolean | `false` | Connect on mount |
|
|
231
413
|
| `auto-spawn` | boolean | `false` | Spawn on connect |
|
|
232
414
|
| `no-header` | boolean | `false` | Hide header bar |
|
|
415
|
+
| `show-connection-panel` | boolean | `false` | Show connection panel with container/shell selector |
|
|
416
|
+
| `show-settings` | boolean | `false` | Show settings dropdown (theme, font size) |
|
|
417
|
+
| `show-status-bar` | boolean | `false` | Show status bar with connection info and errors |
|
|
418
|
+
| `show-tabs` | boolean | `false` | Enable tabbed terminal interface |
|
|
233
419
|
|
|
234
420
|
**Methods:**
|
|
235
421
|
|
|
@@ -244,6 +430,15 @@ terminal.clear(); // Clear display
|
|
|
244
430
|
terminal.write('text'); // Write to display
|
|
245
431
|
terminal.writeln('line'); // Write line to display
|
|
246
432
|
terminal.focus(); // Focus terminal
|
|
433
|
+
|
|
434
|
+
// Session multiplexing
|
|
435
|
+
await terminal.join(sessionId); // Join existing session
|
|
436
|
+
terminal.leave(); // Leave without killing
|
|
437
|
+
|
|
438
|
+
// Tab methods (when show-tabs is enabled)
|
|
439
|
+
terminal.createTab('label'); // Create new tab
|
|
440
|
+
terminal.switchTab('tab-id'); // Switch to tab
|
|
441
|
+
terminal.closeTab('tab-id'); // Close tab
|
|
247
442
|
```
|
|
248
443
|
|
|
249
444
|
**Events:**
|
|
@@ -254,8 +449,184 @@ terminal.addEventListener('disconnect', () => {});
|
|
|
254
449
|
terminal.addEventListener('spawned', (e) => console.log(e.detail.session));
|
|
255
450
|
terminal.addEventListener('exit', (e) => console.log(e.detail.exitCode));
|
|
256
451
|
terminal.addEventListener('error', (e) => console.log(e.detail.error));
|
|
452
|
+
terminal.addEventListener('theme-change', (e) => console.log(e.detail.theme));
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## Docker Container Support
|
|
456
|
+
|
|
457
|
+
lit-shell.js can connect to Docker containers, allowing you to exec into running containers directly from the browser.
|
|
458
|
+
|
|
459
|
+
### Server Configuration
|
|
460
|
+
|
|
461
|
+
```javascript
|
|
462
|
+
const server = new TerminalServer({
|
|
463
|
+
// Enable Docker exec feature
|
|
464
|
+
allowDockerExec: true,
|
|
465
|
+
|
|
466
|
+
// Restrict which containers can be accessed (regex patterns)
|
|
467
|
+
allowedContainerPatterns: [
|
|
468
|
+
'^myapp-', // Containers starting with 'myapp-'
|
|
469
|
+
'^dev-container$', // Exact match
|
|
470
|
+
'backend', // Contains 'backend'
|
|
471
|
+
],
|
|
472
|
+
|
|
473
|
+
// Default shell for containers
|
|
474
|
+
defaultContainerShell: '/bin/bash',
|
|
475
|
+
|
|
476
|
+
// Path to Docker CLI (default: 'docker')
|
|
477
|
+
dockerPath: '/usr/bin/docker',
|
|
478
|
+
|
|
479
|
+
verbose: true,
|
|
480
|
+
});
|
|
257
481
|
```
|
|
258
482
|
|
|
483
|
+
### Client Usage
|
|
484
|
+
|
|
485
|
+
```javascript
|
|
486
|
+
// Connect to a Docker container
|
|
487
|
+
await client.spawn({
|
|
488
|
+
container: 'my-container-name', // Container ID or name
|
|
489
|
+
containerShell: '/bin/sh', // Shell inside container
|
|
490
|
+
containerUser: 'root', // User to run as
|
|
491
|
+
containerCwd: '/app', // Working directory in container
|
|
492
|
+
env: { DEBUG: 'true' }, // Environment variables
|
|
493
|
+
});
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Web Component
|
|
497
|
+
|
|
498
|
+
```html
|
|
499
|
+
<lit-shell-terminal
|
|
500
|
+
url="ws://localhost:3000/terminal"
|
|
501
|
+
container="my-container-name"
|
|
502
|
+
container-shell="/bin/bash"
|
|
503
|
+
container-user="node"
|
|
504
|
+
container-cwd="/app"
|
|
505
|
+
theme="dark"
|
|
506
|
+
auto-connect
|
|
507
|
+
auto-spawn
|
|
508
|
+
></lit-shell-terminal>
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### Docker Attach Mode
|
|
512
|
+
|
|
513
|
+
Docker attach connects to a container's main process (PID 1) instead of spawning a new shell. This is useful for:
|
|
514
|
+
- Interacting with interactive containers started with `docker run -it`
|
|
515
|
+
- Debugging container startup issues
|
|
516
|
+
- Sharing a session with `docker attach` from another terminal
|
|
517
|
+
|
|
518
|
+
```javascript
|
|
519
|
+
// Client: Attach to container's main process
|
|
520
|
+
await client.spawn({
|
|
521
|
+
container: 'my-container',
|
|
522
|
+
attachMode: true, // Use docker attach instead of docker exec
|
|
523
|
+
});
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
**Important:** Docker attach connects to whatever is running as PID 1. If the container was started with a non-interactive command (like a web server), attach may not provide useful interaction.
|
|
527
|
+
|
|
528
|
+
## Session Multiplexing
|
|
529
|
+
|
|
530
|
+
Session multiplexing allows multiple clients to connect to the same terminal session. This enables:
|
|
531
|
+
- **Collaboration**: Multiple users can share a terminal
|
|
532
|
+
- **Session Persistence**: Sessions survive client disconnects
|
|
533
|
+
- **History Replay**: New clients receive recent output when joining
|
|
534
|
+
- **Monitoring**: Watch others' terminal sessions in real-time
|
|
535
|
+
|
|
536
|
+
### How It Works
|
|
537
|
+
|
|
538
|
+
```
|
|
539
|
+
┌──────────┐ ┌─────────────────────────────────────────┐ ┌──────────┐
|
|
540
|
+
│ Client A │◄────┤ SessionManager ├────►│ PTY │
|
|
541
|
+
└──────────┘ │ ┌─────────────────────────────────────┐ │ │ Process │
|
|
542
|
+
│ │ SharedSession │ │ └──────────┘
|
|
543
|
+
┌──────────┐ │ │ - clients: [A, B, C] │ │
|
|
544
|
+
│ Client B │◄────┼──┤ - historyBuffer (50KB) │ │
|
|
545
|
+
└──────────┘ │ │ - orphanedAt: null │ │
|
|
546
|
+
│ └─────────────────────────────────────┘ │
|
|
547
|
+
┌──────────┐ │ │
|
|
548
|
+
│ Client C │◄────┼─────────────────────────────────────────┘
|
|
549
|
+
└──────────┘ (broadcast output)
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### Client API
|
|
553
|
+
|
|
554
|
+
```javascript
|
|
555
|
+
// List available sessions
|
|
556
|
+
const sessions = await client.listSessions();
|
|
557
|
+
|
|
558
|
+
// Create a shareable session
|
|
559
|
+
await client.spawn({
|
|
560
|
+
shell: '/bin/bash',
|
|
561
|
+
label: 'dev-session', // Optional label for identification
|
|
562
|
+
allowJoin: true, // Allow others to join (default: true)
|
|
563
|
+
orphanTimeout: 3600000, // Keep alive 1 hour after last client leaves
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
// Join an existing session
|
|
567
|
+
const session = await client.join({
|
|
568
|
+
sessionId: 'term-abc123...',
|
|
569
|
+
requestHistory: true, // Request output history
|
|
570
|
+
historyLimit: 50000, // Max history chars to receive
|
|
571
|
+
});
|
|
572
|
+
// session.history contains recent output
|
|
573
|
+
|
|
574
|
+
// Leave session without killing it
|
|
575
|
+
client.leave(sessionId);
|
|
576
|
+
// Session survives if other clients connected
|
|
577
|
+
// Or waits orphanTimeout before closing
|
|
578
|
+
|
|
579
|
+
// Kill session
|
|
580
|
+
client.kill();
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### Use Cases
|
|
584
|
+
|
|
585
|
+
**1. Pair Programming**
|
|
586
|
+
```javascript
|
|
587
|
+
// Developer A creates session
|
|
588
|
+
await client.spawn({ label: 'pair-session' });
|
|
589
|
+
// Share session ID with Developer B
|
|
590
|
+
// Developer B joins with history
|
|
591
|
+
await client.join({ sessionId, requestHistory: true });
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
**2. Session Persistence**
|
|
595
|
+
```javascript
|
|
596
|
+
// Start long-running task
|
|
597
|
+
await client.spawn({ shell: '/bin/bash', orphanTimeout: 86400000 });
|
|
598
|
+
client.write('npm run build\n');
|
|
599
|
+
client.disconnect(); // Session survives!
|
|
600
|
+
|
|
601
|
+
// Later, reconnect
|
|
602
|
+
await client.connect();
|
|
603
|
+
const sessions = await client.listSessions();
|
|
604
|
+
await client.join({ sessionId: sessions[0].sessionId, requestHistory: true });
|
|
605
|
+
// See build output that happened while disconnected
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
**3. Monitoring**
|
|
609
|
+
```javascript
|
|
610
|
+
// Admin joins session in read-only mode
|
|
611
|
+
await client.join({ sessionId, requestHistory: true });
|
|
612
|
+
// Watch activity without interfering
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
## Mobile Support
|
|
616
|
+
|
|
617
|
+
On mobile devices, lit-shell automatically shows a touch keyboard with common terminal keys:
|
|
618
|
+
|
|
619
|
+
```
|
|
620
|
+
Row 1: [ESC] [/] [-] [HOME] [↑] [END] [PGUP]
|
|
621
|
+
Row 2: [TAB] [CTRL] [ALT] [←] [↓] [→] [PGDN]
|
|
622
|
+
Row 3: [^C] [^D] [^Z] [^L] [^A] [^E] [^R] (expandable)
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
- **Auto-detection**: Detects mobile via touch capability + viewport size
|
|
626
|
+
- **Sticky modifiers**: CTRL and ALT are toggle keys (tap to activate, applies to next key)
|
|
627
|
+
- **Collapsible**: Extra row can be expanded/collapsed for more screen space
|
|
628
|
+
- **Hide/show toggle**: Entire keyboard can be hidden when not needed
|
|
629
|
+
|
|
259
630
|
## Theming
|
|
260
631
|
|
|
261
632
|
The component uses CSS custom properties for theming:
|
|
@@ -291,14 +662,72 @@ const server = new TerminalServer({
|
|
|
291
662
|
// Restrict working directories
|
|
292
663
|
allowedPaths: ['/home/app', '/var/www'],
|
|
293
664
|
|
|
665
|
+
// Restrict Docker containers
|
|
666
|
+
allowDockerExec: true,
|
|
667
|
+
allowedContainerPatterns: ['^myapp-'],
|
|
668
|
+
|
|
294
669
|
// Limit sessions per client
|
|
295
670
|
maxSessionsPerClient: 2,
|
|
296
671
|
|
|
297
672
|
// Set idle timeout
|
|
298
|
-
idleTimeout: 10 * 60 * 1000,
|
|
673
|
+
idleTimeout: 10 * 60 * 1000,
|
|
299
674
|
});
|
|
300
675
|
```
|
|
301
676
|
|
|
677
|
+
## Examples
|
|
678
|
+
|
|
679
|
+
See the [examples](./examples) directory for complete working examples:
|
|
680
|
+
|
|
681
|
+
- [**docker-container**](./examples/docker-container) - Connect to Docker containers from the browser
|
|
682
|
+
- [**multiplexing**](./examples/multiplexing) - Session multiplexing with multiple clients sharing terminals
|
|
683
|
+
|
|
684
|
+
### Running Locally (Development)
|
|
685
|
+
|
|
686
|
+
```bash
|
|
687
|
+
# Clone the repository
|
|
688
|
+
git clone https://github.com/lsadehaan/lit-shell.git
|
|
689
|
+
cd lit-shell
|
|
690
|
+
|
|
691
|
+
# Install dependencies (including node-pty)
|
|
692
|
+
npm install
|
|
693
|
+
npm install node-pty --save-dev --legacy-peer-deps
|
|
694
|
+
|
|
695
|
+
# Build the project
|
|
696
|
+
npm run build
|
|
697
|
+
|
|
698
|
+
# Start a test container (optional, for Docker exec testing)
|
|
699
|
+
docker run -d --name test-container alpine sleep infinity
|
|
700
|
+
|
|
701
|
+
# Run the example server
|
|
702
|
+
node examples/docker-container/server.js
|
|
703
|
+
|
|
704
|
+
# Open http://localhost:3000 in your browser
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
### Quick Start with Docker Compose
|
|
708
|
+
|
|
709
|
+
Run the full demo with Docker Compose (no local node-pty installation required):
|
|
710
|
+
|
|
711
|
+
```bash
|
|
712
|
+
cd docker
|
|
713
|
+
docker compose up -d
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
This starts:
|
|
717
|
+
- lit-shell server on http://localhost:3000
|
|
718
|
+
- Two test containers (Alpine and Ubuntu) to exec into
|
|
719
|
+
|
|
720
|
+
Open http://localhost:3000 and use the connection panel to:
|
|
721
|
+
1. Select "Docker Container" mode
|
|
722
|
+
2. Choose a container from the dropdown
|
|
723
|
+
3. Click "Start Session"
|
|
724
|
+
|
|
725
|
+
Stop the demo:
|
|
726
|
+
|
|
727
|
+
```bash
|
|
728
|
+
docker compose down
|
|
729
|
+
```
|
|
730
|
+
|
|
302
731
|
## License
|
|
303
732
|
|
|
304
733
|
MIT
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// src/version.ts
|
|
2
|
+
var VERSION = "1.2.1";
|
|
3
|
+
|
|
1
4
|
// src/client/terminal-client.ts
|
|
2
5
|
var TerminalClient = class {
|
|
3
6
|
constructor(config) {
|
|
@@ -8,6 +11,8 @@ var TerminalClient = class {
|
|
|
8
11
|
this.serverInfo = null;
|
|
9
12
|
this.reconnectAttempts = 0;
|
|
10
13
|
this.reconnectTimeout = null;
|
|
14
|
+
this.previousSessionId = null;
|
|
15
|
+
this.isReconnecting = false;
|
|
11
16
|
// Event handlers
|
|
12
17
|
this.connectHandlers = [];
|
|
13
18
|
this.disconnectHandlers = [];
|
|
@@ -24,6 +29,7 @@ var TerminalClient = class {
|
|
|
24
29
|
this.clientJoinedHandlers = [];
|
|
25
30
|
this.clientLeftHandlers = [];
|
|
26
31
|
this.sessionClosedHandlers = [];
|
|
32
|
+
this.reconnectWithSessionHandlers = [];
|
|
27
33
|
// Promise resolvers for spawn/join
|
|
28
34
|
this.spawnResolve = null;
|
|
29
35
|
this.spawnReject = null;
|
|
@@ -58,17 +64,25 @@ var TerminalClient = class {
|
|
|
58
64
|
this.state = "connected";
|
|
59
65
|
this.reconnectAttempts = 0;
|
|
60
66
|
this.connectHandlers.forEach((handler) => handler());
|
|
67
|
+
if (this.isReconnecting && this.previousSessionId) {
|
|
68
|
+
this.checkPreviousSessionAndNotify();
|
|
69
|
+
}
|
|
70
|
+
this.isReconnecting = false;
|
|
61
71
|
resolve();
|
|
62
72
|
};
|
|
63
73
|
this.ws.onclose = () => {
|
|
64
74
|
const wasConnected = this.state === "connected";
|
|
65
75
|
this.state = "disconnected";
|
|
76
|
+
if (this.sessionId) {
|
|
77
|
+
this.previousSessionId = this.sessionId;
|
|
78
|
+
}
|
|
66
79
|
this.sessionId = null;
|
|
67
80
|
this.sessionInfo = null;
|
|
68
81
|
if (wasConnected) {
|
|
69
82
|
this.disconnectHandlers.forEach((handler) => handler());
|
|
70
83
|
}
|
|
71
84
|
if (this.config.reconnect && this.reconnectAttempts < this.config.maxReconnectAttempts) {
|
|
85
|
+
this.isReconnecting = true;
|
|
72
86
|
this.scheduleReconnect();
|
|
73
87
|
}
|
|
74
88
|
};
|
|
@@ -543,8 +557,54 @@ var TerminalClient = class {
|
|
|
543
557
|
getServerInfo() {
|
|
544
558
|
return this.serverInfo;
|
|
545
559
|
}
|
|
560
|
+
/**
|
|
561
|
+
* Get previous session ID (available after disconnect)
|
|
562
|
+
*/
|
|
563
|
+
getPreviousSessionId() {
|
|
564
|
+
return this.previousSessionId;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Clear previous session ID (call after user declines to rejoin)
|
|
568
|
+
*/
|
|
569
|
+
clearPreviousSessionId() {
|
|
570
|
+
this.previousSessionId = null;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Called when reconnected and previous session is available
|
|
574
|
+
*/
|
|
575
|
+
onReconnectWithSession(handler) {
|
|
576
|
+
this.reconnectWithSessionHandlers.push(handler);
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Check if previous session exists and notify handlers
|
|
580
|
+
*/
|
|
581
|
+
async checkPreviousSessionAndNotify() {
|
|
582
|
+
if (!this.previousSessionId)
|
|
583
|
+
return;
|
|
584
|
+
try {
|
|
585
|
+
const sessions = await this.listSessions();
|
|
586
|
+
const previousSession = sessions.find(
|
|
587
|
+
(s) => s.sessionId === this.previousSessionId
|
|
588
|
+
);
|
|
589
|
+
if (previousSession && previousSession.accepting) {
|
|
590
|
+
this.reconnectWithSessionHandlers.forEach((handler) => {
|
|
591
|
+
try {
|
|
592
|
+
handler(this.previousSessionId);
|
|
593
|
+
} catch (e) {
|
|
594
|
+
console.error("[lit-shell] Error in reconnectWithSession handler:", e);
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
} else {
|
|
598
|
+
this.previousSessionId = null;
|
|
599
|
+
}
|
|
600
|
+
} catch (e) {
|
|
601
|
+
console.error("[lit-shell] Failed to check previous session:", e);
|
|
602
|
+
this.previousSessionId = null;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
546
605
|
};
|
|
547
606
|
export {
|
|
548
|
-
TerminalClient
|
|
607
|
+
TerminalClient,
|
|
608
|
+
VERSION
|
|
549
609
|
};
|
|
550
610
|
//# sourceMappingURL=browser-bundle.js.map
|