Dark Terminal Chat
Plain English terminal chat. No banner, logo, animation, or forced color. Python 3.10+.
pip install --upgrade dark-chat
dark-chat
iPhone / iSH installation and login.
Temporary chat
dark-chat --new
dark-chat --chat
The creator gets a secret invite code. Share it privately with the other person; they enter it at the prompt. Both commands use the hosted relay automatically. Each room has two slots. Temporary messages are never saved or queued. A disconnected participant has 60 seconds to reconnect. When both participants leave, or the server restarts, the old invite stops working.
Chats and offline messages
Both people start on their own devices:
dark-chat
On the first launch, choose your account password (at least 12 characters) and a username. Usernames have 3-24 letters, numbers, dots, dashes or underscores and start with a letter. Later launches ask only for your account password. Existing accounts keep their keys and conversations.
After login, only the message prompt appears. Use /chat when you want to choose a conversation, and / or /help when you need commands. Profile details and saved contacts appear only when requested.
The app gives you a separate contact password. Share your username and contact password privately with people who should be able to start a chat with you. The contact password stays masked at startup and in /profile; use /profile --show when you want to reveal it to share. Keep your account password private; it unlocks your identity and is never needed by a friend. If a username is taken, choose another with /profile YOUR_USERNAME.
To start a new conversation:
/chat alice
Contact password for @alice: ************
Hello.
The contact password is hidden while typing. After the first introduction, /chat alice opens the saved conversation without asking again. Your friend must have opened the app at least once but can be offline when you write.
Use /password to change the password people use to start a new chat with you. Enter any 1-128 printable characters, then confirm it; both prompts are masked. One-character passwords, spaces and non-English letters are supported. Press Enter at the first prompt to generate a random password instead. /cancel cancels the change. /profile --show reveals the saved password when you want to share it. A change made offline takes effect on the relay after reconnecting. Your account password and existing chats stay the same; changing a contact password does not revoke people who already saved your identity. Short, predictable contact passwords are easier for others to guess.
Use /username NEW_NAME to change your username, or /username to enter it at a prompt. The prompt and new messages use the new name, including after restarting. Your keys and saved conversations remain the same. An offline rename is registered after reconnecting, provided the name is still available. Existing contacts may keep their own saved label for you.
Use /login-password to change the account password that unlocks this device. Enter your current password, then the new password twice; all three prompts are masked. Account passwords still require 12-1024 characters. The identity file is replaced atomically, preserving private keys, contacts, history and queued messages. /cancel cancels the change. Run /backup afterward to create a backup protected by the new password. Existing backup copies retain their old passwords and remain valid identities.
To open an existing conversation, type /chat, then enter its number:
/chat
1. alice (2 new)
2. bob
Number or username: 1
/chat 1 opens it directly. /chat alice also opens a received conversation when its displayed name is unique. If several chats share a name, choose a number. Incoming messages show the short command to open their conversation. Unread counts stay saved until you open the chat. Enter a new username in the same menu to start another chat, or use /cancel to go back. After typing /chat , the arrow keys can select a suggested saved contact.
Long IDs remain available through /id and /chat FULL_ID [local_name] for manual fingerprint verification and older contacts. They are no longer part of the normal chat flow. Names shown on incoming messages are display labels; verify a new contact using their privately shared username/password or full fingerprint. If moving your identity to another device, restore its backup first.
Outgoing messages are saved encrypted on your device before sending. Sending adds no receipt line to the conversation. The outbox wakes the network task immediately and retries automatically in order. Permanent-chat connection loss and reconnection are silent, so an unstable network does not fill the conversation with status messages. Incoming authenticated messages appear as soon as they are saved, before waiting for the relay's acknowledgment response. The client drains active batches without an extra one-second pause, polls idle inboxes every 0.2 seconds plus network time, and caps requests below the relay's rate limit. Reconnect attempts start after 0.5 seconds and back off to at most eight seconds on repeated failures. These timings do not guarantee network latency or uninterrupted Internet service.
A full mailbox pauses that contact's queue while other conversations continue. The relay keeps encrypted messages until the recipient has saved and acknowledged them, for at most 30 days. Unconfirmed outgoing messages older than 30 days stop retrying and appear as [unconfirmed: expired] in local history; copy and resend their text if needed. They do not block newer messages. The last 50 locally saved messages appear when opening a conversation. /history shows them again.
The password unlocks this device's identity; it is never sent to the server. Logging in on another computer requires your encrypted identity backup as well as the password. Use one active device per identity; multi-device history synchronization is not provided.
Commands
Type / to show only /exit and /help. Start typing more letters to see matching commands, with no descriptions. Each letter filters the suggestions: /p shows /profile and /password, and /pa shows /password. Enter completes and runs the first suggestion, even if the command is only partly typed. Arrow keys can select another suggestion. /help explains all commands. / followed by Enter selects /exit; typing exit also works.
| Command | Action |
|---|---|
/exit or exit |
Close the chat and clear the terminal screen and supported scrollback |
/clear or clear |
Clear the terminal screen and supported scrollback, then keep chatting |
/help |
Show commands |
/chat username |
Open a saved chat, or enter the contact password for a new one |
/chat or /chat number |
Choose a chat or open one by its number |
/back |
Leave the selected conversation, clear the terminal, and keep dark-chat open |
/profile [username] |
Show your username with a masked contact password; optionally change the username |
/profile --show |
Reveal your contact password to share privately |
/password |
Change your contact password through masked prompts |
/username [name] |
Change your username while keeping your identity and chats |
/login-password |
Change the account password through masked prompts |
/history |
Show the selected conversation's recent saved messages |
/exit exits dark-chat and clears the terminal; saved conversations remain. exit works in the chat, chat selection and contact-password prompts. The command menu uses the terminal's normal colors and a simple selection highlight.
clear and /clear remove text from the terminal without deleting saved messages or leaving the conversation. They also work before choosing a chat. Other commands, including /backup and /delete, appear as suggestions after typing their prefixes. Deletion requires the explicit -f flag.
Use /back to leave only the current conversation. Use /exit or exit to close dark-chat entirely. Neither command deletes saved chats.
Additional commands remain available: /cancel cancels selection or password entry, /contacts lists chats, /id shows your permanent ID, and /backup saves an encrypted identity backup. Ctrl+C closes the app. Temporary rooms also support /who to list participants.
Client updates
Interactive startup checks published PyPI releases in the background. If a newer stable release supports your Python version, a single short notice shows how to update. If no update is available or the check fails, startup stays quiet. Checking does not wait before opening the chat or delay shutdown.
Close the chat with exit, then run:
dark-chat --update
dark-chat
The update command uses pipx for a pipx-managed installation, or pip through the active Python for other installations. It updates only when explicitly requested and keeps your identity and conversations. After installation, a fresh process reads the actual installed version and prints the old and new versions, for example Updated dark-chat: 3.4.0 -> 3.4.1. If the version is unchanged, it prints Already up to date: dark-chat 3.4.1. Normal pip installation also works: python -m pip install --upgrade dark-chat. Local builds that are newer than the published PyPI release do not show a downgrade notice.
For pipx the shell command is pipx upgrade dark-chat. WSL, Windows, iSH and other environments have separate installations; update in each environment you use. If --update is unrecognized, that executable predates the update command. Use pipx upgrade dark-chat or the install environment's python -m pip install --upgrade dark-chat first. Only published releases can be installed from PyPI; newer source code on another computer does not update the public package.
Run dark-chat --diagnose to show the exact version, command path, Python path, package path and installer without logging in or opening your identity. On older clients use dark-chat --version and command -v dark-chat (Windows PowerShell: Get-Command dark-chat). If several copies are installed, check type -a dark-chat in WSL/Linux/macOS and clear a stale shell command cache with hash -r after updating. Restart any running chat process to load the updated code.
Deleting saved history
In permanent chat, /delete -f removes the selected chat, contact, saved history and queued messages from both people's clients and returns an open conversation to the main prompt. An unfinished draft in the deleted conversation is discarded too. Both people must install client 3.4.1 or later and restart it; older clients keep their local contact entry. The relay can remain on version 3.2.0 or later. If either person is offline, the signed deletion is saved and applied on reconnection before old messages can be retried. The conversation's deletion counter remains locally to reject stale messages. A repeated deletion receipt cannot erase a conversation deliberately started again afterward. This does not block the person: a new message in the new conversation generation can start another chat. Plain /delete only shows the required command. Before client 3.2.2 the destructive history action was named /clear; update the client before using /clear for terminal-only cleanup.
If a contact disappears while you are choosing a numbered chat, selection is canceled and the typed number is discarded. Open /chat again to choose from the updated list.
Clearing cannot erase separate backups, terminal recordings, screenshots, modified third-party clients or operating-system memory. Scrollback clearing depends on the terminal; it is not secure forensic erasure.
Identity backup and recovery
While logged in, run /backup. Move the resulting encrypted file to a safe location on another device. It contains private keys, encrypted with your existing password. It does not contain conversation history. Anyone with both the backup and password can use your identity and decrypt messages addressed to it.
On a new device:
dark-chat --restore /path/to/identity-backup.json
dark-chat
Restore refuses to overwrite an existing identity. There is no server-side password reset or private-key recovery. For a complete local backup, close all clients and copy the whole ~/.dark-chat directory. History is encrypted and tied to the identity. DARK_CHAT_HOME selects a different profile directory.
Security model
Permanent messages use libsodium sealed boxes through PyNaCl and Ed25519 signatures. Clients verify the sender's key fingerprint and signature and the intended recipient. Authentication signs a fresh server challenge; old responses cannot log in again. Message IDs and durable acknowledgments suppress retries and replay. The server never receives private keys, account passwords, contact passwords, local contact aliases, or decrypted message text.
Each generated contact password has 72 random bits. The username and password derive an AES-256-GCM key that protects the public contact card; the relay stores the encrypted card and a one-way lookup token. Clients decrypt and authenticate the card before saving the keys. Share the contact password through a trusted channel. Anyone who has it can resolve that contact's keys; changing a username removes its old directory entry but does not revoke saved conversations. The relay sees usernames and directory lookups. Your own contact credentials are encrypted in the local history database; copy the entire profile directory to preserve them alongside history.
The identity file uses AES-256-GCM with a random salt/nonce and scrypt (N=131072, r=8, p=1) to protect against offline password guessing. Local message bodies and contact names use AES-256-GCM under an independent random storage key kept inside the encrypted identity. Files are owner-only on Unix; Windows uses the profile directory's inherited access permissions, plus encryption. Names/text received from peers are stripped of terminal control characters.
Temporary rooms continue to use AES-256-GCM with keys derived from their random 32-byte invite codes. A shared invite grants room access; it is not a permanent identity. Version 3 supports version 2 temporary clients. Permanent accounts require a version 3 server. Username introductions require both client and relay version 3.2.0 or later. Saved conversations and manual ID lookup continue to work with older version 3 relays.
Public Internet connections require verified WSS/TLS. Account mode refuses insecure remote/LAN connections even if --allow-insecure is supplied. Local loopback WS is available for development.
The relay sees IP addresses, public IDs/keys, sender/recipient relationships, timing, sizes, and delivery metadata. The local database exposes contact IDs and message metadata, although bodies and names are encrypted. There is no anonymity guarantee, forward secrecy against later compromise of a recipient's long-term private key, independent security audit, or protection from malware on an unlocked device. Sealed messages are signed, so they are not deniable. Terminal clearing does not change these limits.
Hosting
The default relay is wss://zubayr.alwaysdata.net/dark-chat/ws. Advanced options remain available but are hidden from the short help: --server, --name, --ca, --allow-insecure, --password-file, and --restore. DARK_CHAT_SERVER overrides the relay; DARK_CHAT_CODE provides a temporary invite for automation. --password-file reads a protected local file for automation; do not put passwords directly on a command line.
dark-chat-server --database /persistent/private/path/mailbox.db
dark-chat --server ws://127.0.0.1:8080/ws --new
Run one relay process. Its default database is ~/.dark-chat-server/mailbox.db, outside release directories; DARK_CHAT_DATABASE overrides it. Never put it in .deploy/releases. The server keeps ciphertext only and bounds storage to 1,000 identities, 1,000 pending messages overall, 100 pending messages per recipient, and 10,000 pending-message/delivery-receipt rows. A full mailbox returns an error while the encrypted outbox stays on the sender's device. Delivered ciphertext is deleted after acknowledgment; replay receipts expire after 30 days. Pending mail and public identities survive normal restarts and upgrades. Availability and backups of the server database remain the operator's responsibility.
AlwaysData setup and automatic updates. Package distribution.
Development
python -m venv .venv
python -m pip install -e . build twine
python -m unittest discover -s tests -v
python -m build
python -m twine check dist/*
Activate the virtual environment before installing. The GitHub Actions matrix covers installed wheels on Linux (Python 3.10, 3.12 and 3.14), Windows (Python 3.12 and 3.14) and macOS (Python 3.12). Only passing commits advance the deployment branch; AlwaysData tests the candidate again before activation. Tests cover offline delivery, database persistence, signatures, challenge replay, recipient isolation, key substitution, local encryption, credential changes, CLI flows, and slash completion. A configured CI matrix or local test run does not establish physical iSH or live production behavior.
The earlier Node.js experiment is reference source only. The Python package is the application.
Metadata
Release files for dark-chat 3.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| dark_chat-3.4.1.tar.gz | 85.5 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dark_chat-3.4.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 128.8 kB
Release files / dark_chat-3.4.1.tar.gz
| Download URL | dark_chat-3.4.1.tar.gz |
|---|---|
| Size | 85.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3b2584c93ba7ca21c785c003e22d373e17ec17ab900d8dd36962ae23124a55b4
|
|
BLAKE2b-256 checksum How to use checksums |
9891d45ffc2c760b2d21c4cdda48f0b278b6197de3b7ec576a9a92881c8f5c0b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logRelease files / dark_chat-3.4.1-py3-none-any.whl
| Download URL | dark_chat-3.4.1-py3-none-any.whl |
|---|---|
| Size | 43.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
29db65b8a439753f4f12ff7435c5a1ffecae343d2ad9a1efec90ce6810e18388
|
|
BLAKE2b-256 checksum How to use checksums |
3632540a0eb88394eb454360656590d27f13001b0c7dbf6bb485d7c3cedc21d5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logSecurity for dark-chat 3.4.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for dark_chat-3.4.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.