A GPU‑rendered, native multiplexer terminal for Windows, Linux and Mac. Rust server (superterminald) owns the terminals; a Bun 1.4.0 + React client renders them through gpuix (React bindings for Zed's GPUI) with a native Rust <terminal-grid> element.
Status: implemented and in daily use. Terminals live in a background server, so closing the window never kills a program and reopening it is instant. Windows (native client against a WSL server) and macOS (Apple silicon) ship as installers; Linux and WSLg run from source.
Grab the latest build from Releases:
- Windows:
Superterminal-<version>.msi, a per-user install with no admin prompt. The daemon runs in WSL2; setup is indocs/WINDOWS.md. - macOS:
superterminal-<version>-arm64.dmg. It is ad-hoc signed, not notarized, so open it the first time with right-click → Open.
- Terminals that outlive the window. A per-user server owns every shell. Quit the app, reopen it, and every tab, split and scroll position is back.
- Sessions, tabs and split panes, in a sidebar or a top strip. Right-click a tab for Split Right, Split Down and Close; drag dividers to resize.
- GPU rendering through Zed's GPUI, with block-art and box-drawing characters drawn from the cell itself, so logos tile without seams.
- One palette for everything (⌘K on macOS, Ctrl+K on Windows and Linux). Commands, tabs and sessions share one list. Type part of a tab's title, its directory, or anything visible on its screen to jump to it. Screen search runs locally and needs no key: the server returns the visible rows of every tab, including ones not on screen, and the matching line shows beside the row.
- Plain-words queries with Jev (optional, below).
- Plain-text
config.tomlfor font, theme, shell and keybindings;st config initwrites a commented copy of every default.
| Action | macOS | Windows / Linux |
|---|---|---|
| Command palette | ⌘K or ⌘⇧P | Ctrl+K or Ctrl+Shift+P |
| New tab / close tab | ⌘T / ⌘W | Ctrl+Shift+T / Ctrl+Shift+W |
| Split right / split down | ⌘D / ⌘⇧D | Ctrl+Shift+D / Alt+Shift+D |
| Go to tab 1–9 | ⌘1…⌘9 | Alt+1…Alt+9 |
| Switch session | ⌘⇧S | Ctrl+Shift+S |
Ctrl+K takes readline's kill-to-end-of-line on Windows and Linux. To give it
back, add "palette.commands" = "ctrl+shift+p" under [keybindings].
With a key, the palette also understands what you mean. "kill this tab", "split below", "make the text bigger", "the frontend one" and "the busy one" land on the right row even when no title contains those words.
The ranking comes from Jev, TypeSafe's System One model. Jev does not generate text. The palette sends the query and the list of rows as one typed question, and Jev returns a calibrated probability for each row. Local matching still answers every keystroke instantly. A settled query is ranked about a quarter of a second later, and a confident pick moves to the top, marked ✦. Enter always acts on the row under the cursor, so a slow or missing answer never gets in the way.
Providers. Jev is available directly from TypeSafe or through OpenCode Zen:
| TypeSafe (direct) | OpenCode Zen | |
|---|---|---|
| Key | console.typesafe.ai/keys, starts with apikey_ |
your OpenCode login is picked up automatically |
| Model | jev-1.13.0 |
jev-1.13 |
| Median latency on the palette test set | 290 ms | 654 ms |
Setup. Open the palette, run AI Settings…, paste a key and press Enter. The dialog saves it, runs a connection test and reports the provider, the model that answered and the latency. The provider row cycles Auto → TypeSafe → OpenCode Zen. Auto, the default, uses TypeSafe when it finds a TypeSafe key and otherwise falls back to Zen.
The same settings in config.toml:
[ai]
provider = "auto" # or "typesafe" / "zen"
# api_key = "apikey_…" # or set TYPESAFE_API_KEYA key is looked up in this order: api_key in config.toml, the key saved by
the dialog, the TYPESAFE_API_KEY or SUPERTERMINAL_AI_API_KEY environment
variable, then OpenCode's own login. The dialog stores one key per provider in
secrets.json next to the window state and never shows more than its last
four characters.
What leaves your machine. Without a key, nothing. With one, a palette
query sends the query, command titles, tab titles, working directories and
session names to the provider you picked. Screen text is not sent unless
you turn on [ai] screen_context, which is off by default. Even then only a
short excerpt of each tab goes out, with bearer tokens, API keys, private keys
and password=/token= values masked first.
Design notes: docs/plan/08-jev-palette.md
(the palette) and docs/plan/09-typesafe-endpoint.md
(providers). bun scripts/jev-palette-probe.ts --provider typesafe|zen re-runs
the 15 test queries against a live endpoint.
- Server (
superterminald): per-Surface PTYs overportable-pty,alacritty_terminalVT engine behind theVtEnginetrait, Snapshot/Delta fan-out with ack window and slow-client Snapshot, Workspace actor (Sessions → Tabs → Panes → Surfaces),workspace.jsonpersistence with cwd tracking, idle exit,stCLI (status,ls,probe,kill-server,dump-data,config). - Protocol (
st-proto, version 1.1): Control Plane (NDJSON) + Data Plane (u32 len | u16 type | postcard) on one sniffed socket, plus loopback TCP (--tcp,tcp://) for the Windows/WSL split. 1.1 added split Panes andsurface.screen_text. - Client: Bun + React chrome on gpuix
with a native Rust
<terminal-grid>element (run-shaping cache, selection, mouse reporting, scrollbar + lazy history, resize, IME/focus handling). Cell data never passes through JavaScript. The AI ranking lives in the client (packages/app/src/ai/); the server makes no network calls. - Platforms: Linux/WSLg and native Windows (MSVC build, Direct3D,
per-user MSI, no-console exe) against a WSL daemon; macOS on Apple silicon
(Metal, CoreText), packaged as an ad-hoc signed
.app+.dmg.
Run the Server as a release build for daily use
(cargo build --release -p st-server -p st-cli). A debug Server is for
development only: it is much slower, and with the default 10 000-line
scrollback the Server's memory is dominated by scrollback itself, roughly
40 MB per busy Surface at 168 columns — a debug build holds the same rows with
worse overhead. Release builds keep the budget in the handover
(docs/handover-reattach-memory-placement.md §B). Debug builds stay useful for
development and RUST_LOG tracing.
# prerequisites: rustup (stable + 1.97.1), Bun 1.4.0, GPU/dev libraries —
# full list in docs/DEV.md §1
git clone <repo> && cd superterminal
git submodule update --init
git -C vendor/gpuix submodule update --init --depth 1 --recursive zed
for p in patches/*.patch; do git -C vendor/gpuix apply "../../$p"; done
./scripts/run.sh # build what is missing, start daemon + client
./scripts/run.sh --no-build # run what is already builtThe window appears via WSLg (X11 backend for real decorations). Headless
checks anytime: ./target/debug/st status, st ls, st probe <id>.
# prerequisites: Rust stable MSVC, VS 2022 Build Tools with C++ workload,
# Bun 1.4.x, Git for Windows — details in docs/WINDOWS.md
git clone <repo>; cd superterminal
git submodule update --init
git -C vendor\gpuix submodule update --init --depth 1 --recursive zed
# apply patches\*.patch inside vendor\gpuix, then:
cd crates\st-native; cargo build; cd ..\..
copy target\debug\st_native.dll dist\superterminal-native.win32-x64-msvc.node
bun install# terminal 1 (WSL): superterminald --tcp 127.0.0.1:7171
# terminal 2 (Windows):
$env:SUPERTERMINAL_TCP = "127.0.0.1:7171"
$env:NAPI_RS_NATIVE_LIBRARY_PATH = "C:\...\superterminal\crates\st-native\dist\superterminal-native.win32-x64-msvc.node"
bun packages\app\src\app.tsxPackaged form: packaging/windows/ builds superterminal.exe +
side-by-side .node (bun build --compile, then editbin /SUBSYSTEM:WINDOWS
for no-console launch) and a per-user MSI (WiX 3.11). Full chain in
docs/WINDOWS.md.
xcode-select --install # Metal / CoreText come with the SDK, nothing else
rustup toolchain install 1.97.1
git submodule update --init && just vendor-patch
./scripts/run.sh # builds the daemon + native module, then launches
./scripts/run.sh --no-build # relaunch what is already builtscripts/env.sh picks the Darwin triple, toolchain and socket dir
($TMPDIR/superterminal-<uid>); the client falls back to Menlo when the
generic monospace face is missing. Packaged form: just dmg (or
just dmg --no-build) writes dist/superterminal.app and a .dmg, with the
daemon and st beside the client, assets/superterminal.icns for Dock and
Finder, and, with Xcode 26+ actool, a Liquid Glass Assets.car compiled
from assets/superterminal.icon. The bundle is ad-hoc signed only: no
Developer ID, no notarization, so a downloaded copy still trips Gatekeeper.
Bring-up notes and the bugs the first Mac run found are in docs/DEV.md
"macOS bring-up".
From docs/plan/07-milestones.md:
- M0 De-risk & skeleton — toolchain end to end, vendored gpuix 0.7.0
with the factory-hook + mouse-lease patches,
<hello-box>. - M1 Protocol + server core — frozen wire types, PTY engine,
attach/detach fan-out,
st probeon a live grid, history. - M2 Native grid gate —
<terminal-grid>paints live Deltas; app shows a real prompt. - M3 Input & interaction — keys, mouse + selection, clipboard, wheel/alt-screen, scrollbar paging, cursor shapes, resize.
- M4 Workspace + chrome — control-plane commands, sessions/tabs, palette, banners, reconnect, persistence; vertical sidebar default.
- [~] M5 Polish — config TOML, themes, exited UX, bell, cwd inheritance,
st status, fonts/emoji/HiDPI fixes and the macOS GUI bring-up all landed; macOS blur/traffic-light fit and multi-day dogfooding still open. - [~] M6 Packaging — Windows exe + per-user MSI and the macOS
.app+.dmg(app icon, Liquid Glass) ship and install; signing/notarization, Linux tarball, release CI, nightly perf still open. - [+] Beyond the plan — loopback TCP transport, Windows-client/WSL-server
split, gpuix 0.7.0 bump,
fixing-gpuix-layoutskill, remembered window size / tab layout / sidebar width (Client State, ADR 0008), split Panes with a right-click tab Menu and draggable dividers (ADR 0009, protocol 1.1), the unified palette with screen-text search, and Jev ranking through TypeSafe or OpenCode Zen (plans 08 and 09). - [ ] Out of scope (unchanged) — remote SSH, web client, ligatures, graphics protocols, scrollback search (the palette searches the visible screen only), signing/notarization.
| File | Purpose |
|---|---|
HANDOVER.md |
Entry point for an AI agent (or human) picking up implementation |
CONTEXT.md |
Ubiquitous language / glossary — use these words everywhere |
docs/DEV.md |
Building and running on Linux/WSL and macOS, debugging, bring-up notes |
docs/WINDOWS.md |
Windows client against a WSL server: install, build, packaging |
docs/config-example.toml |
Every config.toml key with its default, generated from the schema |
docs/plan/00-grilling.md |
The 58 decisions, with reasoning, that everything else depends on |
docs/plan/01-architecture.md |
Processes, threads, connections, crate layout, failure modes |
docs/plan/02-protocol.md |
Wire protocol: Control Plane (JSON) and Data Plane (binary) |
docs/plan/03-server.md |
superterminald: workspace actor, VT engine, PTYs, persistence |
docs/plan/04-client-native.md |
Rust native module: gpuix patch, Replica, <terminal-grid> painting & input |
docs/plan/05-client-app.md |
Bun/React chrome: tabs, sessions, palette, control‑plane client, packaging |
docs/plan/06-testing-perf-ci.md |
Test pyramid, VT conformance, perf budgets, CI |
docs/plan/07-milestones.md |
M0–M6 work breakdown with task ids, estimates, acceptance tests |
docs/plan/08-jev-palette.md |
The unified palette, screen-text search and Jev ranking |
docs/plan/09-typesafe-endpoint.md |
TypeSafe's direct endpoint as a second Jev provider |
docs/adr/ |
Architecture decision records (the hard‑to‑reverse choices) |
MIT.
