Metadata-Version: 2.3
Name: retalk
Version: 0.0.8
Summary: Minimal, self-hosted, end-to-end-encrypted messaging bus for AI agents, services, and humans
Author: Xing Han Lu
Requires-Dist: vodozemac
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# retalk

Try giving a cron job a Signal account: you'll need a phone number, a QR
code, and a phone that stays on. Matrix gets you closer, but now you're
running a homeserver and coding against an SDK. All you wanted was for one
script to tell another script — or to tell *you* — that something happened,
without the server in the middle being able to read it.

retalk is that, as a CLI:

```sh
retalk send --peer bob "backup done: 412 files"
retalk receive --peer bob   # -> {"name": "bob", "text": "nice, all verified", ...}
```

No accounts and nothing to sign up for: an identity is a keypair created by
one command, and its fingerprint is its address. Every message is end-to-end
encrypted (Olm, via `vodozemac`), so the relay — a single process you can run
anywhere — holds only public keys and ciphertext, and deletes each message on
delivery ([what a hostile relay can and can't do](docs/server.md)). Output is
JSON lines, so it pipes.

retalk exists so AI agents, bots, and cron jobs can message each other, and
their humans, with the guarantees people expect from Signal. Two people in
two terminals works just as well.

## Install

<details>
<summary>Want to isolate your install? Create a virtual environment</summary>

A virtual environment keeps retalk and its dependencies separate from your
system Python. Create and activate one, then use any method below:

```sh
python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
```

</details>

<details>
<summary>New to uv? Install it first</summary>

[uv](https://docs.astral.sh/uv/) is a fast Python package manager. Install it
once:

```sh
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 uv
```

Then 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.

</details>

<table>
<thead><tr><th>Method</th><th>Command</th></tr></thead>
<tbody>

<tr>
<td>

`pip3`

</td>
<td>

```sh
pip3 install retalk
```

</td>
</tr>

<tr>
<td>

`pipx` (direct run)

</td>
<td>

```sh
pipx run retalk --help
```

</td>
</tr>

<tr>
<td>

`uv` (full project install)

</td>
<td>

```sh
uv add retalk
```

</td>
</tr>

<tr>
<td>

`uv` (install CLI global)

</td>
<td>

```sh
uv tool install retalk
```

</td>
</tr>

<tr>
<td>

`uvx` (direct run)

</td>
<td>

```sh
uvx retalk --help
```

</td>
</tr>

<tr>
<td>

`pip3` (install from git)

</td>
<td>

```sh
pip3 install git+https://github.com/xhluca/retalk
```

</td>
</tr>

<tr>
<td>

`uv` (install from git)

</td>
<td>

```sh
uv add git+https://github.com/xhluca/retalk
```

</td>
</tr>

</tbody>
</table>

This gives you the Python library (`import retalk`) and two commands:
`retalk` (the client) and `retalk-server` (the relay). Next: [Quickstart](#quickstart).

<details>
<summary>Installing from a development clone</summary>

```sh
git clone https://github.com/xhluca/retalk
cd retalk
uv sync
uv run retalk --help
uv run python -m unittest discover -s tests
```

Without uv, run `pip3 install -e .` inside the clone.

</details>

## Quickstart

Three steps get you messaging. You run them on your machine; your peer runs
the same three on theirs.

```sh
# 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-PRIVATE-PASSPHRASE>

export RETALK_USER=alice                             # which user to act as
export RETALK_PASSPHRASE=<YOUR-PRIVATE-PASSPHRASE>   # unlocks your keys
# (or skip the exports and pass -u / -p on each command)

# 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 listening
```

What the steps rely on:

- **Relay.** With no `--relay`, commands use the public test relay
  `https://retalk-relay.mcgill-nlp.org` — fine for trying retalk out, but
  best-effort with **no uptime guarantee**. Use your own with
  `retalk init --relay URL` (saved into the identity), the `RETALK_RELAY` env
  var, or `retalk config --relay URL` (owner-wide default). See
  [Run your own relay](#run-your-own-relay).
- **Reachability.** `init` publishes your public keys to the relay
  automatically, so peers can message you right away. Skip that with
  `--no-register` and publish later with `retalk register`.
- **Passphrase.** It encrypts your private keys at rest, and every later
  command needs it again (`-p` or `RETALK_PASSPHRASE` — there are no
  interactive prompts). For agents or throwaway identities,
  `--no-passphrase` stores keys unencrypted and warns you loudly.
- **Lost your ID?** `retalk id` reprints it (`retalk id --last` for the most
  recently created identity). `init` also prints a ready-to-paste invite for
  onboarding a peer who isn't on retalk yet.

<details>
<summary>Troubleshooting: <code>ssl.SSLCertVerificationError: … unable to get local issuer certificate</code></summary>

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:

```sh
open "/Applications/Python 3.10/Install Certificates.command"   # match your version
```

**Fix for any Python / OS** — point Python at a CA bundle via `SSL_CERT_FILE`:

```sh
python3 -m pip install certifi
export SSL_CERT_FILE="$(python3 -m certifi)"    # add to your shell profile
```

On macOS, `export SSL_CERT_FILE=/etc/ssl/cert.pem` (the system bundle) also
works.

</details>

## Commands

One line per subcommand, matching `retalk --help`. Run
`retalk <command> --help` for its flags and examples, or read the
[full command reference](docs/README.md#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. |
| `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. |
| `receive` | Decrypt pending messages (`--follow` to keep listening). |
| `history` | Replay messages saved by `send`/`receive --save-messages`. |
| `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](docs/README.md#selecting-the-user)). Results go to stdout; banners
and errors go to stderr, so output pipes cleanly into `jq` and friends
([scripting recipes](docs/README.md#scripting-the-cli)).

## Concepts

- **User** — anything with a keypair and a mailbox: a person, an AI agent, a
  service. Its **identity** is a local folder (`~/.retalk/NAME/`, created by
  `retalk 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](docs/README.md#contacts--add-verify-contacts)).
- **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](docs/server.md)).
- **Names** — display names and peer names are conveniences layered on top of
  fingerprints, never identity ([how names work](docs/README.md#creating-a-user)).

## Run your own relay

> [!NOTE]
> This is **optional**: the public test relay
> `https://retalk-relay.mcgill-nlp.org` works out of the box (no uptime
> guarantee — testing only). Self-host for anything you rely on.

```sh
retalk-server --host 127.0.0.1 --port 8766 --audience https://server.example.com
```

One 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](docs/README.md#running-a-server-on-the-internet).

## Try it locally

A full round trip on one machine, no internet needed: one relay and two users
in three terminals.

```sh
# terminal 1 — the relay (leave it running; local demo, so no --audience needed)
retalk-server --host 127.0.0.1 --port 8766
```

```sh
# 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
```

```sh
# 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:

```sh
retalk send --peer bob "one more"                       # ...and don't receive it
sqlite3 server.db 'SELECT body FROM messages LIMIT 1'   # base64 ciphertext
```

## Going further

The [docs index](docs/README.md) covers everything this page doesn't:

- [Sharing contacts](docs/README.md#sharing-contacts--share-import) —
  introduce saved peers to each other with `share`/`import` instead of
  copying fingerprints by hand.
- [Saving a message history](docs/README.md#messaging--send-receive-history) —
  opt-in, sealed-at-rest conversation logs with `--save-messages` /
  `RETALK_SAVE_MESSAGE=1`, replayed by `history`.
- [Selecting the user](docs/README.md#selecting-the-user) — `--user` vs
  `--dir`, `RETALK_HOME`, and how commands resolve which identity acts.
- [Filtering who can reach you](docs/README.md#filtering-who-can-reach-you) —
  `block` and `--peers-only`, applied before any decryption.
- [Scripting the CLI](docs/README.md#scripting-the-cli) — cron polling, `jq`
  pipelines, a tiny auto-responder.
- [Library usage](docs/README.md#library-usage) — the same features from
  Python via `retalk.User`.
- Protocol internals: [signed requests](docs/auth.md) ·
  [key management](docs/olm.md) · [server trust model](docs/server.md) ·
  [JSON data contract](docs/STANDARD.md).
