eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1# πŸ€– eleven β€” the command-line client
2
3`eleven` is Eleven Messenger in your terminal: a full E2EE member, not a
4gateway or a bot. It signs in with the same passkey backup or invite links
5every device uses, holds its private keys in local files, and
6encrypts/decrypts everything itself β€” the servers see exactly what they see
7from any other device: ciphertext.
8
9It's a separate small binary (like `push/` and `home/`): members install the
10client, operators install the server.
11
12## Installing
13
14**From the Mac app** (how members get it): the Mac app bundles the CLI at
15`Eleven.app/Contents/Helpers/eleven` β€” universal, signed, and updated by
16Sparkle alongside the app (the "Embed eleven CLI" build phase in
17`ios/project.yml` compiles it into every build). Settings β†’ General β†’
18**Command-line tool** β†’ Install… drops a symlink where you choose
19(`/usr/local/bin` keeps it on PATH); the app is sandboxed, so the folder pick
20runs through the powerbox, and when a folder needs an administrator the pane
21hands you the one Terminal command instead:
22
23```sh
24sudo mkdir -p /usr/local/bin && \
25 sudo ln -sf "/Applications/Eleven.app/Contents/Helpers/eleven" /usr/local/bin/eleven
26```
27
28**From source** (developers):
29
30```sh
31go build -o eleven ./eleven
32```
33
34## Sign in with Eleven (`eleven signin`)
35
36One passkey, every space. `eleven signin` pulls the spaces saved in your
37Eleven account (the home server's passkey-protected backup) and signs the
38terminal into all of them at once.
39
40A terminal can't run a passkey ceremony, so the browser does it β€” the same
41handoff the native Mac app uses when its passkey provider can't help:
42
431. `eleven signin` mints an ephemeral keypair + nonce, opens a local loopback
44 listener, and sends your browser to home's restore page (payload in the
45 `#fragment` β€” the home server never sees it).
462. You approve with Face ID / Touch ID / your passkey. The page decrypts your
47 backup **in the browser**, seals the home session to the ephemeral key, and
48 redirects to `http://127.0.0.1:<port>` β€” hardcoded loopback, ciphertext all
49 the way.
503. The CLI unwraps it, pulls the vault, and writes one identity per space
51 under `~/.eleven/spaces/`. Archived spaces are skipped.
52
53SSH'd into a box whose browser is elsewhere? `eleven signin --paste` prints
54the link and takes the sealed code by paste instead of a redirect.
55
56The home session lands in `~/.eleven/home.json`, so later `eleven signin`
57runs re-sync silently β€” no browser. Note the trust profile: that file (like
58the native apps' Keychain copy) can fetch and open your whole backup, and the
59space session files are the space keys themselves. `~/.eleven` *is* your key
60ring; deleting it signs the terminal out of everything.
61
62## Joining one space by invite (`eleven join`)
63
64```sh
65eleven join # prompts for an invite link (and your name)
66eleven join --name "Paul" 'https://yourspace.11ch.at/join/…#…'
67```
68
69All three link shapes work:
70
71- **A fresh invite** makes you a new member with a new keypair.
72- **A friend's invite link** (the kind that opens a DM with them) joins you
73 *and* unlocks that chat β€” the DM key is re-wrapped to your new keypair
74 locally during the claim.
75- **An add-device link** (from your own account) signs the terminal in as
76 *you*: the link carries your keypair in its `#fragment`, so the terminal
77 becomes another of your devices and can read everything your others can.
78
79Your identity lands in `~/.eleven/spaces/<host>.json` (0600) β€” or pass
80`-session file` (`ELEVEN_SESSION`) to pin one identity file and ignore the
81store, for scripts. Session files **are** private keys: anyone who reads one
82can read those chats, and deleting your only device's key loses it for good β€”
83same as any device.
84
85Two honest gaps on a *fresh* join, both inherent to how keys travel: your
86roster name waits for the instance key, which another member's device grants
87once it's online (`eleven` retries the publish on every run until it lands β€”
88your messages carry your name regardless), and chats appear only once someone
89DMs you or adds you to a group. Joining a space where Eleven runs, the
90welcome DM shows up on its own.
91
92## Talking
93
94```sh
95eleven spaces # what this terminal is signed into
96eleven chats # your chats across every space
97eleven chat # open your most recently active chat
98eleven chat kitchen # …or match one by name (case-insensitive)
99eleven chat family/kitchen # space/name scopes when names collide
100eleven send eleven "what's the weather like for Saturday?"
101git log --oneline -5 | eleven send eleven # pipe stdin as the message
102```
103
104`eleven chat` is a live loop: incoming messages print as they arrive over the
105WebSocket, and anything you type sends on Enter (Ctrl-C or Ctrl-D leaves).
106DMing one of the space's bots this way is Claude-in-your-terminal over your
107own E2EE infrastructure β€” the bot can't tell the terminal from the app.
108
109## What v1 deliberately leaves out
110
111Rendered messages are new chat lines (plus `/me`-style action lines and a
112"[n button(s)]" note on messages carrying chips). Not yet rendered: edits,
113deletions, reactions, typing indicators, attachments, link previews β€” and
114there's no thread creation from the CLI (start chats in the app). Incoming
115lines also interleave with whatever you're typing, classic-IRC style; a
116proper composer line is a follow-up.
117
118## How it's built
119
120`internal/client` is the E2EE member client layer β€” join/claim
121(`join.go`, mirroring `web/static/join.js` headlessly), the WebSocket
122transport with thread-key unwrap, cursor-gated delivery and reconnect
123(`client.go`, the transport half of `internal/bot`'s runtime with the bot
124policy left out), and the invite-link parser (`invite.go`, mirroring
125`web/static/invite.js`). The bot sidecar still carries its own copy of the
126transport; moving it onto `internal/client` is the planned follow-up, so the
127two can't drift. Session files are byte-compatible across the CLI, the Go bot,
128and the Node sidecar.
129
130E2E coverage lives in `eleven_client_test.go` (root package, against the real
131router): all three join paths, name decryption, live delivery, and both-ways
132sends.
133