termbeam 1.3.0 → 1.4.0

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
@@ -1,17 +1,23 @@
1
+ <div align="center">
2
+
1
3
  # TermBeam
2
4
 
3
5
  **Beam your terminal to any device.**
4
6
 
5
7
  [![npm version](https://img.shields.io/npm/v/termbeam.svg)](https://www.npmjs.com/package/termbeam)
8
+ [![npm downloads](https://img.shields.io/npm/dm/termbeam.svg)](https://www.npmjs.com/package/termbeam)
6
9
  [![CI](https://github.com/dorlugasigal/TermBeam/actions/workflows/ci.yml/badge.svg)](https://github.com/dorlugasigal/TermBeam/actions/workflows/ci.yml)
7
10
  [![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/dorlugasigal/TermBeam/coverage-data/endpoint.json)](https://github.com/dorlugasigal/TermBeam/actions/workflows/ci.yml)
11
+ [![Node.js](https://img.shields.io/node/v/termbeam.svg)](https://nodejs.org/)
8
12
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
13
 
14
+ </div>
15
+
10
16
  TermBeam lets you access your terminal from a phone, tablet, or any browser — no SSH, no port forwarding, no config files. Run one command and scan the QR code.
11
17
 
12
18
  I built this because I kept needing to run quick commands on my dev machine while away from my desk, and SSH on a phone is painful. TermBeam gives you a real terminal with a touch-friendly UI that actually works on small screens.
13
19
 
14
- [Full documentation](https://dorlugasigal.github.io/TermBeam/)
20
+ [Full documentation](https://dorlugasigal.github.io/TermBeam/) · [Website](https://termbeam.pages.dev)
15
21
 
16
22
  https://github.com/user-attachments/assets/9dd4f3d7-f017-4314-9b3a-f6a5688e3671
17
23
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termbeam",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
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": {
@@ -38,7 +38,7 @@
38
38
  ],
39
39
  "author": "Dor Lugasi <dorlugasigal@gmail.com>",
40
40
  "license": "MIT",
41
- "homepage": "https://github.com/dorlugasigal/TermBeam",
41
+ "homepage": "https://termbeam.pages.dev",
42
42
  "repository": {
43
43
  "type": "git",
44
44
  "url": "https://github.com/dorlugasigal/TermBeam.git"
package/public/index.html CHANGED
@@ -55,6 +55,196 @@
55
55
  --shadow: rgba(0, 0, 0, 0.06);
56
56
  --overlay-bg: rgba(0, 0, 0, 0.4);
57
57
  }
58
+ [data-theme='monokai'] {
59
+ --bg: #272822;
60
+ --surface: #1e1f1c;
61
+ --border: #49483e;
62
+ --border-subtle: #5c5c4f;
63
+ --text: #f8f8f2;
64
+ --text-secondary: #a59f85;
65
+ --text-dim: #75715e;
66
+ --text-muted: #5a5854;
67
+ --accent: #a6e22e;
68
+ --accent-hover: #b8f53c;
69
+ --accent-active: #8acc16;
70
+ --danger: #f92672;
71
+ --danger-hover: #e0155d;
72
+ --success: #a6e22e;
73
+ --info: #a59f85;
74
+ --shadow: rgba(0, 0, 0, 0.3);
75
+ --overlay-bg: rgba(0, 0, 0, 0.75);
76
+ }
77
+ [data-theme='solarized-dark'] {
78
+ --bg: #002b36;
79
+ --surface: #073642;
80
+ --border: #586e75;
81
+ --border-subtle: #657b83;
82
+ --text: #839496;
83
+ --text-secondary: #657b83;
84
+ --text-dim: #586e75;
85
+ --text-muted: #4a5a62;
86
+ --accent: #268bd2;
87
+ --accent-hover: #379ce3;
88
+ --accent-active: #1a7abf;
89
+ --danger: #dc322f;
90
+ --danger-hover: #c8221f;
91
+ --success: #859900;
92
+ --info: #657b83;
93
+ --shadow: rgba(0, 0, 0, 0.25);
94
+ --overlay-bg: rgba(0, 0, 0, 0.75);
95
+ }
96
+ [data-theme='solarized-light'] {
97
+ --bg: #fdf6e3;
98
+ --surface: #eee8d5;
99
+ --border: #93a1a1;
100
+ --border-subtle: #839496;
101
+ --text: #657b83;
102
+ --text-secondary: #93a1a1;
103
+ --text-dim: #a0a0a0;
104
+ --text-muted: #b0b0b0;
105
+ --accent: #268bd2;
106
+ --accent-hover: #379ce3;
107
+ --accent-active: #1a7abf;
108
+ --danger: #dc322f;
109
+ --danger-hover: #c8221f;
110
+ --success: #859900;
111
+ --info: #93a1a1;
112
+ --shadow: rgba(0, 0, 0, 0.08);
113
+ --overlay-bg: rgba(0, 0, 0, 0.4);
114
+ }
115
+ [data-theme='nord'] {
116
+ --bg: #2e3440;
117
+ --surface: #3b4252;
118
+ --border: #434c5e;
119
+ --border-subtle: #4c566a;
120
+ --text: #d8dee9;
121
+ --text-secondary: #b0bac9;
122
+ --text-dim: #7b88a1;
123
+ --text-muted: #5c6a85;
124
+ --accent: #88c0d0;
125
+ --accent-hover: #9fd4e4;
126
+ --accent-active: #6aafbf;
127
+ --danger: #bf616a;
128
+ --danger-hover: #a84d57;
129
+ --success: #a3be8c;
130
+ --info: #b0bac9;
131
+ --shadow: rgba(0, 0, 0, 0.2);
132
+ --overlay-bg: rgba(0, 0, 0, 0.75);
133
+ }
134
+ [data-theme='dracula'] {
135
+ --bg: #282a36;
136
+ --surface: #343746;
137
+ --border: #44475a;
138
+ --border-subtle: #525568;
139
+ --text: #f8f8f2;
140
+ --text-secondary: #c1c4d2;
141
+ --text-dim: #8e92a4;
142
+ --text-muted: #6272a4;
143
+ --accent: #bd93f9;
144
+ --accent-hover: #d0b0ff;
145
+ --accent-active: #a77de7;
146
+ --danger: #ff5555;
147
+ --danger-hover: #e03d3d;
148
+ --success: #50fa7b;
149
+ --info: #c1c4d2;
150
+ --shadow: rgba(0, 0, 0, 0.25);
151
+ --overlay-bg: rgba(0, 0, 0, 0.75);
152
+ }
153
+ [data-theme='github-dark'] {
154
+ --bg: #0d1117;
155
+ --surface: #161b22;
156
+ --border: #30363d;
157
+ --border-subtle: #3d444d;
158
+ --text: #c9d1d9;
159
+ --text-secondary: #8b949e;
160
+ --text-dim: #6e7681;
161
+ --text-muted: #484f58;
162
+ --accent: #58a6ff;
163
+ --accent-hover: #79b8ff;
164
+ --accent-active: #388bfd;
165
+ --danger: #f85149;
166
+ --danger-hover: #da3633;
167
+ --success: #3fb950;
168
+ --info: #8b949e;
169
+ --shadow: rgba(0, 0, 0, 0.3);
170
+ --overlay-bg: rgba(0, 0, 0, 0.75);
171
+ }
172
+ [data-theme='one-dark'] {
173
+ --bg: #282c34;
174
+ --surface: #21252b;
175
+ --border: #3e4452;
176
+ --border-subtle: #4b5263;
177
+ --text: #abb2bf;
178
+ --text-secondary: #7f848e;
179
+ --text-dim: #5c6370;
180
+ --text-muted: #4b5263;
181
+ --accent: #61afef;
182
+ --accent-hover: #7dc0ff;
183
+ --accent-active: #4d9ede;
184
+ --danger: #e06c75;
185
+ --danger-hover: #c95c67;
186
+ --success: #98c379;
187
+ --info: #7f848e;
188
+ --shadow: rgba(0, 0, 0, 0.25);
189
+ --overlay-bg: rgba(0, 0, 0, 0.75);
190
+ }
191
+ [data-theme='catppuccin'] {
192
+ --bg: #1e1e2e;
193
+ --surface: #313244;
194
+ --border: #45475a;
195
+ --border-subtle: #585b70;
196
+ --text: #cdd6f4;
197
+ --text-secondary: #a6adc8;
198
+ --text-dim: #7f849c;
199
+ --text-muted: #585b70;
200
+ --accent: #89b4fa;
201
+ --accent-hover: #b4d0ff;
202
+ --accent-active: #5c9de3;
203
+ --danger: #f38ba8;
204
+ --danger-hover: #eb7c9d;
205
+ --success: #a6e3a1;
206
+ --info: #a6adc8;
207
+ --shadow: rgba(0, 0, 0, 0.2);
208
+ --overlay-bg: rgba(0, 0, 0, 0.75);
209
+ }
210
+ [data-theme='gruvbox'] {
211
+ --bg: #282828;
212
+ --surface: #3c3836;
213
+ --border: #504945;
214
+ --border-subtle: #665c54;
215
+ --text: #ebdbb2;
216
+ --text-secondary: #d5c4a1;
217
+ --text-dim: #a89984;
218
+ --text-muted: #7c6f64;
219
+ --accent: #83a598;
220
+ --accent-hover: #9dbfb4;
221
+ --accent-active: #6a8f8a;
222
+ --danger: #fb4934;
223
+ --danger-hover: #e33826;
224
+ --success: #b8bb26;
225
+ --info: #d5c4a1;
226
+ --shadow: rgba(0, 0, 0, 0.25);
227
+ --overlay-bg: rgba(0, 0, 0, 0.75);
228
+ }
229
+ [data-theme='night-owl'] {
230
+ --bg: #011627;
231
+ --surface: #0d2a45;
232
+ --border: #1d3b53;
233
+ --border-subtle: #264863;
234
+ --text: #d6deeb;
235
+ --text-secondary: #8badc1;
236
+ --text-dim: #5f7e97;
237
+ --text-muted: #3f5f7d;
238
+ --accent: #7fdbca;
239
+ --accent-hover: #9ff0e0;
240
+ --accent-active: #62c5b5;
241
+ --danger: #ef5350;
242
+ --danger-hover: #d83130;
243
+ --success: #addb67;
244
+ --info: #8badc1;
245
+ --shadow: rgba(0, 0, 0, 0.3);
246
+ --overlay-bg: rgba(0, 0, 0, 0.75);
247
+ }
58
248
  * {
59
249
  margin: 0;
60
250
  padding: 0;
@@ -116,10 +306,12 @@
116
306
  border-color: var(--border-subtle);
117
307
  background: var(--border);
118
308
  }
119
- .theme-toggle {
309
+ .theme-wrap {
120
310
  position: absolute;
121
311
  top: 16px;
122
312
  right: 16px;
313
+ }
314
+ .theme-toggle {
123
315
  background: none;
124
316
  border: 1px solid var(--border);
125
317
  color: var(--text-dim);
@@ -142,6 +334,47 @@
142
334
  border-color: var(--border-subtle);
143
335
  background: var(--border);
144
336
  }
337
+ .theme-picker {
338
+ display: none;
339
+ position: absolute;
340
+ top: calc(100% + 4px);
341
+ right: 0;
342
+ background: var(--surface);
343
+ border: 1px solid var(--border);
344
+ border-radius: 8px;
345
+ min-width: 160px;
346
+ padding: 4px 0;
347
+ z-index: 200;
348
+ box-shadow: 0 4px 12px var(--shadow);
349
+ }
350
+ .theme-picker.open {
351
+ display: block;
352
+ }
353
+ .theme-option {
354
+ display: flex;
355
+ align-items: center;
356
+ gap: 8px;
357
+ padding: 7px 12px;
358
+ cursor: pointer;
359
+ font-size: 13px;
360
+ color: var(--text);
361
+ transition: background 0.1s;
362
+ white-space: nowrap;
363
+ }
364
+ .theme-option:hover {
365
+ background: var(--border);
366
+ }
367
+ .theme-option.active {
368
+ color: var(--accent);
369
+ }
370
+ .theme-swatch {
371
+ width: 14px;
372
+ height: 14px;
373
+ border-radius: 50%;
374
+ display: inline-block;
375
+ flex-shrink: 0;
376
+ border: 1px solid rgba(128, 128, 128, 0.3);
377
+ }
145
378
 
146
379
  .sessions-list {
147
380
  padding: 16px;
@@ -691,28 +924,66 @@
691
924
  <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" />
692
925
  </svg>
693
926
  </button>
694
- <button class="theme-toggle" id="theme-toggle" title="Toggle theme">
695
- <svg
696
- width="16"
697
- height="16"
698
- viewBox="0 0 24 24"
699
- fill="none"
700
- stroke="currentColor"
701
- stroke-width="2"
702
- stroke-linecap="round"
703
- stroke-linejoin="round"
704
- >
705
- <circle cx="12" cy="12" r="5" />
706
- <line x1="12" y1="1" x2="12" y2="3" />
707
- <line x1="12" y1="21" x2="12" y2="23" />
708
- <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
709
- <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
710
- <line x1="1" y1="12" x2="3" y2="12" />
711
- <line x1="21" y1="12" x2="23" y2="12" />
712
- <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
713
- <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
714
- </svg>
715
- </button>
927
+ <div class="theme-wrap" id="theme-wrap">
928
+ <button class="theme-toggle" id="theme-toggle" title="Switch theme">
929
+ <svg
930
+ width="16"
931
+ height="16"
932
+ viewBox="0 0 24 24"
933
+ fill="none"
934
+ stroke="currentColor"
935
+ stroke-width="2"
936
+ stroke-linecap="round"
937
+ stroke-linejoin="round"
938
+ >
939
+ <circle cx="13.5" cy="6.5" r=".5" fill="currentColor" />
940
+ <circle cx="17.5" cy="10.5" r=".5" fill="currentColor" />
941
+ <circle cx="8.5" cy="7.5" r=".5" fill="currentColor" />
942
+ <circle cx="6.5" cy="12.5" r=".5" fill="currentColor" />
943
+ <path
944
+ d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"
945
+ />
946
+ </svg>
947
+ </button>
948
+ <div class="theme-picker" id="theme-picker">
949
+ <div class="theme-option" data-theme-option="dark">
950
+ <span class="theme-swatch" style="background: #1e1e1e"></span>Dark
951
+ </div>
952
+ <div class="theme-option" data-theme-option="light">
953
+ <span class="theme-swatch" style="background: #ffffff"></span>Light
954
+ </div>
955
+ <div class="theme-option" data-theme-option="monokai">
956
+ <span class="theme-swatch" style="background: #272822"></span>Monokai
957
+ </div>
958
+ <div class="theme-option" data-theme-option="solarized-dark">
959
+ <span class="theme-swatch" style="background: #002b36"></span>Solarized Dark
960
+ </div>
961
+ <div class="theme-option" data-theme-option="solarized-light">
962
+ <span class="theme-swatch" style="background: #fdf6e3"></span>Solarized Light
963
+ </div>
964
+ <div class="theme-option" data-theme-option="nord">
965
+ <span class="theme-swatch" style="background: #2e3440"></span>Nord
966
+ </div>
967
+ <div class="theme-option" data-theme-option="dracula">
968
+ <span class="theme-swatch" style="background: #282a36"></span>Dracula
969
+ </div>
970
+ <div class="theme-option" data-theme-option="github-dark">
971
+ <span class="theme-swatch" style="background: #0d1117"></span>GitHub Dark
972
+ </div>
973
+ <div class="theme-option" data-theme-option="one-dark">
974
+ <span class="theme-swatch" style="background: #282c34"></span>One Dark
975
+ </div>
976
+ <div class="theme-option" data-theme-option="catppuccin">
977
+ <span class="theme-swatch" style="background: #1e1e2e"></span>Catppuccin
978
+ </div>
979
+ <div class="theme-option" data-theme-option="gruvbox">
980
+ <span class="theme-swatch" style="background: #282828"></span>Gruvbox
981
+ </div>
982
+ <div class="theme-option" data-theme-option="night-owl">
983
+ <span class="theme-swatch" style="background: #011627"></span>Night Owl
984
+ </div>
985
+ </div>
986
+ </div>
716
987
  </div>
717
988
 
718
989
  <div class="sessions-list" id="sessions-list"></div>
@@ -852,24 +1123,46 @@
852
1123
 
853
1124
  <script>
854
1125
  // Theme
1126
+ const THEMES = [
1127
+ { id: 'dark', name: 'Dark', bg: '#1e1e1e' },
1128
+ { id: 'light', name: 'Light', bg: '#f3f3f3' },
1129
+ { id: 'monokai', name: 'Monokai', bg: '#272822' },
1130
+ { id: 'solarized-dark', name: 'Solarized Dark', bg: '#002b36' },
1131
+ { id: 'solarized-light', name: 'Solarized Light', bg: '#fdf6e3' },
1132
+ { id: 'nord', name: 'Nord', bg: '#2e3440' },
1133
+ { id: 'dracula', name: 'Dracula', bg: '#282a36' },
1134
+ { id: 'github-dark', name: 'GitHub Dark', bg: '#0d1117' },
1135
+ { id: 'one-dark', name: 'One Dark', bg: '#282c34' },
1136
+ { id: 'catppuccin', name: 'Catppuccin', bg: '#1e1e2e' },
1137
+ { id: 'gruvbox', name: 'Gruvbox', bg: '#282828' },
1138
+ { id: 'night-owl', name: 'Night Owl', bg: '#011627' },
1139
+ ];
855
1140
  function getTheme() {
856
1141
  return localStorage.getItem('termbeam-theme') || 'dark';
857
1142
  }
858
1143
  function applyTheme(theme) {
859
1144
  document.documentElement.setAttribute('data-theme', theme);
860
- document.querySelector('meta[name="theme-color"]').content =
861
- theme === 'light' ? '#f3f3f3' : '#1e1e1e';
862
- const btn = document.getElementById('theme-toggle');
863
- if (btn)
864
- btn.innerHTML =
865
- theme === 'light'
866
- ? '<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>'
867
- : '<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>';
1145
+ const t = THEMES.find((x) => x.id === theme) || THEMES[0];
1146
+ document.querySelector('meta[name="theme-color"]').content = t.bg;
868
1147
  localStorage.setItem('termbeam-theme', theme);
1148
+ document.querySelectorAll('.theme-option').forEach((el) => {
1149
+ el.classList.toggle('active', el.dataset.themeOption === theme);
1150
+ });
869
1151
  }
870
1152
  applyTheme(getTheme());
871
- document.getElementById('theme-toggle').addEventListener('click', () => {
872
- applyTheme(getTheme() === 'light' ? 'dark' : 'light');
1153
+ document.getElementById('theme-toggle').addEventListener('click', (e) => {
1154
+ e.stopPropagation();
1155
+ document.getElementById('theme-picker').classList.toggle('open');
1156
+ });
1157
+ document.addEventListener('click', () => {
1158
+ document.getElementById('theme-picker').classList.remove('open');
1159
+ });
1160
+ document.querySelectorAll('.theme-option').forEach((el) => {
1161
+ el.addEventListener('click', (e) => {
1162
+ e.stopPropagation();
1163
+ applyTheme(el.dataset.themeOption);
1164
+ document.getElementById('theme-picker').classList.remove('open');
1165
+ });
873
1166
  });
874
1167
 
875
1168
  const listEl = document.getElementById('sessions-list');