circle
Chat with your circle of friends, entirely in the terminal. Text, audio calls, and video.
Install
circle is distributed on PyPI and installed with uv:
uv tool install circle-chat
circle
uv tool install puts the circle command on your PATH; run circle to start. If you don't
have uv yet:
curl -LsSf https://astral.sh/uv/install.sh | sh
Join
circle is invite only. A friend already in the circle makes you a code from INVITE (^N);
each member can bring in 3 people.
- Run
circleand type:register. - Enter the invite code, your email and a passphrase of 12 characters or more.
- circle mails you a 6 digit code from
access@mail.gaibulloev.com; type it in. No mail? Type:resend, and look in spam. - Pick an @handle (
a-z,0-9,_, 3 to 20) and the name friends see. - Write down the 8 burn codes circle shows. Each one removes your second factor or resets your passphrase when the phone or the mailbox is lost.
- Write down the 24 recovery words, then type back the 3 circle asks for. They bring your identity and history to a new computer, and nobody, circle included, can give them back.
You land in the chat, already friends with whoever invited you. Your email stays private: friends
see your @handle. circle mails it only codes and account notices, from access@mail.gaibulloev.com
through Resend.
End to end
Messages and calls are encrypted on your computer for each of your friend's computers and your own. The server stores who wrote to whom and when, never the text, and your history lives on your computers, backed up under your 24 recovery words.
To check that nobody sits between you and a friend, open their chat, press ^Y and compare the
60 digits with theirs, face to face or on a call. VERIFY marks the key; if it ever changes, circle
stops sending to that friend until you compare again. An unverified friend's new key shows
KEY CHANGED once, as when they start on a new computer without LINK.
The encryption is vodozemac, the Olm implementation of Matrix. circle itself has had no outside audit.
Privacy
circle runs on one Supabase project that its maintainer operates. The server keeps:
- your email, a bcrypt hash of your passphrase, your @handle and the name friends see;
- friendships, friend requests, and which code let whom in;
- each computer's public keys, and each sign-in's time and label,
circle/<version> <hostname> <os>; Supabase's auth logs also keep the sign-in's IP address; - a message's sender, recipient and time, with one ciphertext per receiving computer, until the last of those computers fetches it;
- your history backup, encrypted under your 24 recovery words.
Friends see your @handle, your name and your public keys, never your email or your computers' labels. Calls go through LiveKit Cloud, which sees who joins and from which IP address; the audio and video are encrypted end to end. Mail goes through Resend, which sees your address and the mail itself: codes and account notices.
SETTINGS (^G), then DELETE ACCOUNT, removes your account and every row tied to it at
once. Supabase's and Resend's logs age out on their own. Your computers keep their own history
until you delete circle's data folder.
Sign in on another computer
Install circle there and sign in with your email and passphrase. The new computer then asks how to get your keys:
:linkshows 3 numbers. On a computer where circle already runs, open DEVICES (^D), press LINK and type those numbers. Your keys and history move over.:wordstakes your 24 recovery words and brings back your keys and history.:freshstarts a new identity. Friends see KEY CHANGED, and your old messages stay only on your other computers.
Each computer keeps its own session; SIGN OUT (^O) ends it on this one or on all of them.
SETTINGS (^G) makes new recovery words.
Forgot the passphrase
Type :recover, enter your email, then the 6 digit code circle mails you, then a new passphrase.
Update
uv tool upgrade circle-chat
A circle older than 0.3.0 can no longer send: it shows "circle 0.3.0 is out" under the composer until it is upgraded.
If that still reports the old version (uv can cache the package index), force a clean reinstall:
uv tool install --force --no-cache circle-chat
Check what you're running with uv tool list. To remove circle entirely: uv tool uninstall circle-chat.
Video quality by terminal
Video adapts to whatever terminal each person runs — no setup:
| Terminal | How video looks |
|---|---|
| iTerm2, kitty, WezTerm, Ghostty | HD pixels (Sixel or Kitty graphics) |
| Terminal.app, tmux, herdr | 2×2 colour blocks (works anywhere) |
Call quality is per person: whoever runs a graphics-capable terminal is seen in HD. Two commands tell you what to expect before a call:
circle termcheck # which video mode this terminal will use, and why
circle videocheck # show your own camera in the video panel, no call needed
On macOS the terminal asks for camera and microphone permission the first time; if a prompt is
missed, allow your terminal under System Settings → Privacy & Security. circle camcheck and
circle miccheck verify each. circle --help lists every command.
SETTINGS (^G) picks the camera, the microphone and the speaker for this machine. A camera
that is on switches at the pick. A microphone or speaker pick made during a call starts with the
next call: LiveKit's audio engine holds its devices until the call ends. A picked device that is
unplugged falls back to the default, and circle says so. The checks above test the picked devices.
Development
uv run pytest
runs the offline tests; with a .env that holds SUPABASE_SERVICE_ROLE_KEY it also runs the live
tests, which make and delete their own accounts on the project. The SQL checks the live project
cannot take, forced interleavings and one mutation per rule, run on a throwaway local Postgres:
uv run --python 3.12 --no-project --with pgserver --with "psycopg[binary]" python tests/local_pg/run.py
Release
Actions, then release, then Run workflow on main. Pick the part of the version that grows:
patch, minor or major. The run tests, builds, pushes the release commit and the vX.Y.Z tag
to main, publishes to PyPI and makes the GitHub release. dry_run stops after the build.
PyPI trusts the workflow through a trusted publisher on the circle-chat project: owner
bgaibull, repository circle, workflow release.yml, environment pypi. No PyPI token is kept.
Metadata
Release files for circle-chat 0.3.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| circle_chat-0.3.5.tar.gz | 65.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| circle_chat-0.3.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 136.6 kB
Release files / circle_chat-0.3.5.tar.gz
| Download URL | circle_chat-0.3.5.tar.gz |
|---|---|
| Size | 65.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
998aebcd719c072f9ed6da728b1a576e6fc179f9b2417ebdba0790ae2a253516
|
|
BLAKE2b-256 checksum How to use checksums |
f3b45d7b33946202a6265a6855a33ab707e87241279e7279cbf7e8c707cef8b3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / circle_chat-0.3.5-py3-none-any.whl
| Download URL | circle_chat-0.3.5-py3-none-any.whl |
|---|---|
| Size | 71.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a92150c22cdf77b0df6afc00b0a252b4667ea5e80000c4574a588d54a4442e37
|
|
BLAKE2b-256 checksum How to use checksums |
ba240fcbbf76d5974c76c3e0fe50ab0d38b94ed2f90c85d20d93382d621954a1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|