Discord and Telegram to tmux session bridge for agent-driven workflows.
Project description
tetherly
Discord / Telegram channel ↔ tmux session bridge.
📖 Full documentation: docs/. Start with Getting Started, then Command Reference, Architecture, and Security. This README is a quick start.
Features
Same slash commands work in both Discord and Telegram:
/bind <session>: bind the current chat to a tmux session/unbind: release this chat from its tmux session/config: toggle plain-text auto-send (Discordauto_send:true|false, Telegram/config on|off) andtrust_chatto let every member of a chat run commands without enumerating user IDs (owner-only)/send <text>: send text plus Enter into the bound tmux session/key <Enter|Escape|Ctrl-C|Ctrl-D|Tab|Up|Down|Left|Right>: send a special key/tail [lines]: fetch recent tmux output/status: inspect the current binding and tmux session status- Quick keys (no arguments):
/enter,/esc,/ctrlc,/ctrld,/tab - Inline buttons on Codex / Claude Code alerts and on
/status//tail: tap[Enter],[Yes],[Refresh],[Stop]directly in chat — no typing needed
CLI helpers (run from inside a tmux session):
tetherly send --message <text>: forward a reply to whichever chat (Discord or Telegram) is bound to the sessiontetherly codex-stop/tetherly codex-permission-request: Codex hook handlers that route messages to the bound chattetherly claude-stop/tetherly claude-notification: Claude Code hook handlers (Stop, Notification)
A tmux session is globally unique across platforms — it can be bound to one Discord channel or one Telegram chat, not both. Run /unbind first to move it.
Requirements
- Python 3.11+
tmuxinstalled- A Discord bot token and/or a Telegram bot token (at least one)
- Discord: enable Message Content Intent if you want plain-text auto-send
- Telegram: created via @BotFather — full walkthrough in docs/platforms/telegram.md
Setup
Install once on your machine:
pipx install tetherly
# or: uv tool install tetherly
tetherly init
tetherly init is interactive. It writes ~/.tetherly/.env and lets you enable Discord, Telegram, or both. It also asks where to install hooks for Codex and Claude Code (separately, so you can pick a different scope per CLI):
- Global — writes once to
~/.codex/hooks.jsonor~/.claude/settings.json. Hooks fire in every project automatically. - Project — skip global hooks and run
tetherly install-hooks/tetherly install-claude-hooksinside each project. - Skip — don't touch the agent CLI's hooks.
Then start the bot(s):
tetherly
A single process runs whichever bots are configured. State lives at ~/.tetherly/state.json so one process serves every project.
Per-project usage
For each project you want to drive from chat:
tmux new -s <session-name>
# then in the bound chat:
# /bind <session-name> (Telegram)
# /bind session:<session-name> (Discord)
# /config on (Telegram) / /config auto_send:true (Discord)
If you chose Project mode during init, also run once per project:
cd <project>
tetherly install-hooks # Codex
tetherly install-claude-hooks # Claude Code
Both accept --global to (re)install user-level hooks instead.
Sending from inside a session
tetherly send --message "작업 끝났습니다"
cat result.txt | tetherly send --stdin
tetherly send --session t1 --message "..." # explicit session
tetherly send automatically routes to whichever chat (Discord or Telegram) the session is bound to. The legacy tetherly discord-send is still accepted as an alias.
Configuration
tetherly init writes everything you need. Advanced overrides live in ~/.tetherly/.env or shell env.
Discord
| Variable | Default | Notes |
|---|---|---|
DISCORD_BOT_TOKEN |
— | Bot token (required to enable Discord) |
TETHERLY_ALLOWED_USER_IDS |
— | Comma-separated user IDs |
TETHERLY_ALLOWED_GUILD_IDS |
— | Restrict commands to these guilds |
TETHERLY_ALLOWED_ROLE_IDS |
— | Allow members holding any of these roles |
TETHERLY_TEST_GUILD_ID |
— | Dev guild for instant slash-command sync |
Telegram
| Variable | Default | Notes |
|---|---|---|
TELEGRAM_BOT_TOKEN |
— | Bot token from @BotFather (required to enable Telegram) |
TETHERLY_TELEGRAM_ALLOWED_USER_IDS |
— | Comma-separated user IDs (required) |
TETHERLY_TELEGRAM_ALLOWED_CHAT_IDS |
— | Restrict commands to these chats |
Shared
| Variable | Default | Notes |
|---|---|---|
TETHERLY_STATE_PATH |
~/.tetherly/state.json |
Where bindings are persisted |
TETHERLY_DEFAULT_TAIL_LINES |
40 |
Default /tail line count |
TETHERLY_MAX_TAIL_LINES |
200 |
Cap for /tail |
TETHERLY_LOG_LEVEL |
INFO |
Logger verbosity |
A .env in the current working directory still overrides ~/.tetherly/.env. At least one of DISCORD_BOT_TOKEN or TELEGRAM_BOT_TOKEN must be set.
Agent CLI hooks
All hook handlers gate on the same flag: they only forward when the active tmux session has TETHERLY_NOTIFY_ON_FINISH=1. /bind sets it, /unbind clears it — projects without a binding stay silent even when global hooks are installed. None of the handlers return a blocking decision, so each CLI's normal prompts still appear in the terminal.
Codex
Installed via tetherly install-hooks (or globally during tetherly init).
Stop→tetherly codex-stopforwardslast_assistant_messageto the bound chat.PermissionRequest→tetherly codex-permission-requestforwards the tool/command/reason.
Claude Code
Installed via tetherly install-claude-hooks (or globally during tetherly init). Writes to ~/.claude/settings.json (global) or <project>/.claude/settings.json (project) — existing keys, other event entries, and other tools' commands are preserved.
Stop→tetherly claude-stopforwardslast_assistant_message. Skips whenstop_hook_active=trueso it doesn't double-fire if another hook is keeping Claude going.Notification→tetherly claude-notificationforwards the notificationmessage. Routespermission_promptnotifications with the permission keyboard (Yes/No), other types as plain text.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tetherly-0.3.0.tar.gz.
File metadata
- Download URL: tetherly-0.3.0.tar.gz
- Upload date:
- Size: 698.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8a3146cae94554cbb71af1c8da8be8c3c31db93ff35b707180b437239c4c22de
|
|
| MD5 |
0786df6e7d149bd919f417477f3ce36c
|
|
| BLAKE2b-256 |
e57a365f463af69a1f40053e9bfcbfb641e76d39c1aa72ce9038e0ef8f9b1daf
|
Provenance
The following attestation bundles were made for tetherly-0.3.0.tar.gz:
Publisher:
publish.yml on changhyeon363/tetherly
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tetherly-0.3.0.tar.gz -
Subject digest:
8a3146cae94554cbb71af1c8da8be8c3c31db93ff35b707180b437239c4c22de - Sigstore transparency entry: 1499712149
- Sigstore integration time:
-
Permalink:
changhyeon363/tetherly@b88787d8f336faf401eb2b964aaa16f43855f7c9 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/changhyeon363
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b88787d8f336faf401eb2b964aaa16f43855f7c9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tetherly-0.3.0-py3-none-any.whl.
File metadata
- Download URL: tetherly-0.3.0-py3-none-any.whl
- Upload date:
- Size: 36.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4154548c718fea1e7ab3f7604bcb0a61be2904d0a80eac949e5052706f9ac50d
|
|
| MD5 |
7ba3659c0f630b7a57731408fee5a9d5
|
|
| BLAKE2b-256 |
5acfd94ef9eeda5b973ad52b53aea845e6a0f8195feae4b9bf5531358aeedaec
|
Provenance
The following attestation bundles were made for tetherly-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on changhyeon363/tetherly
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tetherly-0.3.0-py3-none-any.whl -
Subject digest:
4154548c718fea1e7ab3f7604bcb0a61be2904d0a80eac949e5052706f9ac50d - Sigstore transparency entry: 1499712191
- Sigstore integration time:
-
Permalink:
changhyeon363/tetherly@b88787d8f336faf401eb2b964aaa16f43855f7c9 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/changhyeon363
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b88787d8f336faf401eb2b964aaa16f43855f7c9 -
Trigger Event:
push
-
Statement type: