termbeam 1.2.10 → 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 +27 -2
- package/bin/termbeam.js +26 -1
- package/package.json +3 -3
- package/public/index.html +326 -33
- package/public/terminal.html +770 -37
- package/src/auth.js +135 -22
- package/src/cli.js +10 -1
- package/src/server.js +3 -4
- package/src/service.js +731 -0
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
|
[](https://www.npmjs.com/package/termbeam)
|
|
8
|
+
[](https://www.npmjs.com/package/termbeam)
|
|
6
9
|
[](https://github.com/dorlugasigal/TermBeam/actions/workflows/ci.yml)
|
|
7
10
|
[](https://github.com/dorlugasigal/TermBeam/actions/workflows/ci.yml)
|
|
11
|
+
[](https://nodejs.org/)
|
|
8
12
|
[](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
|
|
|
@@ -106,6 +112,11 @@ termbeam [shell] [args...] # start with a specific shell (default: auto-d
|
|
|
106
112
|
termbeam --port 8080 # custom port (default: 3456)
|
|
107
113
|
termbeam --host 0.0.0.0 # allow LAN access (default: 127.0.0.1)
|
|
108
114
|
termbeam --lan # shortcut for --host 0.0.0.0
|
|
115
|
+
termbeam service install # interactive PM2 service setup wizard
|
|
116
|
+
termbeam service uninstall # stop & remove PM2 service
|
|
117
|
+
termbeam service status # show PM2 service status
|
|
118
|
+
termbeam service logs # tail PM2 service logs
|
|
119
|
+
termbeam service restart # restart PM2 service
|
|
109
120
|
```
|
|
110
121
|
|
|
111
122
|
| Flag | Description | Default |
|
|
@@ -121,6 +132,16 @@ termbeam --lan # shortcut for --host 0.0.0.0
|
|
|
121
132
|
| `--host <addr>` | Bind address | `127.0.0.1` |
|
|
122
133
|
| `--lan` | Bind to all interfaces (LAN access) | Off |
|
|
123
134
|
| `--log-level <level>` | Log verbosity (error/warn/info/debug) | `info` |
|
|
135
|
+
| `-h, --help` | Show help | — |
|
|
136
|
+
| `-v, --version` | Show version | — |
|
|
137
|
+
|
|
138
|
+
| Subcommand | Description |
|
|
139
|
+
| ------------------- | ----------------------------- |
|
|
140
|
+
| `service install` | Interactive PM2 service setup |
|
|
141
|
+
| `service uninstall` | Stop & remove from PM2 |
|
|
142
|
+
| `service status` | Show PM2 service status |
|
|
143
|
+
| `service logs` | Tail PM2 service logs |
|
|
144
|
+
| `service restart` | Restart PM2 service |
|
|
124
145
|
|
|
125
146
|
Environment variables: `PORT`, `TERMBEAM_PASSWORD`, `TERMBEAM_CWD`, `TERMBEAM_LOG_LEVEL`, `SHELL` (Unix fallback), `COMSPEC` (Windows fallback). See [Configuration docs](https://dorlugasigal.github.io/TermBeam/configuration/).
|
|
126
147
|
|
|
@@ -128,7 +149,7 @@ Environment variables: `PORT`, `TERMBEAM_PASSWORD`, `TERMBEAM_CWD`, `TERMBEAM_LO
|
|
|
128
149
|
|
|
129
150
|
TermBeam auto-generates a password and creates a tunnel by default, so your terminal is protected out of the box. By default, the server binds to `127.0.0.1` (localhost only). Use `--lan` or `--host 0.0.0.0` to allow LAN access, or `--no-tunnel` to disable the tunnel.
|
|
130
151
|
|
|
131
|
-
Auth uses secure httpOnly cookies with 24-hour expiry, login is rate-limited to 5 attempts per minute, and security headers (X-Frame-Options, X-Content-Type-Options, etc.) are set on all responses.
|
|
152
|
+
Auth uses secure httpOnly cookies with 24-hour expiry, login is rate-limited to 5 attempts per minute, and security headers (X-Frame-Options, X-Content-Type-Options, etc.) are set on all responses. Each QR code contains a single-use share token (5-minute expiry) for password-free login. API clients that can't use cookies can authenticate with an `Authorization: Bearer <password>` header.
|
|
132
153
|
|
|
133
154
|
For the full threat model, safe usage guidance, and a quick safety checklist, see [SECURITY.md](SECURITY.md). For detailed security feature documentation, see the [Security Guide](https://dorlugasigal.github.io/TermBeam/security/).
|
|
134
155
|
|
|
@@ -136,6 +157,10 @@ For the full threat model, safe usage guidance, and a quick safety checklist, se
|
|
|
136
157
|
|
|
137
158
|
Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
138
159
|
|
|
160
|
+
## Changelog
|
|
161
|
+
|
|
162
|
+
See [CHANGELOG.md](CHANGELOG.md) for version history.
|
|
163
|
+
|
|
139
164
|
## License
|
|
140
165
|
|
|
141
166
|
[MIT](LICENSE)
|
package/bin/termbeam.js
CHANGED
|
@@ -1,2 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
// Dispatch subcommands before loading the server
|
|
4
|
+
const subcommand = (process.argv[2] || '').toLowerCase();
|
|
5
|
+
if (subcommand === 'service') {
|
|
6
|
+
const { run } = require('../src/service');
|
|
7
|
+
run(process.argv.slice(3)).catch((err) => {
|
|
8
|
+
console.error(err.message);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
});
|
|
11
|
+
} else {
|
|
12
|
+
const { createTermBeamServer } = require('../src/server.js');
|
|
13
|
+
const instance = createTermBeamServer();
|
|
14
|
+
|
|
15
|
+
process.on('SIGINT', () => {
|
|
16
|
+
console.log('\n[termbeam] Shutting down...');
|
|
17
|
+
instance.shutdown();
|
|
18
|
+
setTimeout(() => process.exit(0), 500).unref();
|
|
19
|
+
});
|
|
20
|
+
process.on('SIGTERM', () => {
|
|
21
|
+
console.log('\n[termbeam] Shutting down...');
|
|
22
|
+
instance.shutdown();
|
|
23
|
+
setTimeout(() => process.exit(0), 500).unref();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
instance.start();
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "termbeam",
|
|
3
|
-
"version": "1.
|
|
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": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"start": "node bin/termbeam.js",
|
|
11
11
|
"dev": "node bin/termbeam.js --generate-password",
|
|
12
12
|
"test": "node -e \"require('child_process').execFileSync(process.execPath,['--test',...require('fs').readdirSync('test').filter(f=>f.endsWith('.test.js')&&!f.startsWith('e2e-')&&f!=='devtunnel-install.test.js').map(f=>'test/'+f)],{stdio:'inherit'})\"",
|
|
13
|
-
"test:coverage": "c8 --exclude=src/tunnel.js --reporter=text --reporter=lcov --reporter=json-summary --reporter=json node -e \"require('child_process').execFileSync(process.execPath,['--test','--test-reporter=spec','--test-reporter-destination=stdout',...require('fs').readdirSync('test').filter(f=>f.endsWith('.test.js')&&!f.startsWith('e2e-')&&f!=='devtunnel-install.test.js').map(f=>'test/'+f)],{stdio:'inherit'})\"",
|
|
13
|
+
"test:coverage": "c8 --exclude=src/tunnel.js --exclude=src/devtunnel-install.js --exclude=test --reporter=text --reporter=lcov --reporter=json-summary --reporter=json node -e \"require('child_process').execFileSync(process.execPath,['--test','--test-reporter=spec','--test-reporter-destination=stdout',...require('fs').readdirSync('test').filter(f=>f.endsWith('.test.js')&&!f.startsWith('e2e-')&&f!=='devtunnel-install.test.js').map(f=>'test/'+f)],{stdio:'inherit'})\"",
|
|
14
14
|
"prepare": "husky",
|
|
15
15
|
"format": "prettier --write .",
|
|
16
16
|
"lint": "node --check src/*.js bin/*.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
],
|
|
39
39
|
"author": "Dor Lugasi <dorlugasigal@gmail.com>",
|
|
40
40
|
"license": "MIT",
|
|
41
|
-
"homepage": "https://
|
|
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-
|
|
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
|
-
<
|
|
695
|
-
<
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
</
|
|
715
|
-
|
|
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
|
-
|
|
861
|
-
|
|
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
|
-
|
|
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');
|