termbeam 0.0.4 β 0.0.6
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 +10 -0
- package/package.json +2 -2
- package/public/index.html +237 -70
- package/public/terminal.html +286 -100
- package/src/cli.js +52 -1
- package/src/routes.js +9 -1
- package/src/sessions.js +6 -1
- package/src/shells.js +77 -0
- package/src/tunnel.js +61 -8
package/README.md
CHANGED
|
@@ -65,6 +65,12 @@ termbeam --password mysecret
|
|
|
65
65
|
termbeam --tunnel --generate-password
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
> Requires the [Azure Dev Tunnels CLI](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/get-started):
|
|
69
|
+
>
|
|
70
|
+
> - **Windows:** `winget install Microsoft.devtunnel`
|
|
71
|
+
> - **macOS:** `brew install --cask devtunnel`
|
|
72
|
+
> - **Linux:** `curl -sL https://aka.ms/DevTunnelCliInstall | bash`
|
|
73
|
+
|
|
68
74
|
## π Usage
|
|
69
75
|
|
|
70
76
|
```bash
|
|
@@ -158,3 +164,7 @@ Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for:
|
|
|
158
164
|
## π License
|
|
159
165
|
|
|
160
166
|
[MIT](LICENSE) β made with β€οΈ by [@dorlugasigal](https://github.com/dorlugasigal)
|
|
167
|
+
|
|
168
|
+
## π Acknowledgments
|
|
169
|
+
|
|
170
|
+
Special thanks to [@tamirdresher](https://github.com/tamirdresher) for the [blog post](https://www.tamirdresher.com/blog/2026/02/26/squad-remote-control) that inspired the solution idea for this project, and for his [cli-tunnel](https://github.com/tamirdresher/cli-tunnel) implementation.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "termbeam",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Beam your terminal to any device β mobile-optimized web terminal with multi-session support",
|
|
5
5
|
"main": "src/server.js",
|
|
6
6
|
"bin": {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"cookie-parser": "^1.4.7",
|
|
59
59
|
"express": "^5.2.1",
|
|
60
|
-
"node-pty": "1.
|
|
60
|
+
"node-pty": "^1.1.0",
|
|
61
61
|
"qrcode": "^1.5.4",
|
|
62
62
|
"ws": "^8.19.0"
|
|
63
63
|
},
|
package/public/index.html
CHANGED
|
@@ -8,9 +8,47 @@
|
|
|
8
8
|
/>
|
|
9
9
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
10
10
|
<meta name="mobile-web-app-capable" content="yes" />
|
|
11
|
-
<meta name="theme-color" content="#
|
|
11
|
+
<meta name="theme-color" content="#1e1e1e" />
|
|
12
12
|
<title>TermBeam</title>
|
|
13
13
|
<style>
|
|
14
|
+
:root {
|
|
15
|
+
--bg: #1e1e1e;
|
|
16
|
+
--surface: #252526;
|
|
17
|
+
--border: #3c3c3c;
|
|
18
|
+
--border-subtle: #474747;
|
|
19
|
+
--text: #d4d4d4;
|
|
20
|
+
--text-secondary: #858585;
|
|
21
|
+
--text-dim: #6e6e6e;
|
|
22
|
+
--text-muted: #5a5a5a;
|
|
23
|
+
--accent: #0078d4;
|
|
24
|
+
--accent-hover: #1a8ae8;
|
|
25
|
+
--accent-active: #005a9e;
|
|
26
|
+
--danger: #f14c4c;
|
|
27
|
+
--danger-hover: #d73a3a;
|
|
28
|
+
--success: #89d185;
|
|
29
|
+
--info: #b0b0b0;
|
|
30
|
+
--shadow: rgba(0,0,0,0.15);
|
|
31
|
+
--overlay-bg: rgba(0,0,0,0.7);
|
|
32
|
+
}
|
|
33
|
+
[data-theme="light"] {
|
|
34
|
+
--bg: #ffffff;
|
|
35
|
+
--surface: #f3f3f3;
|
|
36
|
+
--border: #e0e0e0;
|
|
37
|
+
--border-subtle: #d0d0d0;
|
|
38
|
+
--text: #1e1e1e;
|
|
39
|
+
--text-secondary: #616161;
|
|
40
|
+
--text-dim: #767676;
|
|
41
|
+
--text-muted: #a0a0a0;
|
|
42
|
+
--accent: #0078d4;
|
|
43
|
+
--accent-hover: #106ebe;
|
|
44
|
+
--accent-active: #005a9e;
|
|
45
|
+
--danger: #e51400;
|
|
46
|
+
--danger-hover: #c20000;
|
|
47
|
+
--success: #16825d;
|
|
48
|
+
--info: #616161;
|
|
49
|
+
--shadow: rgba(0,0,0,0.06);
|
|
50
|
+
--overlay-bg: rgba(0,0,0,0.4);
|
|
51
|
+
}
|
|
14
52
|
* {
|
|
15
53
|
margin: 0;
|
|
16
54
|
padding: 0;
|
|
@@ -20,39 +58,66 @@
|
|
|
20
58
|
body {
|
|
21
59
|
height: 100%;
|
|
22
60
|
width: 100%;
|
|
23
|
-
background:
|
|
24
|
-
color:
|
|
61
|
+
background: var(--bg);
|
|
62
|
+
color: var(--text);
|
|
25
63
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
64
|
+
transition: background 0.3s, color 0.3s;
|
|
26
65
|
}
|
|
27
66
|
|
|
28
67
|
.header {
|
|
29
68
|
padding: 20px 16px 12px;
|
|
30
69
|
text-align: center;
|
|
31
|
-
border-bottom: 1px solid
|
|
70
|
+
border-bottom: 1px solid var(--border);
|
|
71
|
+
transition: border-color 0.3s;
|
|
72
|
+
position: relative;
|
|
32
73
|
}
|
|
33
74
|
.header h1 {
|
|
34
75
|
font-size: 22px;
|
|
35
76
|
font-weight: 700;
|
|
36
77
|
}
|
|
37
78
|
.header h1 span {
|
|
38
|
-
color:
|
|
79
|
+
color: var(--accent);
|
|
39
80
|
}
|
|
40
81
|
.header p {
|
|
41
82
|
font-size: 13px;
|
|
42
|
-
color:
|
|
83
|
+
color: var(--text-secondary);
|
|
43
84
|
margin-top: 4px;
|
|
44
85
|
}
|
|
86
|
+
.theme-toggle {
|
|
87
|
+
position: absolute;
|
|
88
|
+
top: 16px;
|
|
89
|
+
right: 16px;
|
|
90
|
+
background: none;
|
|
91
|
+
border: 1px solid var(--border);
|
|
92
|
+
color: var(--text-dim);
|
|
93
|
+
width: 32px;
|
|
94
|
+
height: 32px;
|
|
95
|
+
border-radius: 8px;
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
justify-content: center;
|
|
100
|
+
font-size: 16px;
|
|
101
|
+
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
|
102
|
+
-webkit-tap-highlight-color: transparent;
|
|
103
|
+
}
|
|
104
|
+
.theme-toggle:hover {
|
|
105
|
+
color: var(--text);
|
|
106
|
+
border-color: var(--border-subtle);
|
|
107
|
+
background: var(--border);
|
|
108
|
+
}
|
|
45
109
|
|
|
46
110
|
.sessions-list {
|
|
47
111
|
padding: 16px;
|
|
112
|
+
padding-bottom: 80px;
|
|
48
113
|
display: flex;
|
|
49
114
|
flex-direction: column;
|
|
50
115
|
gap: 12px;
|
|
51
116
|
}
|
|
52
117
|
|
|
53
118
|
.session-card {
|
|
54
|
-
background:
|
|
55
|
-
border: 1px solid
|
|
119
|
+
background: var(--surface);
|
|
120
|
+
border: 1px solid var(--border);
|
|
56
121
|
border-radius: 12px;
|
|
57
122
|
padding: 16px;
|
|
58
123
|
display: flex;
|
|
@@ -60,14 +125,14 @@
|
|
|
60
125
|
gap: 8px;
|
|
61
126
|
text-decoration: none;
|
|
62
127
|
color: inherit;
|
|
63
|
-
transition: transform 0.2s ease;
|
|
128
|
+
transition: transform 0.2s ease, border-color 0.15s, background 0.3s;
|
|
64
129
|
cursor: pointer;
|
|
65
130
|
-webkit-tap-highlight-color: transparent;
|
|
66
131
|
position: relative;
|
|
67
132
|
z-index: 1;
|
|
68
133
|
}
|
|
69
134
|
.session-card:hover {
|
|
70
|
-
border-color:
|
|
135
|
+
border-color: var(--accent);
|
|
71
136
|
}
|
|
72
137
|
|
|
73
138
|
.swipe-wrap {
|
|
@@ -81,7 +146,7 @@
|
|
|
81
146
|
top: 0;
|
|
82
147
|
bottom: 0;
|
|
83
148
|
width: 80px;
|
|
84
|
-
background:
|
|
149
|
+
background: var(--danger);
|
|
85
150
|
display: flex;
|
|
86
151
|
align-items: center;
|
|
87
152
|
justify-content: center;
|
|
@@ -123,22 +188,23 @@
|
|
|
123
188
|
width: 10px;
|
|
124
189
|
height: 10px;
|
|
125
190
|
border-radius: 50%;
|
|
126
|
-
background:
|
|
191
|
+
background: var(--success);
|
|
127
192
|
flex-shrink: 0;
|
|
128
193
|
}
|
|
129
194
|
.session-card .pid {
|
|
130
195
|
font-size: 12px;
|
|
131
|
-
color:
|
|
132
|
-
background:
|
|
196
|
+
color: var(--text-secondary);
|
|
197
|
+
background: var(--bg);
|
|
133
198
|
padding: 2px 8px;
|
|
134
199
|
border-radius: 4px;
|
|
200
|
+
transition: background 0.3s, color 0.3s;
|
|
135
201
|
}
|
|
136
202
|
.session-card .details {
|
|
137
203
|
display: flex;
|
|
138
204
|
flex-wrap: wrap;
|
|
139
205
|
gap: 6px 16px;
|
|
140
206
|
font-size: 13px;
|
|
141
|
-
color:
|
|
207
|
+
color: var(--info);
|
|
142
208
|
}
|
|
143
209
|
.session-card .details span {
|
|
144
210
|
display: flex;
|
|
@@ -147,43 +213,57 @@
|
|
|
147
213
|
}
|
|
148
214
|
.session-card .connect-btn {
|
|
149
215
|
align-self: flex-end;
|
|
150
|
-
background:
|
|
151
|
-
color:
|
|
216
|
+
background: var(--accent);
|
|
217
|
+
color: #ffffff;
|
|
152
218
|
border: none;
|
|
153
219
|
border-radius: 8px;
|
|
154
220
|
padding: 8px 20px;
|
|
155
221
|
font-size: 14px;
|
|
156
222
|
font-weight: 600;
|
|
157
223
|
cursor: pointer;
|
|
224
|
+
transition: background 0.15s, transform 0.1s;
|
|
225
|
+
}
|
|
226
|
+
.session-card .connect-btn:hover {
|
|
227
|
+
background: var(--accent-hover);
|
|
158
228
|
}
|
|
159
229
|
.session-card .connect-btn:active {
|
|
160
|
-
background:
|
|
230
|
+
background: var(--accent-active);
|
|
231
|
+
transform: scale(0.95);
|
|
161
232
|
}
|
|
162
233
|
|
|
163
234
|
.new-session {
|
|
164
|
-
|
|
235
|
+
position: fixed;
|
|
236
|
+
bottom: 16px;
|
|
237
|
+
left: 16px;
|
|
238
|
+
right: 16px;
|
|
165
239
|
padding: 14px;
|
|
166
|
-
background:
|
|
167
|
-
|
|
240
|
+
background: var(--accent);
|
|
241
|
+
color: #ffffff;
|
|
242
|
+
border: none;
|
|
168
243
|
border-radius: 12px;
|
|
169
|
-
color: #888;
|
|
170
244
|
font-size: 15px;
|
|
171
245
|
font-weight: 600;
|
|
172
246
|
cursor: pointer;
|
|
173
247
|
text-align: center;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
248
|
+
z-index: 50;
|
|
249
|
+
transition: background 0.15s, transform 0.1s, box-shadow 0.15s;
|
|
250
|
+
box-shadow: 0 2px 8px rgba(0, 120, 212, 0.3);
|
|
251
|
+
padding-bottom: calc(14px + env(safe-area-inset-bottom, 0px));
|
|
252
|
+
}
|
|
253
|
+
.new-session:hover {
|
|
254
|
+
background: var(--accent-hover);
|
|
255
|
+
box-shadow: 0 4px 12px rgba(0, 120, 212, 0.4);
|
|
177
256
|
}
|
|
178
257
|
.new-session:active {
|
|
179
|
-
|
|
180
|
-
|
|
258
|
+
background: var(--accent-active);
|
|
259
|
+
transform: scale(0.98);
|
|
260
|
+
box-shadow: 0 1px 4px rgba(0, 120, 212, 0.2);
|
|
181
261
|
}
|
|
182
262
|
|
|
183
263
|
.empty-state {
|
|
184
264
|
text-align: center;
|
|
185
265
|
padding: 60px 20px;
|
|
186
|
-
color:
|
|
266
|
+
color: var(--text-muted);
|
|
187
267
|
font-size: 15px;
|
|
188
268
|
}
|
|
189
269
|
|
|
@@ -195,7 +275,7 @@
|
|
|
195
275
|
left: 0;
|
|
196
276
|
right: 0;
|
|
197
277
|
bottom: 0;
|
|
198
|
-
background:
|
|
278
|
+
background: var(--overlay-bg);
|
|
199
279
|
z-index: 100;
|
|
200
280
|
justify-content: center;
|
|
201
281
|
align-items: center;
|
|
@@ -204,11 +284,12 @@
|
|
|
204
284
|
display: flex;
|
|
205
285
|
}
|
|
206
286
|
.modal {
|
|
207
|
-
background:
|
|
287
|
+
background: var(--surface);
|
|
208
288
|
border-radius: 16px;
|
|
209
289
|
width: 90%;
|
|
210
290
|
max-width: 500px;
|
|
211
291
|
padding: 24px 20px;
|
|
292
|
+
transition: background 0.3s;
|
|
212
293
|
}
|
|
213
294
|
.modal h2 {
|
|
214
295
|
font-size: 18px;
|
|
@@ -217,7 +298,7 @@
|
|
|
217
298
|
.modal label {
|
|
218
299
|
display: block;
|
|
219
300
|
font-size: 13px;
|
|
220
|
-
color:
|
|
301
|
+
color: var(--text-secondary);
|
|
221
302
|
margin-bottom: 4px;
|
|
222
303
|
margin-top: 12px;
|
|
223
304
|
}
|
|
@@ -225,15 +306,26 @@
|
|
|
225
306
|
.modal select {
|
|
226
307
|
width: 100%;
|
|
227
308
|
padding: 10px 12px;
|
|
228
|
-
background:
|
|
229
|
-
border: 1px solid
|
|
309
|
+
background: var(--bg);
|
|
310
|
+
border: 1px solid var(--border);
|
|
230
311
|
border-radius: 8px;
|
|
231
|
-
color:
|
|
312
|
+
color: var(--text);
|
|
232
313
|
font-size: 15px;
|
|
233
314
|
outline: none;
|
|
315
|
+
-webkit-appearance: none;
|
|
316
|
+
appearance: none;
|
|
317
|
+
transition: background 0.3s, border-color 0.15s, color 0.3s;
|
|
234
318
|
}
|
|
235
|
-
.modal
|
|
236
|
-
|
|
319
|
+
.modal select {
|
|
320
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
|
321
|
+
background-repeat: no-repeat;
|
|
322
|
+
background-position: right 12px center;
|
|
323
|
+
padding-right: 32px;
|
|
324
|
+
cursor: pointer;
|
|
325
|
+
}
|
|
326
|
+
.modal input:focus,
|
|
327
|
+
.modal select:focus {
|
|
328
|
+
border-color: var(--accent);
|
|
237
329
|
}
|
|
238
330
|
.modal-actions {
|
|
239
331
|
display: flex;
|
|
@@ -248,14 +340,24 @@
|
|
|
248
340
|
font-size: 15px;
|
|
249
341
|
font-weight: 600;
|
|
250
342
|
cursor: pointer;
|
|
343
|
+
transition: background 0.15s, transform 0.1s;
|
|
344
|
+
}
|
|
345
|
+
.modal-actions button:active {
|
|
346
|
+
transform: scale(0.95);
|
|
251
347
|
}
|
|
252
348
|
.btn-cancel {
|
|
253
|
-
background:
|
|
254
|
-
color:
|
|
349
|
+
background: var(--border);
|
|
350
|
+
color: var(--text);
|
|
351
|
+
}
|
|
352
|
+
.btn-cancel:hover {
|
|
353
|
+
background: var(--border-subtle);
|
|
255
354
|
}
|
|
256
355
|
.btn-create {
|
|
257
|
-
background:
|
|
258
|
-
color:
|
|
356
|
+
background: var(--accent);
|
|
357
|
+
color: #ffffff;
|
|
358
|
+
}
|
|
359
|
+
.btn-create:hover {
|
|
360
|
+
background: var(--accent-hover);
|
|
259
361
|
}
|
|
260
362
|
|
|
261
363
|
/* Folder browser */
|
|
@@ -267,9 +369,9 @@
|
|
|
267
369
|
flex: 1;
|
|
268
370
|
}
|
|
269
371
|
.cwd-browse-btn {
|
|
270
|
-
background:
|
|
271
|
-
color:
|
|
272
|
-
border: 1px solid
|
|
372
|
+
background: var(--border);
|
|
373
|
+
color: var(--text);
|
|
374
|
+
border: 1px solid var(--border);
|
|
273
375
|
border-radius: 8px;
|
|
274
376
|
padding: 0 14px;
|
|
275
377
|
font-size: 18px;
|
|
@@ -277,9 +379,14 @@
|
|
|
277
379
|
flex-shrink: 0;
|
|
278
380
|
display: flex;
|
|
279
381
|
align-items: center;
|
|
382
|
+
transition: background 0.15s, border-color 0.15s;
|
|
383
|
+
}
|
|
384
|
+
.cwd-browse-btn:hover {
|
|
385
|
+
border-color: var(--accent);
|
|
280
386
|
}
|
|
281
387
|
.cwd-browse-btn:active {
|
|
282
|
-
background:
|
|
388
|
+
background: var(--accent);
|
|
389
|
+
color: #ffffff;
|
|
283
390
|
}
|
|
284
391
|
|
|
285
392
|
.browser-overlay {
|
|
@@ -289,7 +396,7 @@
|
|
|
289
396
|
left: 0;
|
|
290
397
|
right: 0;
|
|
291
398
|
bottom: 0;
|
|
292
|
-
background:
|
|
399
|
+
background: var(--overlay-bg);
|
|
293
400
|
z-index: 200;
|
|
294
401
|
justify-content: center;
|
|
295
402
|
align-items: flex-end;
|
|
@@ -298,7 +405,7 @@
|
|
|
298
405
|
display: flex;
|
|
299
406
|
}
|
|
300
407
|
.browser-sheet {
|
|
301
|
-
background:
|
|
408
|
+
background: var(--surface);
|
|
302
409
|
border-radius: 16px 16px 0 0;
|
|
303
410
|
width: 100%;
|
|
304
411
|
max-width: 500px;
|
|
@@ -306,10 +413,11 @@
|
|
|
306
413
|
display: flex;
|
|
307
414
|
flex-direction: column;
|
|
308
415
|
overflow: hidden;
|
|
416
|
+
transition: background 0.3s;
|
|
309
417
|
}
|
|
310
418
|
.browser-header {
|
|
311
419
|
padding: 16px 16px 12px;
|
|
312
|
-
border-bottom: 1px solid
|
|
420
|
+
border-bottom: 1px solid var(--border);
|
|
313
421
|
display: flex;
|
|
314
422
|
align-items: center;
|
|
315
423
|
justify-content: space-between;
|
|
@@ -321,14 +429,18 @@
|
|
|
321
429
|
.browser-close {
|
|
322
430
|
background: none;
|
|
323
431
|
border: none;
|
|
324
|
-
color:
|
|
432
|
+
color: var(--text-dim);
|
|
325
433
|
font-size: 24px;
|
|
326
434
|
cursor: pointer;
|
|
327
435
|
padding: 0 4px;
|
|
328
436
|
line-height: 1;
|
|
437
|
+
transition: color 0.15s;
|
|
438
|
+
}
|
|
439
|
+
.browser-close:hover {
|
|
440
|
+
color: var(--text);
|
|
329
441
|
}
|
|
330
442
|
.browser-close:active {
|
|
331
|
-
color:
|
|
443
|
+
color: var(--text);
|
|
332
444
|
}
|
|
333
445
|
|
|
334
446
|
.browser-breadcrumb {
|
|
@@ -339,31 +451,32 @@
|
|
|
339
451
|
font-size: 13px;
|
|
340
452
|
overflow-x: auto;
|
|
341
453
|
white-space: nowrap;
|
|
342
|
-
border-bottom: 1px solid
|
|
454
|
+
border-bottom: 1px solid var(--border);
|
|
343
455
|
flex-shrink: 0;
|
|
344
456
|
-webkit-overflow-scrolling: touch;
|
|
345
457
|
}
|
|
346
458
|
.crumb {
|
|
347
459
|
background: none;
|
|
348
460
|
border: none;
|
|
349
|
-
color:
|
|
461
|
+
color: var(--text-dim);
|
|
350
462
|
font-size: 13px;
|
|
351
463
|
cursor: pointer;
|
|
352
464
|
padding: 4px 6px;
|
|
353
465
|
border-radius: 4px;
|
|
354
466
|
flex-shrink: 0;
|
|
467
|
+
transition: background 0.15s, color 0.15s;
|
|
355
468
|
}
|
|
356
469
|
.crumb:active,
|
|
357
470
|
.crumb:hover {
|
|
358
|
-
background:
|
|
359
|
-
color:
|
|
471
|
+
background: var(--border);
|
|
472
|
+
color: var(--text);
|
|
360
473
|
}
|
|
361
474
|
.crumb.current {
|
|
362
|
-
color:
|
|
475
|
+
color: var(--text);
|
|
363
476
|
font-weight: 600;
|
|
364
477
|
}
|
|
365
478
|
.crumb-sep {
|
|
366
|
-
color:
|
|
479
|
+
color: var(--border-subtle);
|
|
367
480
|
flex-shrink: 0;
|
|
368
481
|
}
|
|
369
482
|
|
|
@@ -376,7 +489,7 @@
|
|
|
376
489
|
.browser-empty {
|
|
377
490
|
text-align: center;
|
|
378
491
|
padding: 40px 20px;
|
|
379
|
-
color:
|
|
492
|
+
color: var(--text-muted);
|
|
380
493
|
font-size: 14px;
|
|
381
494
|
}
|
|
382
495
|
.folder-item {
|
|
@@ -385,15 +498,15 @@
|
|
|
385
498
|
gap: 12px;
|
|
386
499
|
padding: 12px 16px;
|
|
387
500
|
cursor: pointer;
|
|
388
|
-
border-bottom: 1px solid rgba(
|
|
501
|
+
border-bottom: 1px solid rgba(60, 60, 60, 0.5);
|
|
389
502
|
transition: background 0.1s;
|
|
390
503
|
-webkit-tap-highlight-color: transparent;
|
|
391
504
|
}
|
|
392
505
|
.folder-item:active {
|
|
393
|
-
background: rgba(
|
|
506
|
+
background: rgba(0, 120, 212, 0.2);
|
|
394
507
|
}
|
|
395
508
|
.folder-item:hover {
|
|
396
|
-
background: rgba(
|
|
509
|
+
background: rgba(0, 120, 212, 0.1);
|
|
397
510
|
}
|
|
398
511
|
.folder-icon {
|
|
399
512
|
font-size: 22px;
|
|
@@ -403,28 +516,28 @@
|
|
|
403
516
|
}
|
|
404
517
|
.folder-name {
|
|
405
518
|
font-size: 15px;
|
|
406
|
-
color:
|
|
519
|
+
color: var(--text);
|
|
407
520
|
flex: 1;
|
|
408
521
|
overflow: hidden;
|
|
409
522
|
text-overflow: ellipsis;
|
|
410
523
|
white-space: nowrap;
|
|
411
524
|
}
|
|
412
525
|
.folder-arrow {
|
|
413
|
-
color:
|
|
526
|
+
color: var(--border-subtle);
|
|
414
527
|
font-size: 18px;
|
|
415
528
|
flex-shrink: 0;
|
|
416
529
|
}
|
|
417
530
|
|
|
418
531
|
.browser-footer {
|
|
419
532
|
padding: 12px 16px calc(env(safe-area-inset-bottom, 8px) + 8px);
|
|
420
|
-
border-top: 1px solid
|
|
533
|
+
border-top: 1px solid var(--border);
|
|
421
534
|
display: flex;
|
|
422
535
|
flex-direction: column;
|
|
423
536
|
gap: 8px;
|
|
424
537
|
}
|
|
425
538
|
.browser-current-path {
|
|
426
539
|
font-size: 12px;
|
|
427
|
-
color:
|
|
540
|
+
color: var(--text-dim);
|
|
428
541
|
overflow: hidden;
|
|
429
542
|
text-overflow: ellipsis;
|
|
430
543
|
white-space: nowrap;
|
|
@@ -432,23 +545,29 @@
|
|
|
432
545
|
.browser-select-btn {
|
|
433
546
|
width: 100%;
|
|
434
547
|
padding: 12px;
|
|
435
|
-
background:
|
|
436
|
-
color:
|
|
548
|
+
background: var(--accent);
|
|
549
|
+
color: #ffffff;
|
|
437
550
|
border: none;
|
|
438
551
|
border-radius: 10px;
|
|
439
552
|
font-size: 16px;
|
|
440
553
|
font-weight: 600;
|
|
441
554
|
cursor: pointer;
|
|
555
|
+
transition: background 0.15s, transform 0.1s;
|
|
556
|
+
}
|
|
557
|
+
.browser-select-btn:hover {
|
|
558
|
+
background: var(--accent-hover);
|
|
442
559
|
}
|
|
443
560
|
.browser-select-btn:active {
|
|
444
|
-
background:
|
|
561
|
+
background: var(--accent-active);
|
|
562
|
+
transform: scale(0.98);
|
|
445
563
|
}
|
|
446
564
|
</style>
|
|
447
565
|
</head>
|
|
448
566
|
<body>
|
|
449
567
|
<div class="header">
|
|
450
|
-
<h1>π‘ Term<span>
|
|
451
|
-
<p>Beam your terminal to any device Β· <span id="version" style="color:
|
|
568
|
+
<h1>π‘ Term<span>Beam</span></h1>
|
|
569
|
+
<p>Beam your terminal to any device Β· <span id="version" style="color: var(--accent)"></span></p>
|
|
570
|
+
<button class="theme-toggle" id="theme-toggle" title="Toggle theme"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg></button>
|
|
452
571
|
</div>
|
|
453
572
|
|
|
454
573
|
<div class="sessions-list" id="sessions-list"></div>
|
|
@@ -459,8 +578,12 @@
|
|
|
459
578
|
<h2>New Session</h2>
|
|
460
579
|
<label for="sess-name">Name</label>
|
|
461
580
|
<input type="text" id="sess-name" placeholder="My Session" />
|
|
462
|
-
<label for="sess-shell">Shell
|
|
463
|
-
<
|
|
581
|
+
<label for="sess-shell">Shell</label>
|
|
582
|
+
<select id="sess-shell">
|
|
583
|
+
<option value="">Loading shellsβ¦</option>
|
|
584
|
+
</select>
|
|
585
|
+
<label for="sess-cmd">Initial Command <span style="color:var(--text-muted);font-weight:normal">(optional)</span></label>
|
|
586
|
+
<input type="text" id="sess-cmd" placeholder="e.g. copilot, htop, vim" />
|
|
464
587
|
<label for="sess-cwd">Working Directory</label>
|
|
465
588
|
<div class="cwd-picker">
|
|
466
589
|
<input type="text" id="sess-cwd" placeholder="/Users/dorlugasigal" />
|
|
@@ -521,6 +644,23 @@
|
|
|
521
644
|
</div>
|
|
522
645
|
|
|
523
646
|
<script>
|
|
647
|
+
// Theme
|
|
648
|
+
function getTheme() { return localStorage.getItem('termbeam-theme') || 'dark'; }
|
|
649
|
+
function applyTheme(theme) {
|
|
650
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
651
|
+
document.querySelector('meta[name="theme-color"]').content =
|
|
652
|
+
theme === 'light' ? '#f3f3f3' : '#1e1e1e';
|
|
653
|
+
const btn = document.getElementById('theme-toggle');
|
|
654
|
+
if (btn) btn.innerHTML = theme === 'light'
|
|
655
|
+
? '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>'
|
|
656
|
+
: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>';
|
|
657
|
+
localStorage.setItem('termbeam-theme', theme);
|
|
658
|
+
}
|
|
659
|
+
applyTheme(getTheme());
|
|
660
|
+
document.getElementById('theme-toggle').addEventListener('click', () => {
|
|
661
|
+
applyTheme(getTheme() === 'light' ? 'dark' : 'light');
|
|
662
|
+
});
|
|
663
|
+
|
|
524
664
|
const listEl = document.getElementById('sessions-list');
|
|
525
665
|
const modal = document.getElementById('modal');
|
|
526
666
|
|
|
@@ -568,6 +708,7 @@
|
|
|
568
708
|
}
|
|
569
709
|
|
|
570
710
|
document.getElementById('new-session-btn').addEventListener('click', () => {
|
|
711
|
+
loadShells();
|
|
571
712
|
modal.classList.add('visible');
|
|
572
713
|
});
|
|
573
714
|
document.getElementById('modal-cancel').addEventListener('click', () => {
|
|
@@ -581,11 +722,13 @@
|
|
|
581
722
|
const name = document.getElementById('sess-name').value.trim();
|
|
582
723
|
const shell = document.getElementById('sess-shell').value.trim();
|
|
583
724
|
const cwd = document.getElementById('sess-cwd').value.trim();
|
|
725
|
+
const initialCommand = document.getElementById('sess-cmd').value.trim();
|
|
584
726
|
|
|
585
727
|
const body = {};
|
|
586
728
|
if (name) body.name = name;
|
|
587
729
|
if (shell) body.shell = shell;
|
|
588
730
|
if (cwd) body.cwd = cwd;
|
|
731
|
+
if (initialCommand) body.initialCommand = initialCommand;
|
|
589
732
|
|
|
590
733
|
const res = await fetch('/api/sessions', {
|
|
591
734
|
method: 'POST',
|
|
@@ -596,6 +739,30 @@
|
|
|
596
739
|
location.href = data.url;
|
|
597
740
|
});
|
|
598
741
|
|
|
742
|
+
// --- Shell detection ---
|
|
743
|
+
let shellsLoaded = false;
|
|
744
|
+
async function loadShells() {
|
|
745
|
+
if (shellsLoaded) return;
|
|
746
|
+
const shellSelect = document.getElementById('sess-shell');
|
|
747
|
+
try {
|
|
748
|
+
const res = await fetch('/api/shells');
|
|
749
|
+
const data = await res.json();
|
|
750
|
+
shellSelect.innerHTML = '';
|
|
751
|
+
for (const s of data.shells) {
|
|
752
|
+
const opt = document.createElement('option');
|
|
753
|
+
opt.value = s.cmd;
|
|
754
|
+
opt.textContent = `${s.name} (${s.cmd})`;
|
|
755
|
+
if (s.cmd === data.default || s.path === data.default) {
|
|
756
|
+
opt.selected = true;
|
|
757
|
+
}
|
|
758
|
+
shellSelect.appendChild(opt);
|
|
759
|
+
}
|
|
760
|
+
shellsLoaded = true;
|
|
761
|
+
} catch {
|
|
762
|
+
shellSelect.innerHTML = '<option value="">Could not detect shells</option>';
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
599
766
|
// --- Swipe to delete ---
|
|
600
767
|
async function deleteSession(id, e) {
|
|
601
768
|
e.stopPropagation();
|