You don't have one project. You have six. Each one is a different directory, a different mental context, a different set of running shells. terminux gives each of them a home — a workspace — and keeps them alive and arranged exactly how you left them, even across restarts.
It's the workspace UX of cmux on a clean, auditable two-process architecture inspired by terax — rebuilt in Python for reliability over features. No accounts. No telemetry. No AI. Just a terminal that respects your flow.
uv sync && make frontend
uv run terminuxThat's it. You're in.
- Workspaces sidebar — a persistent list of named workspaces. Names track the active shell's working directory automatically (until you pin one).
- Tabbed terminals — every workspace has its own tabs, each a real PTY shell. Switch freely; background tabs keep streaming, no jank.
- Survives restarts — workspaces, tabs, window geometry, font size, and each shell's working directory all come back. Fresh shells, same layout.
- Keyboard-first — jump to a workspace with
Cmd/Ctrl+1..9, fuzzy quick-switcher onCmd/Ctrl+P, find-in-terminal, font zoom, and more. - Attention that finds you — background activity indicators; BEL or
OSC 9on an off-screen tab raises a badge that bubbles up to its workspace. - Drag & drop — reorder workspaces and tabs with live drop feedback (works even in WKWebView, where HTML5 DnD doesn't). Drop a file to paste its shell-quoted path.
- Local-first & hardened — loopback-only by default, per-session auth token, CSP and security headers, atomic versioned persistence.
uv sync
make frontend # build the web UI (needs Node; first run only)
uv run terminux # desktop window (pywebview)
uv run terminux --no-window # server only — open the printed URL in a browser--no-window is the dev/test path and a preview of the future web mode.
terminux ships as a self-contained desktop app — no Python or Node required to run the bundle.
make app # macOS .app → dist/terminux.app
make linux # Linux bundle (built in Docker) → dist/linux/terminux/terminux
make docker-run # run headless web mode on :8000Full platform notes (signing, Gatekeeper, X11, architectures) live in the documentation.
Docs are built with Zensical and live in docs/.
uv run zensical serve # live preview at http://127.0.0.1:8000
uv run zensical build # static site → site/Start with docs/index.md. The original vision, functional
spec, and technical spec are in notes/.
make frontend # build TS/Vite UI → src/terminux/web/static
make frontend-test # vitest unit tests (pure TS logic)
make test # pytest: unit, integration, e2e (Playwright)
make lint # ruff + ty + pyrefly + mypy
make formatThe e2e tier drives the served UI with a real browser (no pywebview); install
it once with uv run playwright install chromium.
Works today: workspaces sidebar (create / rename / reorder / close, auto status dot), tabs with multiple live terminals, real PTY shells over a per-terminal WebSocket, background streaming, structure persisted to disk (fresh shells on restart), per-session loopback token, macOS & Linux bundles.
Not yet: split panes, client/server detach, Windows PTY, scrollback persistence.
A Vite/TypeScript xterm.js web UI runs in a sandboxed pywebview window and talks
to a loopback Starlette/uvicorn backend that owns the PTYs and streams raw bytes
over per-terminal WebSockets. The frontend build output is committed to
src/terminux/web/static/, so the Python package runs with no Node toolchain.

