LAN command-line chat with end-to-end encryption and ephemeral messages. Runs entirely in your terminal — no servers, no accounts, no logs.
inkchat is currently a 0.0.x preview release. The CLI and protocol are usable for local/private LAN sessions, but compatibility may still change before 1.0.
ic host # one person creates a channel
ic join 10.0.0.5:7700 # everyone else joins (uses default passphrase)
Default passphrase: if neither side passes
--pass, both use the public defaultchess-iris-lily-kilo-47. Convenient for local testing or low-stakes channels. For real privacy, both sides must use--pass <shared-secret>— anyone who knows the default can join your channel.
- You're in a meeting room and need to share a snippet, a URL, or a one-time credential — without it sitting forever in Slack history.
- You're on an air-gapped LAN with no IM tool installed.
- You just want to talk to coworkers without a third party seeing the traffic.
- Zero setup —
npm install -g @hulk23/inkchat, runic, share an IP + passphrase. - End-to-end encrypted — AES-256-GCM over WebSocket binary frames. The host transports ciphertext; only members with the passphrase can decrypt.
- Passphrase-based keys — 4 words + 2 digits (
apple-river-cube-bliss-42) derived via Argon2id. Easy to read out loud;--strong-keygives you a 32-byte Base32 key instead. - Ephemeral by design — privacy messages render as
🔒 [press Tab to view]placeholders. Open them, they flash for 3 seconds, then vanish from your terminal. No disk writes. - Private chat —
/pm <nick>to whisper. The host routes the ciphertext but cannot see the plaintext routing payload structure. - Multi-party — up to ~20 members per channel (LAN bandwidth permitting).
- Bun >= 1.1
- An interactive terminal
- Machines on the same LAN for host/join sessions
npm install -g @hulk23/inkchat
ic host
ic join 192.168.1.10:7700 --pass <passphrase>bunx @hulk23/inkchat host
bunx @hulk23/inkchat join 192.168.1.10:7700 --pass <passphrase>On the host machine:
ic host --pass my-shared-secretShare the displayed ic join ... command with other LAN members.
On another machine:
ic join <host-ip>:7700 --pass my-shared-secretic host # default channel "my-room", default passphrase
ic host --pass my-own-words-here-99 # private passphrase (recommended for real use)
ic host --name secret-room --port 9000 # custom name + port
ic host --strong-key # 32-byte random Base32 key instead of passphraseThe host displays a panel with the channel name, your LAN address, and the generated passphrase. Share that line with the people you want in the room.
ic join 10.0.0.5:7700 # uses default passphrase
ic join 10.0.0.5:7700 --pass apple-river-cube-bliss-42 # match host's passphrase
ic join 10.0.0.5:7700 --pass <pass> --channel secret-room --nick hank
ic join 10.0.0.5:7700 --key NB2HI4DTHIXS653XO5SWY3DPEBYHK4DUOJUW4ZJ...--channel must match the host's channel name because it is part of the key-derivation salt. Default is my-room.
git clone https://github.com/byteHulk/inkchat.git
cd inkchat
bun install
bun run dev -- host
bun run dev -- join 192.168.1.10:7700 --pass <passphrase>The npm package intentionally ships the source CLI instead of a compiled platform-specific binary:
README.mdLICENSEpackage.jsonsrc/**
The generated package is small and portable, but users must have Bun installed.
| Command | What it does |
|---|---|
/list |
show online members |
/pm <nick> |
start a private chat with nick |
/pm |
leave private chat, return to broadcast |
/privacy |
toggle privacy mode for next messages |
/kick <nick> |
host only — remove a member |
/quit |
leave the channel |
/help |
reprint this list |
| Key | What it does |
|---|---|
Ctrl+P |
toggle privacy mode |
Tab |
reveal the next unviewed 🔒 message |
Ctrl+C |
quit |
- Transport: AES-256-GCM over WebSocket binary frames. Each frame carries a fresh 12-byte IV and the GCM authentication tag.
- Key derivation: passphrase +
inkchat-v1:<channel>salt → Argon2id (memory=64 MiB, iterations=3, parallelism=1) → 32-byte key. - Host trust: the host is a participant and holds the key. It does not log plaintext to disk, but it can read all messages it forwards. If you need the host to be unable to read PMs, that's on the v1.1 roadmap (X25519 session keys).
- Passphrase entropy: 4 words from a 256-word list + 2 digits ≈ 38 bits. With the configured Argon2id work factor (~100ms per guess), an attacker who is already on the LAN needs centuries on commodity hardware. For higher-value channels use
--strong-key(256 bits). - No persistence: messages live only in process memory. Privacy messages are dropped from the React state after viewing. Quitting the host ends the channel and the key dies with it.
┌──────────────┐ ws + AES-GCM ┌──────────────┐
│ ic join │ ─────────────────────► │ ic host │
│ (Client A) │ ◄───────────────────── │ + Server │
└──────────────┘ └──────┬───────┘
│ broadcast / route
┌──────────────┐ ws + AES-GCM │
│ ic join │ ◄──────────────────────────── │
│ (Client B) │ ────────────────────────────► │
└──────────────┘ ▼
Ink UI (host as participant)
Built with Bun, Ink (React for CLI), and the platform Web Crypto API (no native bindings).
- mDNS discovery — drop the manual IP step on shared Wi-Fi.
- File transfer over the encrypted channel.
- X25519 per-pair session keys so the host cannot decrypt PMs.
- Reconnect / channel resume across short disconnects.
- Linux + Windows builds.
MIT