retalk 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
retalk-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,381 @@
1
+ Metadata-Version: 2.3
2
+ Name: retalk
3
+ Version: 0.0.1
4
+ Summary: Minimal, self-hosted, end-to-end-encrypted messaging bus for AI agents, services, and humans
5
+ Author: Xing Han Lu
6
+ Requires-Dist: vodozemac
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+
10
+ # retalk
11
+
12
+ Retalk is a small, self-hosted message bus for AI agents, services, and
13
+ people. Messages are end-to-end encrypted. The server only relays encrypted
14
+ blobs and publishes public keys.
15
+
16
+ The short version:
17
+
18
+ - The server never receives plaintext or private keys.
19
+ - Clients encrypt, decrypt, and sign every request.
20
+ - There are no accounts, passwords, registration flows, or bearer tokens.
21
+ - A user's ID is also the fingerprint clients use to verify that user's keys.
22
+ - The server still sees metadata: sender, recipient, timing, and message size.
23
+
24
+ Retalk uses `vodozemac` for Olm encryption. Everything else uses plain
25
+ HTTP+JSON and the Python standard library.
26
+
27
+ ## Concepts
28
+
29
+ A **user** is one participant with a keypair and a mailbox. A user can be an
30
+ AI agent, a bot, a service, or a person at a terminal.
31
+
32
+ An **owner** is the person or organization that runs one or more users. The
33
+ protocol does not model owners yet. Today, the protocol only knows users.
34
+
35
+ A **user ID** is a 32-character sha256 fingerprint of the user's public keys.
36
+ That ID is both:
37
+
38
+ - the address other users send messages to, and
39
+ - the key pin clients use to reject substituted keys.
40
+
41
+ Share user IDs over a channel the server does not control, such as chat,
42
+ email, or in person. A hostile server cannot safely swap keys for an ID,
43
+ because clients recompute the fingerprint and refuse mismatches with
44
+ `PIN MISMATCH`.
45
+
46
+ Display names work differently:
47
+
48
+ - A user's self-chosen name is encrypted inside each message. The server does
49
+ not see it. Clients show it with a `~` prefix because it is not verified.
50
+ - A peer name is your local label for a user ID, added with
51
+ `retalk add bob <id>`. It stays on your machine and takes priority over the
52
+ sender's self-chosen `~name`.
53
+
54
+ ## Install
55
+
56
+ ```sh
57
+ uv add retalk
58
+ ```
59
+
60
+ This installs the Python library:
61
+
62
+ ```python
63
+ from retalk import User
64
+ ```
65
+
66
+ It also installs two commands:
67
+
68
+ - `retalk` - user CLI
69
+ - `retalk-server` - relay server
70
+
71
+ For a global CLI install, use:
72
+
73
+ ```sh
74
+ uv tool install retalk
75
+ ```
76
+
77
+ For one-off runs:
78
+
79
+ ```sh
80
+ uvx retalk --help
81
+ ```
82
+
83
+ <details>
84
+ <summary>Other install options</summary>
85
+
86
+ With pip:
87
+
88
+ ```sh
89
+ pip install retalk
90
+ pipx install retalk
91
+ ```
92
+
93
+ From the latest repository version:
94
+
95
+ ```sh
96
+ uv add git+https://github.com/xhluca/retalk
97
+ pip install git+https://github.com/xhluca/retalk
98
+ ```
99
+
100
+ From a development clone:
101
+
102
+ ```sh
103
+ git clone https://github.com/xhluca/retalk
104
+ cd retalk
105
+ uv sync
106
+ uv run retalk --help
107
+ uv run python -m unittest discover -s tests
108
+ ```
109
+
110
+ Without uv, run `pip install -e .` inside the clone.
111
+
112
+ </details>
113
+
114
+ ## Start a server
115
+
116
+ Run the relay on a public machine:
117
+
118
+ ```sh
119
+ SERVER_PORT=8766 SERVER_AUDIENCE=https://server.example.com retalk-server
120
+ ```
121
+
122
+ There is no server-side user setup. Users publish their own public keys when
123
+ they first send or receive.
124
+
125
+ `SERVER_AUDIENCE` must exactly match the URL users connect to. Request
126
+ signatures are bound to that URL, so a mismatch causes signature failures.
127
+
128
+ For internet use, put TLS in front of the relay. Example Caddy config:
129
+
130
+ ```caddy
131
+ server.example.com {
132
+ reverse_proxy 127.0.0.1:8766
133
+ }
134
+ ```
135
+
136
+ ## Create a user
137
+
138
+ Run this once on each machine:
139
+
140
+ ```sh
141
+ retalk init -u --name alice-1 --server https://server.example.com
142
+ ```
143
+
144
+ `init` creates a local identity and prints the user ID. The private keys are
145
+ encrypted with a secret you choose. In scripts, set that secret with
146
+ `PICKLE_SECRET`; otherwise the CLI prompts for it.
147
+
148
+ Then exchange user IDs out-of-band and save your peer:
149
+
150
+ ```sh
151
+ retalk add bob <bob-user-id>
152
+ ```
153
+
154
+ Common commands:
155
+
156
+ ```sh
157
+ retalk id # print my user ID
158
+ retalk add bob <bob-user-id> # save a trusted local name
159
+ retalk send bob "hello" # send one encrypted message
160
+ retalk receive # drain my mailbox once
161
+ retalk receive --follow # keep polling and maintain keys
162
+ retalk receive --json # one JSON object per message
163
+ ```
164
+
165
+ ### Identity locations
166
+
167
+ Each identity lives in its own folder.
168
+
169
+ - `retalk init -u` creates `~/.local/share/retalk/default/`.
170
+ - `retalk init -u work` creates `~/.local/share/retalk/work/`.
171
+ - `retalk init ./alice` creates an identity at `./alice/`.
172
+
173
+ Every command finds its identity in this order:
174
+
175
+ 1. `-s DIR`
176
+ 2. `-u [NAME]`
177
+ 3. `STORE` environment variable
178
+ 4. user-level `default`, if it exists
179
+
180
+ Only `retalk init` creates an identity. Other commands fail if the selected
181
+ folder does not already contain one. Each acting command prints
182
+ `using <name> (<id>) from <dir>` to stderr so stdout stays clean for messages
183
+ and JSON.
184
+
185
+ Machines need a roughly correct clock. Server request signatures expire after
186
+ about 2.5 minutes.
187
+
188
+ ## Two-minute local demo
189
+
190
+ This demo runs on one machine. It creates two identities and a local relay.
191
+
192
+ Terminal 1:
193
+
194
+ ```sh
195
+ SERVER_AUDIENCE=http://127.0.0.1:8766 retalk-server
196
+ ```
197
+
198
+ Terminal 2:
199
+
200
+ ```sh
201
+ export SERVER_URL=http://127.0.0.1:8766
202
+
203
+ ALICE_ID=$(PICKLE_SECRET=alice-secret retalk init ./alice --name alice)
204
+ BOB_ID=$(PICKLE_SECRET=bob-secret retalk init ./bob --name bob)
205
+
206
+ PICKLE_SECRET=alice-secret retalk add bob "$BOB_ID" -s ./alice
207
+ PICKLE_SECRET=bob-secret retalk add alice "$ALICE_ID" -s ./bob
208
+
209
+ PICKLE_SECRET=bob-secret retalk receive -s ./bob
210
+ PICKLE_SECRET=alice-secret retalk send bob "hello bob" -s ./alice
211
+
212
+ PICKLE_SECRET=bob-secret retalk receive -s ./bob
213
+ # alice: hello bob
214
+
215
+ PICKLE_SECRET=bob-secret retalk send alice "hi alice, got it" -s ./bob
216
+ PICKLE_SECRET=alice-secret retalk receive -s ./alice
217
+ # bob: hi alice, got it
218
+ ```
219
+
220
+ The first `receive` publishes Bob's keys so Alice can start a session.
221
+
222
+ To inspect what the server stored:
223
+
224
+ ```sh
225
+ sqlite3 server.db 'SELECT body FROM messages LIMIT 1'
226
+ ```
227
+
228
+ You should see base64 ciphertext, not plaintext. Delivered messages are
229
+ deleted from the server.
230
+
231
+ ## Two machines
232
+
233
+ Machine A:
234
+
235
+ ```sh
236
+ retalk init -u --name alice --server https://server.example.com
237
+ # Share the printed user ID with Bob out-of-band.
238
+
239
+ retalk add bob <bob-user-id>
240
+ retalk send bob "hello from across the internet"
241
+ retalk receive --follow
242
+ ```
243
+
244
+ Machine B does the same with Bob's identity and Alice's user ID.
245
+
246
+ After `init -u`, commands use the user-level identity by default, so you do
247
+ not need `-s` flags.
248
+
249
+ ## Scripting
250
+
251
+ Drain the mailbox from cron:
252
+
253
+ ```cron
254
+ */5 * * * * PICKLE_SECRET=... retalk receive --json >> ~/inbox.jsonl 2>/dev/null
255
+ ```
256
+
257
+ Pipe messages into another tool:
258
+
259
+ ```sh
260
+ retalk receive --json | jq -r .text
261
+ ```
262
+
263
+ Tiny auto-responder:
264
+
265
+ ```sh
266
+ retalk receive --follow --json | while read -r msg; do
267
+ sender=$(jq -r .from <<<"$msg")
268
+ text=$(jq -r .text <<<"$msg")
269
+ retalk send "$sender" "you said: $text"
270
+ done
271
+ ```
272
+
273
+ ## Library usage
274
+
275
+ ```python
276
+ from retalk import User
277
+
278
+ alice = User(
279
+ "https://server.example.com",
280
+ pickle_secret="...",
281
+ name="alice-1",
282
+ store="alice/store.db",
283
+ )
284
+
285
+ print(alice.user_id()) # share out-of-band
286
+ alice.publish() # publish public keys to this server
287
+ alice.send("<bob-user-id>", "hello")
288
+
289
+ for sender, name, text in alice.receive():
290
+ print(name or sender, text)
291
+ ```
292
+
293
+ ## Delivery
294
+
295
+ Each message carries an ID inside the encrypted envelope. When the recipient
296
+ decrypts it, the recipient sends back an encrypted acknowledgement.
297
+
298
+ Senders keep ciphertext in a local outbox until it is acknowledged.
299
+ `maintain()` resends messages that have gone unacknowledged for 2 minutes.
300
+ `retalk receive --follow` runs `maintain()` automatically.
301
+
302
+ This makes server loss or server migration recoverable:
303
+
304
+ - clients republish missing public keys,
305
+ - senders re-upload unacknowledged outbox messages, and
306
+ - recipients drop duplicate ciphertext that they have already processed.
307
+
308
+ ## Key maintenance
309
+
310
+ Users publish one-time prekeys so peers can start encrypted sessions while
311
+ the user is offline.
312
+
313
+ `maintain()` keeps that server-side public key material healthy:
314
+
315
+ - it uploads 100 new one-time keys when fewer than 20 remain unclaimed,
316
+ - it rotates the reusable fallback key daily, and
317
+ - it resends unacknowledged outbox messages.
318
+
319
+ The fallback key is only used when the one-time key pool is empty. It keeps
320
+ new sessions available, but rotation limits how long the reusable key lives.
321
+
322
+ ## More docs
323
+
324
+ - [docs/auth.md](docs/auth.md) explains signed requests, the exact wire
325
+ format, replay protection, and why retalk does not use bearer tokens.
326
+ - [docs/server.md](docs/server.md) explains what the relay stores, what
327
+ metadata it sees, why mailbox calls are authenticated, and what a hostile
328
+ server can and cannot do.
329
+ - [docs/olm.md](docs/olm.md) explains one-time prekeys, fallback keys,
330
+ replenishment, and rotation.
331
+
332
+ ## Test
333
+
334
+ Run the full test suite from the repository root:
335
+
336
+ ```sh
337
+ uv run python -m unittest discover -s tests -v
338
+ ```
339
+
340
+ The tests use stdlib `unittest` and start their own local servers on ports
341
+ 8767-8769. They keep all state in temporary directories and do not touch real
342
+ stores.
343
+
344
+ CI runs the same discovery on every push and pull request. See
345
+ [tests/README.md](tests/README.md).
346
+
347
+ Coverage includes:
348
+
349
+ - bidirectional encrypted delivery,
350
+ - no plaintext in the server database,
351
+ - delivered mail deletion,
352
+ - key substitution refusal with `PIN MISMATCH`,
353
+ - fallback-key session setup when one-time keys are drained,
354
+ - key replenishment and fallback rotation,
355
+ - in-flight messages across fallback rotation,
356
+ - concurrent sends from two processes sharing one store,
357
+ - migration to a fresh server,
358
+ - delivery acknowledgements and outbox recovery,
359
+ - duplicate rejection, and
360
+ - replayed, stale, and cross-server signed-request rejection.
361
+
362
+ ## Release
363
+
364
+ Publishing is automated. Creating a GitHub Release triggers
365
+ `.github/workflows/publish.yaml`, which checks that the tag matches the
366
+ package version, runs the tests, builds with uv, and publishes to PyPI through
367
+ trusted publishing.
368
+
369
+ To cut a release:
370
+
371
+ 1. Bump `version` in `pyproject.toml` and `src/retalk/__init__.py`.
372
+ 2. Commit and push.
373
+ 3. Create a release whose tag is the version, optionally prefixed with `v`.
374
+
375
+ ```sh
376
+ gh release create v0.0.1 --title v0.0.1 --notes "first beta"
377
+ ```
378
+
379
+ Maintainers only need to do PyPI setup once: on pypi.org, add a trusted
380
+ publisher for project `retalk` pointing at this repository, workflow
381
+ `publish.yaml`, environment `pypi`.