Skip to main content

tmuxd

PyPI Python License

tmux + ttyd as a Python library: terminals that outlive the connection, that a program can type into and a person can open in a browser.

简体中文 · English · Changelog


Everyone has written ttyd tmux new -A -s work at some point. tmux keeps the session alive, ttyd makes it visible in a browser. It works — but the result has no handle on it. Who opened that session, in which directory, is it still alive? Want to feed it a command from the outside? SSH in and type tmux send-keys.

tmuxd is that command turned into something you import.

from tmuxd import Tmuxd

t = Tmuxd(port=12345, token="changeme")   # ttyd is up; tmux is not yet
s = t.session(id="id5", cwd="~/proj", cmd="claude")
s.send("run the tests", enter=True)
print(s.url)                              # http://127.0.0.1:12345/?arg=id5

Send that URL to anyone and their browser is in that terminal — watching, and able to take over the keyboard. A program hands out the work; a person watches it run.

Quick start

Needs tmux (≥ 3.0) and ttyd on the machine — see Requirements.

As a library — no server needed

pip install tmuxd          # zero runtime dependencies
from tmuxd import Tmuxd

with Tmuxd(port=12345, token="changeme") as t:
    s = t.session(id="deploy", cwd="/srv/app", cmd="./deploy.sh")
    print("watch it here:", s.url)
# ttyd goes with your process; the deploy is still running

Your process holds the instance, so there is nothing else to run.

From the command line — needs a server

pip install "tmuxd[server]"     # + fastapi + uvicorn
tmuxd start                     # both ports picked free, recorded in ~/.tmuxd/daemon.json
tmuxd new  -s work -c ~/proj
tmuxd send -s work "npm test" --enter
tmuxd url  -s work -o           # open it in a browser
tmuxd ls
tmuxd stop                      # stops the server; sessions keep running

A CLI command lives for milliseconds and can hold neither ttyd nor session state, so it asks a server that can. That is why the CLI and the server install together — why.

What makes it different

It is designed by subtraction. What was removed says more than what is left.

  • One session is one terminal. No windows, no panes — the multiplexing half of tmux is not used. Want more terminals? Open more sessions.
  • Write only, no reading. No capture, no run, no output stream, no recording, no event stream. Reading a terminal belongs to a person (open the URL — ttyd already does that better than any API could) or to ssh (clean stdout, a real exit code, binary safety). What stays is the one write action neither of them can do.
  • The facade is short-lived, the house is not. ttyd is a child of your process; the tmux server is nobody's child. kill -9 your program and the sessions carry on, with their working directory and command remembered.
  • It never touches your own tmux. Only the binary is probed, and the pool always opens on a dedicated tmux -L tmuxd. Your tmux ls is unchanged.
  • A person and a program type into the same terminal. Not a feature we built — tmux gives it away, which is why the whole design is arranged around it.
  • No permission tiers. Everything is read-write. Holding the token means holding a shell on that machine, so a read-only switch here would be a boundary that is not really there. Lock upstairs, where identity exists.

Two ways in

Library CLI
Holds the instance your process tmuxd serve
Needs a server no yes
Install pip install tmuxd pip install "tmuxd[server]"
Ports ttyd only ttyd + control API
Exposing it mount tmuxd.server.router() in the app you already run control API (random port)

Two ports, two audiences. One is ttyd and it is for peoples.url goes straight to a colleague. The other is the control API and it is for programs — JSON in, JSON out, seven endpoints. Both are free ports picked at startup: 7681 is ttyd's own default, which makes it the likeliest port for your own ttyd to be on, and claiming it would be picking a fight. They are recorded in ~/.tmuxd/daemon.json, which every tmuxd command reads, so you never type them. Driving another machine is ssh box tmuxd …, not a port on the internet.

Requirements

tmux ≥ 3.0 apt install tmux · brew install tmux · dnf install tmux
ttyd ≥ 1.6 bundled in the Linux wheels · macOS: brew install ttyd
Python ≥ 3.9
OS Linux, macOS

On Linux, pip install is enough. The wheels carry an upstream ttyd build for their architecture (x86_64, aarch64, armv7l — glibc and musl alike, since upstream links statically). A ttyd already on PATH still wins: that one can be fixed by apt upgrade and ours can only be fixed by a release of tmuxd.

On macOS you install ttyd yourselfbrew install ttyd. Upstream has never shipped a Darwin build (checked back to 1.7.3: ten musl ELFs and one win32.exe, every time), and Homebrew's is dynamically linked against five of its own packages, so re-shipping it would do badly what brew does well. macOS gets the py3-none-any wheel, which installs everywhere and simply expects ttyd on PATH.

Windows is not supported — tmux has no Windows build, and tmuxd imports fcntl.

If your machine is not ready — an architecture no wheel covers, no tmux, or you want a newer ttyd than the one we vendored — there is an optional tmuxd install. It fetches a checksum-verified ttyd from upstream (falling back to the bundled one when the network is down) and tells you the exact command for tmux on this machine, then records both paths in ~/.tmuxd.json so the library and the CLI find them next time. A ready machine never runs it, and Tmuxd() does not check whether you have.

tmuxd never adopts the tmux you use yourself: it runs its own pool on a dedicated socket, so tmux ls shows exactly what it showed before.

Development

pip install -e ".[dev]"
pytest                              # ~198 tests, ~50s
pytest tests/exact_targeting -v     # a single scenario

Tests run against real tmux, real ttyd and a real uvicorn — this project's whole value lives at the seam with those programs, and mocking them would test nothing. Each test gets its own tmux socket, so running the suite never disturbs a tmux you have open. They are organised by scenario, not by module.

License

Apache-2.0 — see LICENSE.

tmuxd drives ttyd (MIT) and tmux (ISC) as external programs; it neither vendors nor modifies them.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tmuxd-2.1.0.tar.gz (167.7 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

tmuxd-2.1.0-py3-none-manylinux_2_17_x86_64.musllinux_1_2_x86_64.whl (802.7 kB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64musllinux: musl 1.2+ x86-64

tmuxd-2.1.0-py3-none-manylinux_2_17_armv7l.musllinux_1_2_armv7l.whl (771.0 kB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARMv7lmusllinux: musl 1.2+ ARMv7l

tmuxd-2.1.0-py3-none-manylinux_2_17_aarch64.musllinux_1_2_aarch64.whl (808.4 kB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64musllinux: musl 1.2+ ARM64

tmuxd-2.1.0-py3-none-any.whl (47.0 kB view details)

Uploaded Python 3

File details

Details for the file tmuxd-2.1.0.tar.gz.

File metadata

  • Download URL: tmuxd-2.1.0.tar.gz
  • Upload date:
  • Size: 167.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for tmuxd-2.1.0.tar.gz
Algorithm Hash digest
SHA256 9d43d7a4a6d607a5dd3f16ed5adfb228d35a9512a8cc4c2812938f326706195e
MD5 99e506b0bb002c262d4c7d0e745d0d3b
BLAKE2b-256 4a2c9415c7447753cbbd55ed032b2dccdeae0e9b162b5730af07b34f34902718

See more details on using hashes here.

Provenance

The following attestation bundles were made for tmuxd-2.1.0.tar.gz:

Publisher: release.yml on memory-co/tmuxd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tmuxd-2.1.0-py3-none-manylinux_2_17_x86_64.musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for tmuxd-2.1.0-py3-none-manylinux_2_17_x86_64.musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 deb86608882c847b77c6f9c386343140dc097031533465ece14af8042a7b6bcc
MD5 90b1ef683bfa7001d60fc95cb0f77a57
BLAKE2b-256 10d9e8d2e9e1bd72cdaea2e140e742a51876fc9deb8298f8f8e0781fcc538d24

See more details on using hashes here.

Provenance

The following attestation bundles were made for tmuxd-2.1.0-py3-none-manylinux_2_17_x86_64.musllinux_1_2_x86_64.whl:

Publisher: release.yml on memory-co/tmuxd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tmuxd-2.1.0-py3-none-manylinux_2_17_armv7l.musllinux_1_2_armv7l.whl.

File metadata

File hashes

Hashes for tmuxd-2.1.0-py3-none-manylinux_2_17_armv7l.musllinux_1_2_armv7l.whl
Algorithm Hash digest
SHA256 c2aaf25a2dc177015dbc551913ea65bdc7c05eaae6a4a30d27a43cf9d8e5efb2
MD5 99c80ec1d12b538799d48f2e2bb0df38
BLAKE2b-256 b5bef8f93316f37628607be96f939bd09ba6d458205bf00476c079e6f9e3f8cf

See more details on using hashes here.

Provenance

The following attestation bundles were made for tmuxd-2.1.0-py3-none-manylinux_2_17_armv7l.musllinux_1_2_armv7l.whl:

Publisher: release.yml on memory-co/tmuxd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tmuxd-2.1.0-py3-none-manylinux_2_17_aarch64.musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for tmuxd-2.1.0-py3-none-manylinux_2_17_aarch64.musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 2d4267270e1ce9fd278d610ce853a1f2a8d428e83fae650f3d7da2e53ef60212
MD5 bb28446741df5c00a2f7b2b0d627324c
BLAKE2b-256 d78dc95f5257e26bc2ce709f95e8a9ccfe491d1cb2d9e6d9f619b08cd0458e0f

See more details on using hashes here.

Provenance

The following attestation bundles were made for tmuxd-2.1.0-py3-none-manylinux_2_17_aarch64.musllinux_1_2_aarch64.whl:

Publisher: release.yml on memory-co/tmuxd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tmuxd-2.1.0-py3-none-any.whl.

File metadata

  • Download URL: tmuxd-2.1.0-py3-none-any.whl
  • Upload date:
  • Size: 47.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for tmuxd-2.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f812e19f34a75677efce16363e519362ea8877ed3f868f26ee9d815e89ee2303
MD5 b6ef6cac9ac6b48da1c36eaf2a782d57
BLAKE2b-256 7b5ec9ea326ad01c6a4d8df7b54d0f8a52883bc0e4734270c12f41b32d52b967

See more details on using hashes here.

Provenance

The following attestation bundles were made for tmuxd-2.1.0-py3-none-any.whl:

Publisher: release.yml on memory-co/tmuxd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

2.1.0 This release

5 files

2.0.0

5 files

1.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page