termbeam 0.0.5 β 0.0.7
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 +4 -0
- package/package.json +1 -1
- package/public/icons/icon-192.png +0 -0
- package/public/icons/icon-512.png +0 -0
- package/public/icons/icon.svg +6 -0
- package/public/index.html +200 -68
- package/public/manifest.json +14 -0
- package/public/sw.js +54 -0
- package/public/terminal.html +393 -104
- package/src/cli.js +7 -2
- package/src/routes.js +4 -0
- package/src/server.js +11 -4
- package/src/tunnel.js +105 -13
package/README.md
CHANGED
|
@@ -164,3 +164,7 @@ Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for:
|
|
|
164
164
|
## π License
|
|
165
165
|
|
|
166
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
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
2
|
+
<rect width="512" height="512" rx="80" fill="#1e1e1e"/>
|
|
3
|
+
<rect x="32" y="32" width="448" height="448" rx="56" fill="#252526" stroke="#3c3c3c" stroke-width="4"/>
|
|
4
|
+
<polyline points="140,200 220,260 140,320" fill="none" stroke="#0078d4" stroke-width="32" stroke-linecap="round" stroke-linejoin="round"/>
|
|
5
|
+
<line x1="260" y1="320" x2="380" y2="320" stroke="#d4d4d4" stroke-width="28" stroke-linecap="round"/>
|
|
6
|
+
</svg>
|
package/public/index.html
CHANGED
|
@@ -8,9 +8,49 @@
|
|
|
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
|
+
<link rel="manifest" href="/manifest.json" />
|
|
13
|
+
<link rel="apple-touch-icon" href="/icons/icon-192.png" />
|
|
12
14
|
<title>TermBeam</title>
|
|
13
15
|
<style>
|
|
16
|
+
:root {
|
|
17
|
+
--bg: #1e1e1e;
|
|
18
|
+
--surface: #252526;
|
|
19
|
+
--border: #3c3c3c;
|
|
20
|
+
--border-subtle: #474747;
|
|
21
|
+
--text: #d4d4d4;
|
|
22
|
+
--text-secondary: #858585;
|
|
23
|
+
--text-dim: #6e6e6e;
|
|
24
|
+
--text-muted: #5a5a5a;
|
|
25
|
+
--accent: #0078d4;
|
|
26
|
+
--accent-hover: #1a8ae8;
|
|
27
|
+
--accent-active: #005a9e;
|
|
28
|
+
--danger: #f14c4c;
|
|
29
|
+
--danger-hover: #d73a3a;
|
|
30
|
+
--success: #89d185;
|
|
31
|
+
--info: #b0b0b0;
|
|
32
|
+
--shadow: rgba(0,0,0,0.15);
|
|
33
|
+
--overlay-bg: rgba(0,0,0,0.7);
|
|
34
|
+
}
|
|
35
|
+
[data-theme="light"] {
|
|
36
|
+
--bg: #ffffff;
|
|
37
|
+
--surface: #f3f3f3;
|
|
38
|
+
--border: #e0e0e0;
|
|
39
|
+
--border-subtle: #d0d0d0;
|
|
40
|
+
--text: #1e1e1e;
|
|
41
|
+
--text-secondary: #616161;
|
|
42
|
+
--text-dim: #767676;
|
|
43
|
+
--text-muted: #a0a0a0;
|
|
44
|
+
--accent: #0078d4;
|
|
45
|
+
--accent-hover: #106ebe;
|
|
46
|
+
--accent-active: #005a9e;
|
|
47
|
+
--danger: #e51400;
|
|
48
|
+
--danger-hover: #c20000;
|
|
49
|
+
--success: #16825d;
|
|
50
|
+
--info: #616161;
|
|
51
|
+
--shadow: rgba(0,0,0,0.06);
|
|
52
|
+
--overlay-bg: rgba(0,0,0,0.4);
|
|
53
|
+
}
|
|
14
54
|
* {
|
|
15
55
|
margin: 0;
|
|
16
56
|
padding: 0;
|
|
@@ -20,39 +60,66 @@
|
|
|
20
60
|
body {
|
|
21
61
|
height: 100%;
|
|
22
62
|
width: 100%;
|
|
23
|
-
background:
|
|
24
|
-
color:
|
|
63
|
+
background: var(--bg);
|
|
64
|
+
color: var(--text);
|
|
25
65
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
66
|
+
transition: background 0.3s, color 0.3s;
|
|
26
67
|
}
|
|
27
68
|
|
|
28
69
|
.header {
|
|
29
70
|
padding: 20px 16px 12px;
|
|
30
71
|
text-align: center;
|
|
31
|
-
border-bottom: 1px solid
|
|
72
|
+
border-bottom: 1px solid var(--border);
|
|
73
|
+
transition: border-color 0.3s;
|
|
74
|
+
position: relative;
|
|
32
75
|
}
|
|
33
76
|
.header h1 {
|
|
34
77
|
font-size: 22px;
|
|
35
78
|
font-weight: 700;
|
|
36
79
|
}
|
|
37
80
|
.header h1 span {
|
|
38
|
-
color:
|
|
81
|
+
color: var(--accent);
|
|
39
82
|
}
|
|
40
83
|
.header p {
|
|
41
84
|
font-size: 13px;
|
|
42
|
-
color:
|
|
85
|
+
color: var(--text-secondary);
|
|
43
86
|
margin-top: 4px;
|
|
44
87
|
}
|
|
88
|
+
.theme-toggle {
|
|
89
|
+
position: absolute;
|
|
90
|
+
top: 16px;
|
|
91
|
+
right: 16px;
|
|
92
|
+
background: none;
|
|
93
|
+
border: 1px solid var(--border);
|
|
94
|
+
color: var(--text-dim);
|
|
95
|
+
width: 32px;
|
|
96
|
+
height: 32px;
|
|
97
|
+
border-radius: 8px;
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
display: flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
justify-content: center;
|
|
102
|
+
font-size: 16px;
|
|
103
|
+
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
|
104
|
+
-webkit-tap-highlight-color: transparent;
|
|
105
|
+
}
|
|
106
|
+
.theme-toggle:hover {
|
|
107
|
+
color: var(--text);
|
|
108
|
+
border-color: var(--border-subtle);
|
|
109
|
+
background: var(--border);
|
|
110
|
+
}
|
|
45
111
|
|
|
46
112
|
.sessions-list {
|
|
47
113
|
padding: 16px;
|
|
114
|
+
padding-bottom: 80px;
|
|
48
115
|
display: flex;
|
|
49
116
|
flex-direction: column;
|
|
50
117
|
gap: 12px;
|
|
51
118
|
}
|
|
52
119
|
|
|
53
120
|
.session-card {
|
|
54
|
-
background:
|
|
55
|
-
border: 1px solid
|
|
121
|
+
background: var(--surface);
|
|
122
|
+
border: 1px solid var(--border);
|
|
56
123
|
border-radius: 12px;
|
|
57
124
|
padding: 16px;
|
|
58
125
|
display: flex;
|
|
@@ -60,14 +127,14 @@
|
|
|
60
127
|
gap: 8px;
|
|
61
128
|
text-decoration: none;
|
|
62
129
|
color: inherit;
|
|
63
|
-
transition: transform 0.2s ease;
|
|
130
|
+
transition: transform 0.2s ease, border-color 0.15s, background 0.3s;
|
|
64
131
|
cursor: pointer;
|
|
65
132
|
-webkit-tap-highlight-color: transparent;
|
|
66
133
|
position: relative;
|
|
67
134
|
z-index: 1;
|
|
68
135
|
}
|
|
69
136
|
.session-card:hover {
|
|
70
|
-
border-color:
|
|
137
|
+
border-color: var(--accent);
|
|
71
138
|
}
|
|
72
139
|
|
|
73
140
|
.swipe-wrap {
|
|
@@ -81,7 +148,7 @@
|
|
|
81
148
|
top: 0;
|
|
82
149
|
bottom: 0;
|
|
83
150
|
width: 80px;
|
|
84
|
-
background:
|
|
151
|
+
background: var(--danger);
|
|
85
152
|
display: flex;
|
|
86
153
|
align-items: center;
|
|
87
154
|
justify-content: center;
|
|
@@ -123,22 +190,23 @@
|
|
|
123
190
|
width: 10px;
|
|
124
191
|
height: 10px;
|
|
125
192
|
border-radius: 50%;
|
|
126
|
-
background:
|
|
193
|
+
background: var(--success);
|
|
127
194
|
flex-shrink: 0;
|
|
128
195
|
}
|
|
129
196
|
.session-card .pid {
|
|
130
197
|
font-size: 12px;
|
|
131
|
-
color:
|
|
132
|
-
background:
|
|
198
|
+
color: var(--text-secondary);
|
|
199
|
+
background: var(--bg);
|
|
133
200
|
padding: 2px 8px;
|
|
134
201
|
border-radius: 4px;
|
|
202
|
+
transition: background 0.3s, color 0.3s;
|
|
135
203
|
}
|
|
136
204
|
.session-card .details {
|
|
137
205
|
display: flex;
|
|
138
206
|
flex-wrap: wrap;
|
|
139
207
|
gap: 6px 16px;
|
|
140
208
|
font-size: 13px;
|
|
141
|
-
color:
|
|
209
|
+
color: var(--info);
|
|
142
210
|
}
|
|
143
211
|
.session-card .details span {
|
|
144
212
|
display: flex;
|
|
@@ -147,43 +215,57 @@
|
|
|
147
215
|
}
|
|
148
216
|
.session-card .connect-btn {
|
|
149
217
|
align-self: flex-end;
|
|
150
|
-
background:
|
|
151
|
-
color:
|
|
218
|
+
background: var(--accent);
|
|
219
|
+
color: #ffffff;
|
|
152
220
|
border: none;
|
|
153
221
|
border-radius: 8px;
|
|
154
222
|
padding: 8px 20px;
|
|
155
223
|
font-size: 14px;
|
|
156
224
|
font-weight: 600;
|
|
157
225
|
cursor: pointer;
|
|
226
|
+
transition: background 0.15s, transform 0.1s;
|
|
227
|
+
}
|
|
228
|
+
.session-card .connect-btn:hover {
|
|
229
|
+
background: var(--accent-hover);
|
|
158
230
|
}
|
|
159
231
|
.session-card .connect-btn:active {
|
|
160
|
-
background:
|
|
232
|
+
background: var(--accent-active);
|
|
233
|
+
transform: scale(0.95);
|
|
161
234
|
}
|
|
162
235
|
|
|
163
236
|
.new-session {
|
|
164
|
-
|
|
237
|
+
position: fixed;
|
|
238
|
+
bottom: 16px;
|
|
239
|
+
left: 16px;
|
|
240
|
+
right: 16px;
|
|
165
241
|
padding: 14px;
|
|
166
|
-
background:
|
|
167
|
-
|
|
242
|
+
background: var(--accent);
|
|
243
|
+
color: #ffffff;
|
|
244
|
+
border: none;
|
|
168
245
|
border-radius: 12px;
|
|
169
|
-
color: #888;
|
|
170
246
|
font-size: 15px;
|
|
171
247
|
font-weight: 600;
|
|
172
248
|
cursor: pointer;
|
|
173
249
|
text-align: center;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
250
|
+
z-index: 50;
|
|
251
|
+
transition: background 0.15s, transform 0.1s, box-shadow 0.15s;
|
|
252
|
+
box-shadow: 0 2px 8px rgba(0, 120, 212, 0.3);
|
|
253
|
+
padding-bottom: calc(14px + env(safe-area-inset-bottom, 0px));
|
|
254
|
+
}
|
|
255
|
+
.new-session:hover {
|
|
256
|
+
background: var(--accent-hover);
|
|
257
|
+
box-shadow: 0 4px 12px rgba(0, 120, 212, 0.4);
|
|
177
258
|
}
|
|
178
259
|
.new-session:active {
|
|
179
|
-
|
|
180
|
-
|
|
260
|
+
background: var(--accent-active);
|
|
261
|
+
transform: scale(0.98);
|
|
262
|
+
box-shadow: 0 1px 4px rgba(0, 120, 212, 0.2);
|
|
181
263
|
}
|
|
182
264
|
|
|
183
265
|
.empty-state {
|
|
184
266
|
text-align: center;
|
|
185
267
|
padding: 60px 20px;
|
|
186
|
-
color:
|
|
268
|
+
color: var(--text-muted);
|
|
187
269
|
font-size: 15px;
|
|
188
270
|
}
|
|
189
271
|
|
|
@@ -195,7 +277,7 @@
|
|
|
195
277
|
left: 0;
|
|
196
278
|
right: 0;
|
|
197
279
|
bottom: 0;
|
|
198
|
-
background:
|
|
280
|
+
background: var(--overlay-bg);
|
|
199
281
|
z-index: 100;
|
|
200
282
|
justify-content: center;
|
|
201
283
|
align-items: center;
|
|
@@ -204,11 +286,12 @@
|
|
|
204
286
|
display: flex;
|
|
205
287
|
}
|
|
206
288
|
.modal {
|
|
207
|
-
background:
|
|
289
|
+
background: var(--surface);
|
|
208
290
|
border-radius: 16px;
|
|
209
291
|
width: 90%;
|
|
210
292
|
max-width: 500px;
|
|
211
293
|
padding: 24px 20px;
|
|
294
|
+
transition: background 0.3s;
|
|
212
295
|
}
|
|
213
296
|
.modal h2 {
|
|
214
297
|
font-size: 18px;
|
|
@@ -217,7 +300,7 @@
|
|
|
217
300
|
.modal label {
|
|
218
301
|
display: block;
|
|
219
302
|
font-size: 13px;
|
|
220
|
-
color:
|
|
303
|
+
color: var(--text-secondary);
|
|
221
304
|
margin-bottom: 4px;
|
|
222
305
|
margin-top: 12px;
|
|
223
306
|
}
|
|
@@ -225,14 +308,15 @@
|
|
|
225
308
|
.modal select {
|
|
226
309
|
width: 100%;
|
|
227
310
|
padding: 10px 12px;
|
|
228
|
-
background:
|
|
229
|
-
border: 1px solid
|
|
311
|
+
background: var(--bg);
|
|
312
|
+
border: 1px solid var(--border);
|
|
230
313
|
border-radius: 8px;
|
|
231
|
-
color:
|
|
314
|
+
color: var(--text);
|
|
232
315
|
font-size: 15px;
|
|
233
316
|
outline: none;
|
|
234
317
|
-webkit-appearance: none;
|
|
235
318
|
appearance: none;
|
|
319
|
+
transition: background 0.3s, border-color 0.15s, color 0.3s;
|
|
236
320
|
}
|
|
237
321
|
.modal select {
|
|
238
322
|
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");
|
|
@@ -243,7 +327,7 @@
|
|
|
243
327
|
}
|
|
244
328
|
.modal input:focus,
|
|
245
329
|
.modal select:focus {
|
|
246
|
-
border-color:
|
|
330
|
+
border-color: var(--accent);
|
|
247
331
|
}
|
|
248
332
|
.modal-actions {
|
|
249
333
|
display: flex;
|
|
@@ -258,14 +342,24 @@
|
|
|
258
342
|
font-size: 15px;
|
|
259
343
|
font-weight: 600;
|
|
260
344
|
cursor: pointer;
|
|
345
|
+
transition: background 0.15s, transform 0.1s;
|
|
346
|
+
}
|
|
347
|
+
.modal-actions button:active {
|
|
348
|
+
transform: scale(0.95);
|
|
261
349
|
}
|
|
262
350
|
.btn-cancel {
|
|
263
|
-
background:
|
|
264
|
-
color:
|
|
351
|
+
background: var(--border);
|
|
352
|
+
color: var(--text);
|
|
353
|
+
}
|
|
354
|
+
.btn-cancel:hover {
|
|
355
|
+
background: var(--border-subtle);
|
|
265
356
|
}
|
|
266
357
|
.btn-create {
|
|
267
|
-
background:
|
|
268
|
-
color:
|
|
358
|
+
background: var(--accent);
|
|
359
|
+
color: #ffffff;
|
|
360
|
+
}
|
|
361
|
+
.btn-create:hover {
|
|
362
|
+
background: var(--accent-hover);
|
|
269
363
|
}
|
|
270
364
|
|
|
271
365
|
/* Folder browser */
|
|
@@ -277,9 +371,9 @@
|
|
|
277
371
|
flex: 1;
|
|
278
372
|
}
|
|
279
373
|
.cwd-browse-btn {
|
|
280
|
-
background:
|
|
281
|
-
color:
|
|
282
|
-
border: 1px solid
|
|
374
|
+
background: var(--border);
|
|
375
|
+
color: var(--text);
|
|
376
|
+
border: 1px solid var(--border);
|
|
283
377
|
border-radius: 8px;
|
|
284
378
|
padding: 0 14px;
|
|
285
379
|
font-size: 18px;
|
|
@@ -287,9 +381,14 @@
|
|
|
287
381
|
flex-shrink: 0;
|
|
288
382
|
display: flex;
|
|
289
383
|
align-items: center;
|
|
384
|
+
transition: background 0.15s, border-color 0.15s;
|
|
385
|
+
}
|
|
386
|
+
.cwd-browse-btn:hover {
|
|
387
|
+
border-color: var(--accent);
|
|
290
388
|
}
|
|
291
389
|
.cwd-browse-btn:active {
|
|
292
|
-
background:
|
|
390
|
+
background: var(--accent);
|
|
391
|
+
color: #ffffff;
|
|
293
392
|
}
|
|
294
393
|
|
|
295
394
|
.browser-overlay {
|
|
@@ -299,7 +398,7 @@
|
|
|
299
398
|
left: 0;
|
|
300
399
|
right: 0;
|
|
301
400
|
bottom: 0;
|
|
302
|
-
background:
|
|
401
|
+
background: var(--overlay-bg);
|
|
303
402
|
z-index: 200;
|
|
304
403
|
justify-content: center;
|
|
305
404
|
align-items: flex-end;
|
|
@@ -308,7 +407,7 @@
|
|
|
308
407
|
display: flex;
|
|
309
408
|
}
|
|
310
409
|
.browser-sheet {
|
|
311
|
-
background:
|
|
410
|
+
background: var(--surface);
|
|
312
411
|
border-radius: 16px 16px 0 0;
|
|
313
412
|
width: 100%;
|
|
314
413
|
max-width: 500px;
|
|
@@ -316,10 +415,11 @@
|
|
|
316
415
|
display: flex;
|
|
317
416
|
flex-direction: column;
|
|
318
417
|
overflow: hidden;
|
|
418
|
+
transition: background 0.3s;
|
|
319
419
|
}
|
|
320
420
|
.browser-header {
|
|
321
421
|
padding: 16px 16px 12px;
|
|
322
|
-
border-bottom: 1px solid
|
|
422
|
+
border-bottom: 1px solid var(--border);
|
|
323
423
|
display: flex;
|
|
324
424
|
align-items: center;
|
|
325
425
|
justify-content: space-between;
|
|
@@ -331,14 +431,18 @@
|
|
|
331
431
|
.browser-close {
|
|
332
432
|
background: none;
|
|
333
433
|
border: none;
|
|
334
|
-
color:
|
|
434
|
+
color: var(--text-dim);
|
|
335
435
|
font-size: 24px;
|
|
336
436
|
cursor: pointer;
|
|
337
437
|
padding: 0 4px;
|
|
338
438
|
line-height: 1;
|
|
439
|
+
transition: color 0.15s;
|
|
440
|
+
}
|
|
441
|
+
.browser-close:hover {
|
|
442
|
+
color: var(--text);
|
|
339
443
|
}
|
|
340
444
|
.browser-close:active {
|
|
341
|
-
color:
|
|
445
|
+
color: var(--text);
|
|
342
446
|
}
|
|
343
447
|
|
|
344
448
|
.browser-breadcrumb {
|
|
@@ -349,31 +453,32 @@
|
|
|
349
453
|
font-size: 13px;
|
|
350
454
|
overflow-x: auto;
|
|
351
455
|
white-space: nowrap;
|
|
352
|
-
border-bottom: 1px solid
|
|
456
|
+
border-bottom: 1px solid var(--border);
|
|
353
457
|
flex-shrink: 0;
|
|
354
458
|
-webkit-overflow-scrolling: touch;
|
|
355
459
|
}
|
|
356
460
|
.crumb {
|
|
357
461
|
background: none;
|
|
358
462
|
border: none;
|
|
359
|
-
color:
|
|
463
|
+
color: var(--text-dim);
|
|
360
464
|
font-size: 13px;
|
|
361
465
|
cursor: pointer;
|
|
362
466
|
padding: 4px 6px;
|
|
363
467
|
border-radius: 4px;
|
|
364
468
|
flex-shrink: 0;
|
|
469
|
+
transition: background 0.15s, color 0.15s;
|
|
365
470
|
}
|
|
366
471
|
.crumb:active,
|
|
367
472
|
.crumb:hover {
|
|
368
|
-
background:
|
|
369
|
-
color:
|
|
473
|
+
background: var(--border);
|
|
474
|
+
color: var(--text);
|
|
370
475
|
}
|
|
371
476
|
.crumb.current {
|
|
372
|
-
color:
|
|
477
|
+
color: var(--text);
|
|
373
478
|
font-weight: 600;
|
|
374
479
|
}
|
|
375
480
|
.crumb-sep {
|
|
376
|
-
color:
|
|
481
|
+
color: var(--border-subtle);
|
|
377
482
|
flex-shrink: 0;
|
|
378
483
|
}
|
|
379
484
|
|
|
@@ -386,7 +491,7 @@
|
|
|
386
491
|
.browser-empty {
|
|
387
492
|
text-align: center;
|
|
388
493
|
padding: 40px 20px;
|
|
389
|
-
color:
|
|
494
|
+
color: var(--text-muted);
|
|
390
495
|
font-size: 14px;
|
|
391
496
|
}
|
|
392
497
|
.folder-item {
|
|
@@ -395,15 +500,15 @@
|
|
|
395
500
|
gap: 12px;
|
|
396
501
|
padding: 12px 16px;
|
|
397
502
|
cursor: pointer;
|
|
398
|
-
border-bottom: 1px solid rgba(
|
|
503
|
+
border-bottom: 1px solid rgba(60, 60, 60, 0.5);
|
|
399
504
|
transition: background 0.1s;
|
|
400
505
|
-webkit-tap-highlight-color: transparent;
|
|
401
506
|
}
|
|
402
507
|
.folder-item:active {
|
|
403
|
-
background: rgba(
|
|
508
|
+
background: rgba(0, 120, 212, 0.2);
|
|
404
509
|
}
|
|
405
510
|
.folder-item:hover {
|
|
406
|
-
background: rgba(
|
|
511
|
+
background: rgba(0, 120, 212, 0.1);
|
|
407
512
|
}
|
|
408
513
|
.folder-icon {
|
|
409
514
|
font-size: 22px;
|
|
@@ -413,28 +518,28 @@
|
|
|
413
518
|
}
|
|
414
519
|
.folder-name {
|
|
415
520
|
font-size: 15px;
|
|
416
|
-
color:
|
|
521
|
+
color: var(--text);
|
|
417
522
|
flex: 1;
|
|
418
523
|
overflow: hidden;
|
|
419
524
|
text-overflow: ellipsis;
|
|
420
525
|
white-space: nowrap;
|
|
421
526
|
}
|
|
422
527
|
.folder-arrow {
|
|
423
|
-
color:
|
|
528
|
+
color: var(--border-subtle);
|
|
424
529
|
font-size: 18px;
|
|
425
530
|
flex-shrink: 0;
|
|
426
531
|
}
|
|
427
532
|
|
|
428
533
|
.browser-footer {
|
|
429
534
|
padding: 12px 16px calc(env(safe-area-inset-bottom, 8px) + 8px);
|
|
430
|
-
border-top: 1px solid
|
|
535
|
+
border-top: 1px solid var(--border);
|
|
431
536
|
display: flex;
|
|
432
537
|
flex-direction: column;
|
|
433
538
|
gap: 8px;
|
|
434
539
|
}
|
|
435
540
|
.browser-current-path {
|
|
436
541
|
font-size: 12px;
|
|
437
|
-
color:
|
|
542
|
+
color: var(--text-dim);
|
|
438
543
|
overflow: hidden;
|
|
439
544
|
text-overflow: ellipsis;
|
|
440
545
|
white-space: nowrap;
|
|
@@ -442,23 +547,29 @@
|
|
|
442
547
|
.browser-select-btn {
|
|
443
548
|
width: 100%;
|
|
444
549
|
padding: 12px;
|
|
445
|
-
background:
|
|
446
|
-
color:
|
|
550
|
+
background: var(--accent);
|
|
551
|
+
color: #ffffff;
|
|
447
552
|
border: none;
|
|
448
553
|
border-radius: 10px;
|
|
449
554
|
font-size: 16px;
|
|
450
555
|
font-weight: 600;
|
|
451
556
|
cursor: pointer;
|
|
557
|
+
transition: background 0.15s, transform 0.1s;
|
|
558
|
+
}
|
|
559
|
+
.browser-select-btn:hover {
|
|
560
|
+
background: var(--accent-hover);
|
|
452
561
|
}
|
|
453
562
|
.browser-select-btn:active {
|
|
454
|
-
background:
|
|
563
|
+
background: var(--accent-active);
|
|
564
|
+
transform: scale(0.98);
|
|
455
565
|
}
|
|
456
566
|
</style>
|
|
457
567
|
</head>
|
|
458
568
|
<body>
|
|
459
569
|
<div class="header">
|
|
460
|
-
<h1>π‘ Term<span>
|
|
461
|
-
<p>Beam your terminal to any device Β· <span id="version" style="color:
|
|
570
|
+
<h1>π‘ Term<span>Beam</span></h1>
|
|
571
|
+
<p>Beam your terminal to any device Β· <span id="version" style="color: var(--accent)"></span></p>
|
|
572
|
+
<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>
|
|
462
573
|
</div>
|
|
463
574
|
|
|
464
575
|
<div class="sessions-list" id="sessions-list"></div>
|
|
@@ -473,7 +584,7 @@
|
|
|
473
584
|
<select id="sess-shell">
|
|
474
585
|
<option value="">Loading shellsβ¦</option>
|
|
475
586
|
</select>
|
|
476
|
-
<label for="sess-cmd">Initial Command <span style="color
|
|
587
|
+
<label for="sess-cmd">Initial Command <span style="color:var(--text-muted);font-weight:normal">(optional)</span></label>
|
|
477
588
|
<input type="text" id="sess-cmd" placeholder="e.g. copilot, htop, vim" />
|
|
478
589
|
<label for="sess-cwd">Working Directory</label>
|
|
479
590
|
<div class="cwd-picker">
|
|
@@ -535,6 +646,23 @@
|
|
|
535
646
|
</div>
|
|
536
647
|
|
|
537
648
|
<script>
|
|
649
|
+
// Theme
|
|
650
|
+
function getTheme() { return localStorage.getItem('termbeam-theme') || 'dark'; }
|
|
651
|
+
function applyTheme(theme) {
|
|
652
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
653
|
+
document.querySelector('meta[name="theme-color"]').content =
|
|
654
|
+
theme === 'light' ? '#f3f3f3' : '#1e1e1e';
|
|
655
|
+
const btn = document.getElementById('theme-toggle');
|
|
656
|
+
if (btn) btn.innerHTML = theme === 'light'
|
|
657
|
+
? '<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>'
|
|
658
|
+
: '<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>';
|
|
659
|
+
localStorage.setItem('termbeam-theme', theme);
|
|
660
|
+
}
|
|
661
|
+
applyTheme(getTheme());
|
|
662
|
+
document.getElementById('theme-toggle').addEventListener('click', () => {
|
|
663
|
+
applyTheme(getTheme() === 'light' ? 'dark' : 'light');
|
|
664
|
+
});
|
|
665
|
+
|
|
538
666
|
const listEl = document.getElementById('sessions-list');
|
|
539
667
|
const modal = document.getElementById('modal');
|
|
540
668
|
|
|
@@ -815,6 +943,10 @@
|
|
|
815
943
|
|
|
816
944
|
loadSessions();
|
|
817
945
|
setInterval(loadSessions, 3000);
|
|
946
|
+
|
|
947
|
+
if ('serviceWorker' in navigator) {
|
|
948
|
+
navigator.serviceWorker.register('/sw.js').catch(() => {});
|
|
949
|
+
}
|
|
818
950
|
</script>
|
|
819
951
|
</body>
|
|
820
952
|
</html>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "TermBeam",
|
|
3
|
+
"short_name": "TermBeam",
|
|
4
|
+
"description": "Beam your terminal to any device",
|
|
5
|
+
"start_url": "/",
|
|
6
|
+
"display": "standalone",
|
|
7
|
+
"background_color": "#1e1e1e",
|
|
8
|
+
"theme_color": "#1e1e1e",
|
|
9
|
+
"icons": [
|
|
10
|
+
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
|
|
11
|
+
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
|
|
12
|
+
{ "src": "/icons/icon.svg", "sizes": "any", "type": "image/svg+xml" }
|
|
13
|
+
]
|
|
14
|
+
}
|
package/public/sw.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const CACHE_NAME = 'termbeam-v1';
|
|
2
|
+
const SHELL_URLS = ['/', '/terminal'];
|
|
3
|
+
|
|
4
|
+
self.addEventListener('install', (event) => {
|
|
5
|
+
event.waitUntil(
|
|
6
|
+
caches.open(CACHE_NAME).then((cache) => cache.addAll(SHELL_URLS))
|
|
7
|
+
);
|
|
8
|
+
self.skipWaiting();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
self.addEventListener('activate', (event) => {
|
|
12
|
+
event.waitUntil(
|
|
13
|
+
caches.keys().then((keys) =>
|
|
14
|
+
Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)))
|
|
15
|
+
)
|
|
16
|
+
);
|
|
17
|
+
self.clients.claim();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
self.addEventListener('fetch', (event) => {
|
|
21
|
+
const url = new URL(event.request.url);
|
|
22
|
+
|
|
23
|
+
// Don't cache WebSocket upgrades or external resources
|
|
24
|
+
if (
|
|
25
|
+
event.request.mode === 'websocket' ||
|
|
26
|
+
url.protocol === 'ws:' ||
|
|
27
|
+
url.protocol === 'wss:' ||
|
|
28
|
+
url.origin !== self.location.origin
|
|
29
|
+
) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Network-first for API calls
|
|
34
|
+
if (url.pathname.startsWith('/api/')) {
|
|
35
|
+
event.respondWith(
|
|
36
|
+
fetch(event.request).catch(() => caches.match(event.request))
|
|
37
|
+
);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Cache-first for static assets
|
|
42
|
+
event.respondWith(
|
|
43
|
+
caches.match(event.request).then((cached) => {
|
|
44
|
+
if (cached) return cached;
|
|
45
|
+
return fetch(event.request).then((response) => {
|
|
46
|
+
if (response.ok) {
|
|
47
|
+
const clone = response.clone();
|
|
48
|
+
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
|
|
49
|
+
}
|
|
50
|
+
return response;
|
|
51
|
+
});
|
|
52
|
+
})
|
|
53
|
+
);
|
|
54
|
+
});
|