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 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.4",
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.0.0",
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="#1a1a2e" />
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: #1a1a2e;
24
- color: #e0e0e0;
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 #0f3460;
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: #533483;
79
+ color: var(--accent);
39
80
  }
40
81
  .header p {
41
82
  font-size: 13px;
42
- color: #888;
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: #16213e;
55
- border: 1px solid #0f3460;
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: #533483;
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: #e74c3c;
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: #2ecc71;
191
+ background: var(--success);
127
192
  flex-shrink: 0;
128
193
  }
129
194
  .session-card .pid {
130
195
  font-size: 12px;
131
- color: #888;
132
- background: #1a1a2e;
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: #aaa;
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: #533483;
151
- color: white;
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: #6a42a8;
230
+ background: var(--accent-active);
231
+ transform: scale(0.95);
161
232
  }
162
233
 
163
234
  .new-session {
164
- margin: 0 16px;
235
+ position: fixed;
236
+ bottom: 16px;
237
+ left: 16px;
238
+ right: 16px;
165
239
  padding: 14px;
166
- background: transparent;
167
- border: 2px dashed #0f3460;
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
- transition:
175
- border-color 0.15s,
176
- color 0.15s;
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
- border-color: #533483;
180
- color: #e0e0e0;
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: #666;
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: rgba(0, 0, 0, 0.7);
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: #16213e;
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: #aaa;
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: #1a1a2e;
229
- border: 1px solid #0f3460;
309
+ background: var(--bg);
310
+ border: 1px solid var(--border);
230
311
  border-radius: 8px;
231
- color: #e0e0e0;
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 input:focus {
236
- border-color: #533483;
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: #0f3460;
254
- color: #e0e0e0;
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: #533483;
258
- color: white;
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: #0f3460;
271
- color: #e0e0e0;
272
- border: 1px solid #0f3460;
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: #533483;
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: rgba(0, 0, 0, 0.8);
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: #16213e;
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 #0f3460;
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: #888;
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: #e0e0e0;
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 #0f3460;
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: #888;
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: #0f3460;
359
- color: #e0e0e0;
471
+ background: var(--border);
472
+ color: var(--text);
360
473
  }
361
474
  .crumb.current {
362
- color: #e0e0e0;
475
+ color: var(--text);
363
476
  font-weight: 600;
364
477
  }
365
478
  .crumb-sep {
366
- color: #444;
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: #666;
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(15, 52, 96, 0.5);
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(83, 52, 131, 0.3);
506
+ background: rgba(0, 120, 212, 0.2);
394
507
  }
395
508
  .folder-item:hover {
396
- background: rgba(83, 52, 131, 0.15);
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: #e0e0e0;
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: #444;
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 #0f3460;
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: #888;
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: #533483;
436
- color: white;
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: #6a42a8;
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>Cast</span></h1>
451
- <p>Beam your terminal to any device Β· <span id="version" style="color: #533483"></span></p>
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 / Command</label>
463
- <input type="text" id="sess-shell" placeholder="/bin/zsh" />
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();