Web
Documentation in progress
Some sections are still incomplete.
A ThinkTerm multiplexer can serve its terminals to a browser. A phone, or a machine with no ThinkTerm on it, opens the sessions already running on the machine that has one — the same Spaces, Projects and Threads, not a copy of them.
Nothing listens until you ask for it.
Turning it on
Three ways, all of them addressing a multiplexer server rather than the GUI's own in-process one:
Settings › Web— a switch, the address it is accepting on, and the list of links you have handed out.thinkterm cli web-server on— the same thing from a shell.--bind-addresschooses where to listen,statusreports where it is accepting, andoffcloses the port and drops the browsers on it.- A
web_serversentry in the configuration — read when the server starts, so the port is open every time it runs.
The first two change a running server and last as long as it does. The config entry is the durable form; reloading the config does not open or close a web port.
Local terminals have to be running in a background session server for there to be anything to serve — that setting is described in Remote, and a new installation starts with it on. Without one, Settings › Web says so rather than offering a switch that would do nothing. If the only thing attached is a remote domain, that section is about that machine's port, not this one's.
The link is the credential
A browser is admitted by a token, and the token arrives as a URL:
thinkterm cli web-token mint --label phone --ttl 12hWhoever opens that URL has what you have on that machine: a shell as your user, and every pane's scrollback. Treat it the way you treat an ssh key — in particular, do not paste one anywhere you would not paste a private key.
--ttltakes30m,12h,7d, or a number of seconds. Without it the token lives until it is revoked. A link copied from Settings expires in eight hours unless you pick something else, up toUntil revoked.web-token listshows every token the server knows, and how many browsers are on each.web-token revoke <id>, or--all, cuts them off at once, live browsers included.--url-onlyprints nothing but the URL, for scripts.
A connected browser appears in thinkterm cli list-clients under the label its token was minted with.
The token travels in the part of the URL after #, which a browser never sends to a server or in a Referer. The page moves it into the browser tab's own session storage and rewrites the address bar without it, so it is not bookmarked, shared or caught in a screenshot by accident. A reload in the same tab keeps working; a new tab, or the bare address, needs the link again and says so.
Who can reach the port
Out of the box the listener binds 127.0.0.1:8088 — the machine it runs on, and nothing else.
Reachable from other devices rebinds it to every address the machine has, so turn it on only on a network you trust. Browsers only expose WebGPU to a secure context, and http://localhost is one while a plain http:// to any other host is not, so a listener off loopback needs TLS: without a certificate of your own, ThinkTerm generates a self-signed one naming the machine's hostname and each of its addresses. A browser warns about that certificate once and then draws the page. Before you accept the warning, compare the certificate's SHA-256 fingerprint in the browser with the one Settings › Web shows, or the one the full web-token mint output prints — --url-only leaves it out — and stop if they differ. require_tls_off_loopback, on by default, is what refuses to serve a non-loopback address in the clear — a page served that way would have no WebGPU and show nothing anyway.
For a phone on the same network or tailnet, the QR code in Settings › Web is the short way to get the link across. Over the open internet, forward the loopback port with ssh -L rather than exposing it.
Behind a reverse proxy
The listener accepts a page only from its own address. A page reached under any other name — through a reverse proxy, say https://terminal.example.com — has to be listed in allowed_origins, or its connection is refused. Listing any origin replaces that default, and the links web-token mint prints then point at the listed origins.
Settings › Web
| Row | What it does |
|---|---|
Allow browser access | Opens or closes the port on the session server |
Reachable from other devices | Listens on every address with its own certificate, instead of loopback only |
Access link › Copy link | Mints a link and copies it |
Link expires | How long the next copied link lives: 1 hour, 8 hours (the default), 1 day, 7 days or Until revoked |
Scan on your phone › Show code | Mints a link and shows it as a QR code |
Links you have handed out | Every live link, with a Revoke button each |
Every link › Revoke all | Cuts off every browser at once |
Each handed-out link is named by the label it was minted with, otherwise by the device that last used it, otherwise Not used yet. Beneath it is when it expires and how many browsers are connected through it. Revoke ends that one link and drops the browsers using it; the others keep working.
The web_servers entry
Each entry in web_servers is one port, read when the server starts:
config.web_servers = {
{
bind_address = '127.0.0.1:8088',
token_file = wezterm.home_dir .. '/.local/share/thinkterm/web-tokens.json',
},
}| Field | Default | Meaning |
|---|---|---|
bind_address | 127.0.0.1:8088 | The address and port to listen on |
pem_private_key | unset | A PEM private key. With pem_cert set too, the port serves https and wss |
pem_cert | unset | A PEM certificate |
pem_ca | unset | A PEM CA chain |
static_dir | beside the installed program | Where the page's files are |
token_file | unset | Where minted tokens are kept, as digests |
allowed_origins | the listener's own address | Origins allowed to open the connection |
require_tls_off_loopback | true | Refuse to start without TLS on a non-loopback address |
token_file matters more than it looks. Unset, tokens live in the server's memory only: when the server restarts — after a reboot, or an update that cannot hand its sessions over — every link stops working and has to be minted again. Set it, and links survive a restart. The file holds digests, never the tokens, so a copy of it lets nobody in.
static_dir is for running a page other than the one installed, such as a development build; the THINKTERM_WEB_STATIC_DIR environment variable does the same without a config change, and static_dir wins when both are set.
What the page can do
The browser draws the same workspace: the Space, Project and Thread tree, the tab strip, split panes, a search palette, and the right-hand panel. Threads and panes can be opened and closed from it.
The right-hand panel offers Snippets, Agents, and the panels of installed plugins. Snippets and plugins come from the machine that served the page, not the one the browser runs on: a browser pointed at a server sees that server's snippets and that server's plugins. A Run or Paste lands in the pane that had focus when you pressed it. The Agents panel is off until you turn it on with the button at the right end of the tab row.
Pictures sent with the Kitty graphics protocol are drawn in the page, animations included. PNG or JPEG pictures sent through the iTerm2 protocol are not yet.
The sidebar
The sidebar shows one Space at a time, as the desktop's does: its name at the top, New Thread, pinned Threads under Pinned, then the Projects under Workspaces with their Threads. The … beside the Space's name lists the other Spaces and offers New Space, and renaming or deleting the one on show — never the last one.
From the page you can:
- create, rename, switch and delete Spaces
- add a Project by typing a directory on the server,
~/diror/dir, underAdd workspace…; it comes with amainThread - collapse a Project by clicking it, rename it by double-clicking its name, archive it, restore it, remove it
- start, pin, unpin and delete Threads, rename one by double-clicking its name, and mark one unread
- drag a Thread within its Project, or a Project within the Space, to reorder them
Archived Projects collect under Archived (N) at the bottom of the Space; click it to show them. A Thread row's delete button and a tab's close button both ask a second time, showing delete? or close?, because they end the programs inside. Windows on the server that no Thread claims are listed under Other windows, so nothing is out of reach.
The sidebar's width is dragged at its edge and remembered by the browser. The button at the start of the tab row hides it; with Reveal the sidebar on hover on, resting the pointer on the left edge brings it back until the pointer leaves.
Menus
Right-click offers the desktop's menus, less what the server has no operation for:
| On | Offers |
|---|---|
| A pane | Copy, Paste, Split Right, Split Left, Split Down, Split Up, Frontend access › A · Shared (tmux-like) / B · Handoff (exclusive) |
| A tab | Close Tabs to Left, Close Tabs to Right, Close Other Tabs, New Terminal Tab to Right, Zoom Pane |
| A Thread | Pin Thread or Unpin Thread, Rename Thread…, Delete Thread, Mark as Unread |
| A Project | Rename Project…, New Thread, Collapse / Expand Threads, Archive Project…, Remove Project |
| An archived Project | Unarchive Project, Delete Permanently… › Delete Project and Threads |
When a Project has panes running, Archive Project… says how many it will close before Archive and Close Panes does it. Renaming a tab, moving a tab between windows and Reset Terminal are desktop-only. Tabs can still be reordered by dragging them along the tab row, and a pane dragged onto the edge of another pane moves there.
Every pane carries a bar with its tabs on the left and, on the right, a new tab in that pane, split down, split right and zoom.
The search palette
⌘K on a Mac, Ctrl+K elsewhere, opens the search palette; the shortcut can be changed in the page's settings. It finds, in sections:
Find Thread— Threads in every SpaceTabsandPanes— those in the window on showSwitch Space— the SpacesCommands—New Thread,New Tab,Split Right,Split Down,Zoom Pane,Close Pane,Take Over the Terminal,Follow the Desktop's Focus,Show or Hide the Sidebar,Settings,Increase font size,Decrease font size,Reset font size
With nothing typed, the things you picked recently come first. Arrows move, Enter runs, Esc closes. The magnifier at the foot of the sidebar opens it with Threads only.
Who drives the terminal
How a terminal is shared is the server's choice: the pane menu's Frontend access submenu switches it, with a tick by the current one. The server keeps the choice, across restarts too, so it applies to every device attached to it, not only this page.
B · Handoff (exclusive), the default, gives a terminal one driver at a time. Opening a terminal the desktop is holding puts a card over it — Terminal is being used on another device, Click or scroll to continue — and the terminal stays visible underneath. A click, a scroll or a keystroke in it takes it over. The first keystroke is not lost; it is typed once the handoff completes. Clicks on the tab row, the pane bars and the sidebar never take it. Ctrl+Shift+T, or Take Over the Terminal in the palette, takes it explicitly. That is the same handoff described in Remote, with a browser counting as one device identified by its link — two tabs opened from the same link share a turn.
A · Shared (tmux-like) has no driver to hand over: every attached client sees the same terminals and can type into them, and the last one to interact with a tab decides its size.
While the page holds a tab, the tab is reshaped to the browser window. While the desktop holds it, the page keeps the desktop's size and shape, and clips when the tab is larger than the window. Ctrl+Shift+F reshapes the tab to this window on demand.
Follow the Desktop's Focus, on when the page opens, keeps one focus between the two: when the desktop moves to another pane, the page moves with it, and a click on a pane in the page moves the desktop. Run the command again to stop following; it is not remembered across loads.
Clipboard
Selecting text with the mouse copies it as soon as you let go. A double click selects a word, a triple click a line. When a program has asked for the mouse, hold Shift to select text yourself. Copy and Paste are also in the pane menu.
A program can set the clipboard with OSC 52, but only from a pane the page is showing: a pane nobody is looking at does not get to write this device's clipboard.
Keyboard shortcuts
| Keys | Action |
|---|---|
Cmd+C or Ctrl+Shift+C | Copy the selection |
Cmd+V or Ctrl+Shift+V | Paste |
Ctrl+Shift+Enter | Split right |
Ctrl+Shift+\ | Split down |
Ctrl+Shift+Z | Zoom the pane, or unzoom it |
Ctrl+Shift+F | Fit the tab to this window |
Ctrl+Shift+T | Take the terminal over |
Ctrl+Shift with an arrow | Focus the neighbouring pane, without moving the desktop's focus |
Cmd+=, Cmd+-, Cmd+0 | The focused pane's font size: larger, smaller, reset |
⌘K / Ctrl+K | The search palette, unless another shortcut is chosen |
Every other Cmd combination is left to the browser — reload, the address bar, tabs — and never reaches the terminal. Everything else goes to the focused pane, including Ctrl keys, so the default palette shortcut means Ctrl+K does not reach the terminal off a Mac; pick Ctrl+Shift+P in the settings if a program needs it. Input methods work: keys the input method is composing stay with it, and its candidate window follows the terminal cursor.
On a phone
The page switches to its phone layout when the pointer is a finger, or when the window is narrower than 720 pixels. Reach the server over https first, as described above.
- The terminal takes the whole screen. The sidebar and the Agents panel become drawers over it; a swipe in from the left edge brings the sidebar out, and a swipe back to the left puts it away. Picking a Thread in the drawer closes it.
- A key bar along the bottom carries
Esc,Tab,CtrlandAlt, the four arrows,Home,End,PgUp,PgDn, and-/|~.CtrlandAltare sticky: tap one, and it applies to the next key from the bar or from the soft keyboard. Holding an arrow repeats it. - The soft keyboard appears only when you ask for it with the keyboard button on the key bar, so a tap on the terminal does not cover half the screen.
- A finger drag scrolls, and a flick keeps scrolling. Two fingers scroll too; pinching changes the font size of the pane under them.
- A tap is a click. A long press, half a second, opens the pane's menu.
- A tab with a single pane shows no pane bar; one appears when there is a split to tell apart.
Text cannot be selected by touch: a drag on a phone always scrolls.
The page's own settings
The gear at the foot of the sidebar opens the page's settings. They belong to this browser, not to the server or your account.
| Section | Setting | Choices |
|---|---|---|
| General | Language | Follow System — the browser's languages — or one of ThinkTerm's languages |
| General | Scrolling | Smooth (default), following a finger or trackpad by the pixel, or Stepped, a row at a time |
| General | Search shortcut | ⌘K (default), ⌘⇧P, Ctrl+Shift+P. Off a Mac, ⌘ means Ctrl |
| Appearance | Color scheme | Follow the desktop (default), using the scheme the server is configured with, or any built-in scheme |
| Appearance | Theme | Dark (default), Light or Follow System, for the page around the terminal |
| Appearance | Font size | Follow the desktop, so a cell here matches one there, or Fixed size, 6 to 72 in half-point steps |
| Sidebar & Plugins | Reveal the sidebar on hover | On by default |
| Sidebar & Plugins | Reset sidebar width | Puts the sidebar back to its default width |
| Sidebar & Plugins | Agents panel, Snippets | Whether the right-hand panel offers them |
| Sidebar & Plugins | Reload plugins, and each plugin | A switch, and how long it runs unused |
| About | — | The language in use, the address the page came from, and the build |
Color scheme opens a searchable list with each scheme's colours beside it; the terminal previews whichever row is under the pointer or the arrows, Enter keeps it, and Esc puts back what you had. The search box at the top of the settings finds a row in any section.
Where they are kept
In the browser's local storage, along with the Space the page was last showing, the palette's recent picks, the sidebar's width, and the picked colour scheme with its colours, so the next load draws in it straight away. Another browser, or a private window, starts from the defaults. Nothing of this goes to the server. A browser that blocks storage keeps the settings for that load only.
URL overrides
A URL can override some settings for one load, without changing what is stored:
| Parameter | Effect |
|---|---|
?lang=de-DE | The page's language |
?theme=light | light, dark or system |
?font=14 | A fixed font size, 6 to 72 |
?glyphfont= | The CSS font list used for characters the page's own fonts lack, which also picks the regional shape of Han characters |
Put them before the # of a minted link: https://example-host:8088/?theme=light#token=….
When the connection drops
The page reconnects by itself, to the same tab: once straight away, then after a second, doubling the wait each time up to fifteen seconds. A note at the bottom right says the connection was lost and that it is reconnecting. After six failed attempts it adds that the link may have expired, or the server may be down — a browser is not told why a connection was refused, so a revoked link, an expired one and a stopped server look the same from the page.
What was on screen stays there while it is down, and the tab is laid out again from the server once it is back. The page gives up only when there is nothing to come back to: the server has no panes, or speaks a different protocol version. It says which.
What the page cannot do
- Files and Notes — desktop-only for now, as are tab icons.
- Live Overview and Remote Hosts — there is no browser counterpart, and no button for them.
- Thread references — they are the desktop's own and are not shown. Projects on an SSH host are not shown either: the server could not open them.
- Notifications and sounds — the bell has nothing in it, and nothing plays.
- Links — links in the output cannot be clicked.
- Scrollback search, copy mode and quick select — the palette searches the workspace, not the text in a pane.
- Selecting by touch — on a phone, dragging scrolls.
- iTerm2 images — PNG and JPEG pictures sent through the iTerm2 protocol are not drawn yet.
- Right-to-left text — shown as boxes, since the page does not reorder bidirectional text and correct letters in the wrong order would be harder to notice.
- Renaming or moving tabs between windows, Reset Terminal — the server has no operation for them.
Characters the page's two bundled fonts lack — CJK, Hangul, emoji — are drawn with the fonts installed on the device the browser runs on. A character none of them has shows as a box. Without WebGPU the page shows nothing but a message saying to open it over https or http://localhost in a browser with WebGPU enabled.
Related
- Remote — the session server that serves the browser, and how the handoff works
- Command Line —
web-serverandweb-tokenin full - Plugins — the panels a browser shows from the server's machine
- Spaces, Projects and Threads — the workspace the page shows
- Privacy and Security — what the page stores and sends
- Configuration — where
web_serversand the listener's certificate live
