Browser terminal
vev --web-daemon
This starts a background web server on 127.0.0.1:8778 and prints a private link. Open it in a browser to get the normal vev UI: palette, tabs, and splits.
- Each browser tab is its own client. Closing the page detaches; shells keep running.
- Reloading creates a new session. Find old ones with the session picker (
SSP) and close the ones you don't need.
- Running the command again prints the same link.
Access from another machine
vev serves plain HTTP. For HTTPS, put a reverse proxy in front of it (for example Caddy, nginx, or a NetBird app).
vev --web-daemon --web-listen 127.0.0.1:8778 \
--web-origin https://terminal.example.internal
Or in ~/.config/vev/config:
web.listen = 127.0.0.1:8778
web.origin = https://terminal.example.internal
web.listen: IP and port to listen on, such as 100.64.0.10:8778 or [::1]:8778. No hostnames.
web.origin: the URL users type in the browser, without a path. Required when not listening on loopback.
- Flags override config. Restart the gateway to apply changes; shells survive.
The proxy must:
- keep the original
Host and Origin headers,
- forward WebSocket upgrades,
- serve vev at the root path (no
/vev/ prefix).
[!WARNING]
Access to this page is access to your shell. Prefer loopback or one private IP. Avoid 0.0.0.0 unless a firewall restricts access.
Controls
| Input |
Action |
| Alt+Space or Palette button |
command palette |
| Alt+h/j/k/l |
focus pane |
| Alt+1…9 |
switch tab |
| Click |
focus pane |
| Shift + wheel |
scroll 10× faster |
| Ctrl+Shift+F6 |
move focus out of the terminal to the Palette button |
- The terminal fits the window, up to 512×256 cells.
- On touch devices, Select allows native text selection and Keys opens the keyboard.
- Your browser or window manager may catch some shortcuts first. The Palette button avoids the common Alt+Space conflict.
Not supported yet: browser scrollback, touch scrolling, copy-mode clipboard export, Kitty images, OSC 52, and full emoji fidelity.
Security
- The access token is random, kept only in memory, and gone when the gateway stops.
- The link carries it once; the page then swaps it for an HttpOnly cookie.
vev --web-renew-token revokes all links and browser sessions and prints a new link. Shells keep running.
- Host and Origin checks, a strict Content Security Policy, and input limits protect the endpoint.
- Everything is embedded in the binary. No CDN or frontend framework.
Stop the gateway
The gateway is a separate process. Stopping the session daemon does not stop it. Send it SIGTERM.
Testing a build
Use an isolated profile so tests don't touch your real sessions:
go build -o .dev/vev-web .
VEV_ENV=web-visual .dev/vev-web --web-daemon
VEV_ENV=web-visual .dev/vev-web ls
Keep the printed link out of screenshots.
scripts/web-visual-smoke.cjs and scripts/web-access-smoke.cjs run Playwright checks. Set PLAYWRIGHT_MODULE, VEV_BINARY, VEV_ENV=web-visual, and optionally CHROMIUM_PATH. Run them only against an isolated profile: they create sessions and revoke access.
scripts/web-scroll-bench.cjs measures how much browser work a fast history scroll costs. It starts its own gateway in a temporary profile, prints 10,000 colored lines, scrolls up and back down with the mouse wheel, and sums the page's main-thread time from a Chromium trace. It stops the gateway and deletes the profile when it ends. Linux only.
go build -o .dev/vev-web .
PLAYWRIGHT_MODULE=/path/to/node_modules/playwright VEV_BINARY=$PWD/.dev/vev-web \
node scripts/web-scroll-bench.cjs --viewport 1638x987 --scale 1.25
| Option |
Default |
Meaning |
--lines |
10000 |
History lines printed before scrolling |
--notches |
120 |
Wheel notches: half up, half down, one every 16 ms |
--port |
8799 |
Gateway port on 127.0.0.1 |
--viewport |
1600x1000 |
Page size in CSS pixels (headless only) |
--scale |
1 |
Device pixel ratio, for example 1.25 for 125% zoom (headless only) |
--cdp |
|
Use a running Chromium instead of headless, for example http://127.0.0.1:9222 |
The output is JSON. wireFrames, wireKB and avgFrameKB count the WebSocket updates the page received during the scroll. totalMs is the main thread's busy time; scriptMs, styleMs, layoutMs and paintMs split it. totalMsPerNotch above 16 means the browser cannot keep up with a 60 Hz wheel. Compare two builds with the same options and run each a few times: the numbers vary by about 10%.
Headless Chromium has no GPU compositor, so it underestimates paint. For numbers that match what users see, start Chromium with --remote-debugging-port=9222, keep the benchmark tab in the foreground, and pass --cdp. The window size and zoom then come from that browser. Close it afterwards: the debugging port lets any local process control it.
To try a frontend change without rebuilding, set WEB_APP_JS or WEB_RUNTIME_JS to a local app.js or terminal.js.
source
1# Browser terminal
2
3```sh
4vev --web-daemon
5```
6
7This starts a background web server on `127.0.0.1:8778` and prints a private link. Open it in a browser to get the normal vev UI: palette, tabs, and splits.
8
9- Each browser tab is its own client. Closing the page detaches; shells keep running.
10- Reloading creates a **new** session. Find old ones with the session picker (`SSP`) and close the ones you don't need.
11- Running the command again prints the same link.
12
13## Access from another machine
14
15vev serves plain HTTP. For HTTPS, put a reverse proxy in front of it (for example Caddy, nginx, or a NetBird app).
16
17```sh
18vev --web-daemon --web-listen 127.0.0.1:8778 \
19 --web-origin https://terminal.example.internal
20```
21
22Or in `~/.config/vev/config`:
23
24```ini
25web.listen = 127.0.0.1:8778
26web.origin = https://terminal.example.internal
27```
28
29- `web.listen`: IP and port to listen on, such as `100.64.0.10:8778` or `[::1]:8778`. No hostnames.
30- `web.origin`: the URL users type in the browser, without a path. Required when not listening on loopback.
31- Flags override config. Restart the gateway to apply changes; shells survive.
32
33The proxy must:
34
35- keep the original `Host` and `Origin` headers,
36- forward WebSocket upgrades,
37- serve vev at the root path (no `/vev/` prefix).
38
39> [!WARNING]
40> Access to this page is access to your shell. Prefer loopback or one private IP. Avoid `0.0.0.0` unless a firewall restricts access.
41
42## Controls
43
44| Input | Action |
45|---|---|
46| Alt+Space or **Palette** button | command palette |
47| Alt+h/j/k/l | focus pane |
48| Alt+1…9 | switch tab |
49| Click | focus pane |
50| Shift + wheel | scroll 10× faster |
51| Ctrl+Shift+F6 | move focus out of the terminal to the Palette button |
52
53- The terminal fits the window, up to 512×256 cells.
54- On touch devices, **Select** allows native text selection and **Keys** opens the keyboard.
55- Your browser or window manager may catch some shortcuts first. The Palette button avoids the common Alt+Space conflict.
56
57Not supported yet: browser scrollback, touch scrolling, copy-mode clipboard export, Kitty images, OSC 52, and full emoji fidelity.
58
59## Security
60
61- The access token is random, kept only in memory, and gone when the gateway stops.
62- The link carries it once; the page then swaps it for an HttpOnly cookie.
63- `vev --web-renew-token` revokes all links and browser sessions and prints a new link. Shells keep running.
64- Host and Origin checks, a strict Content Security Policy, and input limits protect the endpoint.
65- Everything is embedded in the binary. No CDN or frontend framework.
66
67## Stop the gateway
68
69The gateway is a separate process. Stopping the session daemon does not stop it. Send it `SIGTERM`.
70
71## Testing a build
72
73Use an isolated profile so tests don't touch your real sessions:
74
75```sh
76go build -o .dev/vev-web .
77VEV_ENV=web-visual .dev/vev-web --web-daemon
78VEV_ENV=web-visual .dev/vev-web ls
79```
80
81Keep the printed link out of screenshots.
82
83`scripts/web-visual-smoke.cjs` and `scripts/web-access-smoke.cjs` run Playwright checks. Set `PLAYWRIGHT_MODULE`, `VEV_BINARY`, `VEV_ENV=web-visual`, and optionally `CHROMIUM_PATH`. Run them only against an isolated profile: they create sessions and revoke access.
84
85## Measuring scroll performance
86
87`scripts/web-scroll-bench.cjs` measures how much browser work a fast history scroll costs. It starts its own gateway in a temporary profile, prints 10,000 colored lines, scrolls up and back down with the mouse wheel, and sums the page's main-thread time from a Chromium trace. It stops the gateway and deletes the profile when it ends. Linux only.
88
89```sh
90go build -o .dev/vev-web .
91PLAYWRIGHT_MODULE=/path/to/node_modules/playwright VEV_BINARY=$PWD/.dev/vev-web \
92 node scripts/web-scroll-bench.cjs --viewport 1638x987 --scale 1.25
93```
94
95| Option | Default | Meaning |
96|---|---|---|
97| `--lines` | 10000 | History lines printed before scrolling |
98| `--notches` | 120 | Wheel notches: half up, half down, one every 16 ms |
99| `--port` | 8799 | Gateway port on 127.0.0.1 |
100| `--viewport` | 1600x1000 | Page size in CSS pixels (headless only) |
101| `--scale` | 1 | Device pixel ratio, for example `1.25` for 125% zoom (headless only) |
102| `--cdp` | | Use a running Chromium instead of headless, for example `http://127.0.0.1:9222` |
103
104The output is JSON. `wireFrames`, `wireKB` and `avgFrameKB` count the WebSocket updates the page received during the scroll. `totalMs` is the main thread's busy time; `scriptMs`, `styleMs`, `layoutMs` and `paintMs` split it. `totalMsPerNotch` above 16 means the browser cannot keep up with a 60 Hz wheel. Compare two builds with the same options and run each a few times: the numbers vary by about 10%.
105
106Headless Chromium has no GPU compositor, so it underestimates paint. For numbers that match what users see, start Chromium with `--remote-debugging-port=9222`, keep the benchmark tab in the foreground, and pass `--cdp`. The window size and zoom then come from that browser. Close it afterwards: the debugging port lets any local process control it.
107
108To try a frontend change without rebuilding, set `WEB_APP_JS` or `WEB_RUNTIME_JS` to a local `app.js` or `terminal.js`.