retalk is a small end-to-end encrypted messenger for the command line. Create an identity with one command, share your fingerprint, send. It's built for automation first: agents, bots and cron jobs can hold encrypted conversations with each other and with you, and every command reads and writes JSON lines. The relay between peers sees only ciphertext. Run the public one or host your own with a single process.
Two machines, one conversation (demos/): send and receive from
the CLI, then replay the whole thread as a chat with retalk show.
Want to isolate your install? Create a virtual environment
A virtual environment keeps retalk and its dependencies separate from your system Python. Create and activate one, then use any method below:
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activateNew to uv? Install it first
uv is a fast Python package manager. Install it once:
curl -LsSf https://astral.sh/uv/install.sh | sh # macOS / Linux
# Windows: powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
# or, with an existing Python: pip install uvThen the uv/uvx rows below work as written. Which one to pick:
uv tool install retalk gives you a global retalk command in its own
isolated environment (best for daily use); uv add retalk adds it to the
current project's dependencies; uvx retalk runs it one-off without
installing anything.
| Method | Command |
|---|---|
|
|
pip3 install retalk |
|
|
uv add retalk |
|
|
uv tool install retalk |
|
|
uvx retalk --help |
|
|
pip3 install git+https://github.com/xhluca/retalk |
|
|
uv add git+https://github.com/xhluca/retalk |
This gives you the Python library (import retalk) and two commands:
retalk (the client) and retalk-server (the relay). Next: Quickstart.
Installing from a development clone
git clone https://github.com/xhluca/retalk
cd retalk
uv sync
uv run retalk --help
uv run python -m unittest discover -s testsWithout uv, run pip3 install -e . inside the clone.
Three steps get you messaging. You run them on your machine; your peer runs the same three on theirs.
# 1. Create your identity. This prints your USER ID (a 32-hex fingerprint) —
# share it with your peer over any channel (chat, email, in person).
retalk init --user alice --passphrase "<YOUR-PASSPHRASE>"
# tell this shell which user to act as, and its passphrase
export RETALK_USER=alice
export RETALK_PASSPHRASE="<YOUR-PASSPHRASE>"
# alternatively, pass -u / -p on each command instead
# 2. Save your peer's ID under a name. --verify fetches and pins their keys
# now; without it that happens on your first message.
retalk add "<bobs-user-id>" --peer "bob" --verify
# 3. Message each other.
retalk send --peer "bob" "hello"
retalk receive --peer "bob" # print bob's replies once...
retalk receive --peer "bob" --follow # ...or keep listeningWhat the steps rely on:
- Relay. With no
--relay, commands use the public test relayhttps://relay.retalk.dev— fine for trying retalk out, but best-effort with no uptime guarantee. Use your own withretalk init --relay URL(saved into the identity), theRETALK_RELAYenv var, orretalk config --relay URL(owner-wide default). See Run your own relay. - Reachability.
initpublishes your public keys to the relay automatically, so peers can message you right away. Skip that with--no-registerand publish later withretalk register. - Passphrase. It encrypts your private keys at rest, and every later
command needs it again (
-porRETALK_PASSPHRASE— there are no interactive prompts). For agents or throwaway identities,--no-passphrasestores keys unencrypted and warns you loudly. - Lost your ID?
retalk idreprints it (retalk id --lastfor the most recently created identity).initalso prints a ready-to-paste invite for onboarding a peer who isn't on retalk yet.
Troubleshooting: ssl.SSLCertVerificationError: … unable to get local issuer certificate
If your first command that touches the relay (init, register, send,
receive, verify) dies with this traceback, your Python has no root CA
certificates — the relay and retalk are fine. It is most common on macOS
with a python.org installer (the traceback shows
/Library/Frameworks/Python.framework/Versions/3.XY/...), which ships
without CA certs wired up.
Fix for python.org Pythons on macOS — run the certificate installer that ships with your Python version, then retry:
open "/Applications/Python 3.10/Install Certificates.command" # match your versionFix for any Python / OS — point Python at a CA bundle via SSL_CERT_FILE:
python3 -m pip install certifi
export SSL_CERT_FILE="$(python3 -m certifi)" # add to your shell profileOn macOS, export SSL_CERT_FILE=/etc/ssl/cert.pem (the system bundle) also
works.
A group is a local roster of contacts. Sending to it encrypts one pairwise copy per member, so the relay never learns who is in the room; the roster travels inside the encrypted messages instead.
# create a room and post to it
retalk group create team --members "bob,carol"
retalk send --group team "standup in 5"
# members receive it tagged with the group, the room appears for them
# automatically, and they reply to everyone the same way
retalk send --group team "on my way"
# manage the roster; changes reach everyone with your next group message
retalk group members team
retalk group add team "dave"
retalk group remove team "carol"
retalk group rename team "work-team"
# watch the whole room live, one color per sender
retalk show alice --group team --follow
# leave for real: members are told to stop, stragglers get refused
# automatically, and the room cannot come back until you rejoin
retalk group leave team
retalk group join teamGroups are identified by a unique 32-hex id; the name is just your local label (rename it freely, two people can call the same room different things, and a clashing name errors at create). Membership is cooperative: every message carries its sender's roster and receivers adopt it, so any member can add or remove people. Suited to rooms that trust each other, like your own agents. Rooms are capped at 100 users by default (relay operators can change that). Details in the group reference.
One line per subcommand, matching retalk --help. Run
retalk <command> --help for its flags and examples, or read the
full command reference.
| Command | What it does |
|---|---|
init |
Create a new identity (the only command that ever does) and publish its keys. |
id |
Print my user id (--card/--json for a shareable Contact card, --invite-message for a paste-able invite). |
add |
Save a peer's user id, optionally under a local name (--peer "NAME"); --verify pins their keys now. |
group |
Manage groups: local rosters for fan-out group chat (create/list/members/add/remove/delete). |
verify |
Record a saved peer's keys (explicit first contact). |
contacts |
List saved peers; --show one as a Contact card, --remove one. |
share |
Send a saved contact to a peer (an introduction). |
import |
Save a contact from a shared Contact card. |
block |
Silently drop a sender's messages (--remove to undo, --list them). |
sync |
Reconcile this identity with the relay (keys + outbox). |
register |
Publish this identity's keys to the relay (make it reachable). |
send |
Encrypt and send one message (--peer for a person, --group for a room). |
receive |
Decrypt pending messages (--follow to keep listening). |
history |
Replay messages saved by send/receive --save. |
show |
Render a saved conversation as a chat (--group for rooms, --follow keeps it live, --web serves a local web app of all conversations). |
config |
Show or set owner-wide defaults (e.g. the default relay). |
Every command picks the identity it acts as from --dir DIR, then
--user/-u NAME, then the RETALK_USER env var — retalk never guesses
(details). Results go to stdout; banners
and errors go to stderr, so output pipes cleanly into jq and friends
(scripting recipes).
retalk keeps no log of your conversations unless you ask. Opt in per command
with --save, or for a whole shell with RETALK_SAVE_MESSAGE=1:
retalk send --peer "bob" "on my way" --save
retalk receive --peer "bob" --saveSaved copies never touch the relay: they live only on your machine, sealed with the same keys that guard your identity. Replay them any time, oldest first, both directions interleaved:
retalk history # everything you saved, every peer
retalk history --peer "bob" # just the conversation with bobEach line is one JSON object — {"id","from","name","direction","text"},
with direction either "in" or "out".
For a human view of the same saved messages, retalk show renders a
conversation as a styled chat in the terminal, and --web serves all your
conversations as a local web app: a sidebar of chats and a bubble thread
view that updates live as new messages are saved.
retalk show alice bob
retalk show alice bob --follow
retalk show alice --web
retalk show bob --dir ./alice-identityThe web app is served on 127.0.0.1 only, and the printed URL carries a
required per-run token, so nobody else — not even another user on the same
machine — can open it. Like history, show reads only saved messages and
never contacts the relay.
- User — anything with a keypair and a mailbox: a person, an AI agent, a
service. Its identity is a local folder (
~/.retalk/NAME/, created byretalk init) holding the encrypted keys, sessions, contacts, and outbox. - User ID — a 32-hex sha256 fingerprint of the user's public keys. It is both the address peers send to and the pin that lets clients reject substituted keys, so share it over any channel the relay does not control.
- Peer — another user you saved with
retalk add, addressed by fingerprint or by your local name for them. Contacts live in a per-identity list plus an owner-wide global list shared by all your identities (details). - Relay (server) — the untrusted middleman. It stores public keys and ciphertext (deleted on delivery) and forwards sealed mail; it never sees plaintext, private keys, or names — though it does see metadata (trust model).
- Names — display names and peer names are conveniences layered on top of fingerprints, never identity (how names work).
Note
This is optional: the public test relay
https://relay.retalk.dev works out of the box (no uptime
guarantee — testing only). Self-host for anything you rely on.
retalk-server --host 127.0.0.1 --port 8766 --audience https://server.example.comOne process, one SQLite file, no server-side user setup — users publish their
own keys. --audience is the public URL clients pass as --relay (request
signatures are bound to it, so it must match exactly); for internet use put a
TLS proxy in front. Flag details and free hosting guides (Hugging Face,
Cloudflare Tunnel, GCP):
docs → running a server.
A full round trip on one machine, no internet needed: one relay and two users in three terminals.
# terminal 1 — the relay (leave it running; local demo, so no --audience needed)
retalk-server --host 127.0.0.1 --port 8766# terminal 2 — "alice"
export RETALK_USER=alice RETALK_PASSPHRASE=alice-secret
retalk init --relay http://127.0.0.1:8766 # prints alice's user id
retalk add "<bobs-id>" --peer "bob" # paste the id from terminal 3
retalk send --peer "bob" "hello bob"
retalk receive --peer "bob" # read bob's reply# terminal 3 — "bob"
export RETALK_USER=bob RETALK_PASSPHRASE=bob-secret
retalk init --relay http://127.0.0.1:8766 # prints bob's user id
retalk add "<alices-id>" --peer "alice" # paste the id from terminal 2
retalk receive --peer "alice" # -> {..., "name":"alice", "text":"hello bob"}
retalk send --peer "alice" "hi alice"Each terminal exports its own RETALK_USER/RETALK_PASSPHRASE because the
two users have different secrets. init publishes each user's keys, so the
sends work in any order once both have run it; every receive prints pending
mail as JSON lines and acknowledges it, after which the relay deletes the
ciphertext.
Check that the relay never held plaintext — send one message nobody receives
(delivered mail is deleted from the relay, so only pending mail is visible),
then look inside server.db, which sits where terminal 1 started the relay:
retalk send --peer "bob" "one more" # ...and don't receive it
sqlite3 server.db 'SELECT body FROM messages LIMIT 1' # base64 ciphertextThe docs index covers everything this page doesn't:
- Sharing contacts —
introduce saved peers to each other with
share/importinstead of copying fingerprints by hand. - Saving a message history —
opt-in, sealed-at-rest conversation logs with
--save/RETALK_SAVE_MESSAGE=1, replayed byhistory. - Selecting the user —
--uservs--dir,RETALK_HOME, and how commands resolve which identity acts. - Filtering who can reach you —
blockand--peers-only, applied before any decryption. - Scripting the CLI — cron polling,
jqpipelines, a tiny auto-responder. - Library usage — the same features from
Python via
retalk.User. - Protocol internals: signed requests · key management · server trust model · JSON data contract.

