Skip to content

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-address chooses where to listen, status reports where it is accepting, and off closes the port and drops the browsers on it.
  • A web_servers entry 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.

A browser is admitted by a token, and the token arrives as a URL:

bash
thinkterm cli web-token mint --label phone --ttl 12h

Whoever 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.

  • --ttl takes 30m, 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 to Until revoked.
  • web-token list shows 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-only prints 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 ​

RowWhat it does
Allow browser accessOpens or closes the port on the session server
Reachable from other devicesListens on every address with its own certificate, instead of loopback only
Access link › Copy linkMints a link and copies it
Link expiresHow long the next copied link lives: 1 hour, 8 hours (the default), 1 day, 7 days or Until revoked
Scan on your phone › Show codeMints a link and shows it as a QR code
Links you have handed outEvery live link, with a Revoke button each
Every link › Revoke allCuts 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:

lua
config.web_servers = {
  {
    bind_address = '127.0.0.1:8088',
    token_file = wezterm.home_dir .. '/.local/share/thinkterm/web-tokens.json',
  },
}
FieldDefaultMeaning
bind_address127.0.0.1:8088The address and port to listen on
pem_private_keyunsetA PEM private key. With pem_cert set too, the port serves https and wss
pem_certunsetA PEM certificate
pem_caunsetA PEM CA chain
static_dirbeside the installed programWhere the page's files are
token_fileunsetWhere minted tokens are kept, as digests
allowed_originsthe listener's own addressOrigins allowed to open the connection
require_tls_off_loopbacktrueRefuse 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, ~/dir or /dir, under Add workspace…; it comes with a main Thread
  • 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.

Right-click offers the desktop's menus, less what the server has no operation for:

OnOffers
A paneCopy, Paste, Split Right, Split Left, Split Down, Split Up, Frontend access › A · Shared (tmux-like) / B · Handoff (exclusive)
A tabClose Tabs to Left, Close Tabs to Right, Close Other Tabs, New Terminal Tab to Right, Zoom Pane
A ThreadPin Thread or Unpin Thread, Rename Thread…, Delete Thread, Mark as Unread
A ProjectRename Project…, New Thread, Collapse / Expand Threads, Archive Project…, Remove Project
An archived ProjectUnarchive 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 Space
  • Tabs and Panes — those in the window on show
  • Switch Space — the Spaces
  • Commands — 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 ​

KeysAction
Cmd+C or Ctrl+Shift+CCopy the selection
Cmd+V or Ctrl+Shift+VPaste
Ctrl+Shift+EnterSplit right
Ctrl+Shift+\Split down
Ctrl+Shift+ZZoom the pane, or unzoom it
Ctrl+Shift+FFit the tab to this window
Ctrl+Shift+TTake the terminal over
Ctrl+Shift with an arrowFocus the neighbouring pane, without moving the desktop's focus
Cmd+=, Cmd+-, Cmd+0The focused pane's font size: larger, smaller, reset
⌘K / Ctrl+KThe 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, Ctrl and Alt, the four arrows, Home, End, PgUp, PgDn, and - / | ~. Ctrl and Alt are 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.

SectionSettingChoices
GeneralLanguageFollow System — the browser's languages — or one of ThinkTerm's languages
GeneralScrollingSmooth (default), following a finger or trackpad by the pixel, or Stepped, a row at a time
GeneralSearch shortcut⌘K (default), ⌘⇧P, Ctrl+Shift+P. Off a Mac, ⌘ means Ctrl
AppearanceColor schemeFollow the desktop (default), using the scheme the server is configured with, or any built-in scheme
AppearanceThemeDark (default), Light or Follow System, for the page around the terminal
AppearanceFont sizeFollow the desktop, so a cell here matches one there, or Fixed size, 6 to 72 in half-point steps
Sidebar & PluginsReveal the sidebar on hoverOn by default
Sidebar & PluginsReset sidebar widthPuts the sidebar back to its default width
Sidebar & PluginsAgents panel, SnippetsWhether the right-hand panel offers them
Sidebar & PluginsReload plugins, and each pluginA 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:

ParameterEffect
?lang=de-DEThe page's language
?theme=lightlight, dark or system
?font=14A 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.