Commit Graph

91 Commits

Author SHA1 Message Date
Siavash Sameni
cfb227a93d Server auth (challenge-response) + OTP key replenishment
Authentication:
- POST /v1/auth/challenge {fingerprint} → {challenge, expires_at}
- POST /v1/auth/verify {fingerprint, challenge, signature} → {token}
- Client signs challenge with Ed25519 identity key
- Server verifies against stored public key
- Returns bearer token valid for 7 days
- Web clients get token without sig verify (Phase 2: WASM)
- validate_token() helper for protecting endpoints

OTP Key Replenishment:
- GET /v1/keys/:fp/otpk-count → {otpk_count}
- POST /v1/keys/replenish {fingerprint, otpks: [{id, public_key}]}
- OTPKs stored individually: otpk:<fp>:<id> → public_key
- Returns total count after replenishment

Phase 1 complete:
- [x] Seed-based identity + BIP39
- [x] X3DH + Double Ratchet (forward secrecy)
- [x] Pre-key bundles
- [x] Server (keys, messages, groups, aliases, auth)
- [x] CLI TUI + Web client
- [x] Aliases with TTL + recovery
- [x] Seed encryption (Argon2id + ChaCha20)
- [x] Server auth (challenge-response + tokens)
- [x] OTP key replenishment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 07:55:02 +04:00
Siavash Sameni
3ffac0c751 Unlock seed once at startup, pass identity to all commands
- main.rs unlocks seed once, prompts passphrase once per app launch
- Identity passed as parameter to send, recv, register, chat
- No more redundant load_seed() calls (was prompting passphrase multiple times)
- info command uses pre-unlocked identity directly

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 07:49:51 +04:00
Siavash Sameni
37a4c3c54f Seed encryption at rest (Argon2id + ChaCha20-Poly1305) + HW wallet plan
keystore.rs:
- Passphrase prompted on init (hidden input, echo disabled)
- Empty passphrase = plaintext (for testing/scripting)
- Encrypted format: MAGIC("WZS1") + salt(16) + nonce(12) + ciphertext(48)
- Argon2id for key derivation (memory-hard, GPU-resistant)
- ChaCha20-Poly1305 AEAD for encryption
- Backwards compatible: auto-detects plaintext vs encrypted on load
- Keys zeroized after use

DESIGN.md:
- Added hardware wallet section (Ledger/Trezor via USB/BT HID)
- Ed25519 signing delegated to device, seed never exported
- BIP44 derivation path m/44'/1234'/0'
- Phase 2 feature, protocol unchanged

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 07:45:55 +04:00
Siavash Sameni
7fe6de0ba1 Alias TTL renews only on authenticated actions (sending messages)
- Sending a message includes `from` fingerprint
- Server renews alias TTL on send (proves identity: you encrypted it)
- Polling/receiving does NOT renew (anyone can spam messages to you)
- Key registration does NOT renew (separate concern)

This prevents alias keepalive attacks where someone spams a user
just to keep their alias from expiring.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 07:39:15 +04:00
Siavash Sameni
bf67566b0c Alias TTL, recovery keys, and reclamation
Aliases now have a lifecycle:
- 365-day TTL from last activity (send/receive/renew)
- 30-day grace period after expiry (only recovery key can reclaim)
- After grace: anyone can register the alias
- Recovery key generated on first registration, rotated on recovery
- Auto-renew on activity via POST /v1/alias/renew

New endpoints:
- POST /v1/alias/recover {alias, recovery_key, new_fingerprint}
  Reclaim alias with recovery key, even if expired. Works across
  identity changes (new seed → new fingerprint, same alias).
  Recovery key is rotated on each recovery.
- POST /v1/alias/renew {fingerprint}
  Heartbeat — resets TTL. Returns days until expiry.

Resolve now returns expiry info:
- GET /v1/alias/resolve/:name → includes expires_in_days, expired flag
- GET /v1/alias/list → includes expiry status per alias

Phase 2: DNS automation — separate DNS authority manages parent zone,
servers update delegated records via API. Recovery key maps to DNS
record ownership for out-of-band reclamation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 07:18:10 +04:00
Siavash Sameni
29c059cebf Aliases: human-readable names mapped to fingerprints
Server:
- POST /v1/alias/register — claim an alias (one per fingerprint)
- GET /v1/alias/resolve/:name — alias → fingerprint
- GET /v1/alias/whois/:fingerprint — fingerprint → alias (reverse)
- GET /v1/alias/list — list all aliases
- Bidirectional mapping in sled (a:name→fp, fp:fp→name)
- One alias per person, re-registering replaces old alias

Web client:
- /alias <name> — register your alias
- /aliases — list all registered aliases
- /info — now shows alias alongside fingerprint
- Peer input accepts @alias (resolved before sending)
- Received messages show @alias instead of fingerprint
- DM: paste @alias or fingerprint in peer input

CLI TUI:
- /alias <name> — register alias
- /aliases — list all aliases
- /peer @alias — resolves alias to fingerprint
- Alias resolution displayed in system messages

Addressing model:
- @manwe (local) → server resolves → fingerprint
- @manwe.b1.example.com (federated) → DNS resolve (Phase 3)
- Raw fingerprint → always works, no resolution

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 07:01:35 +04:00
Siavash Sameni
b90155c3b7 Fix web client: gracefully handle CLI members in groups
- fetchPeerKey: catch JSON parse error for CLI bincode bundles,
  show clear "CLI client — needs WASM bridge" message
- Group send: silently skip CLI members instead of showing
  error per member (mixed groups work, web members get messages,
  CLI members are skipped without noise)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:20:25 +04:00
Siavash Sameni
5cf7e8a02f Auto-join groups: /g and /gjoin auto-create if group doesn't exist
- Server: /join endpoint creates the group if it doesn't exist
- CLI TUI: /g <name> auto-joins before switching
- Web: /g <name> auto-joins before switching
- No more "group not found" errors — just /g ops and go

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:17:03 +04:00
Siavash Sameni
f3e78c6cff Group chat with E2E encryption for both web and CLI clients
Server:
- POST /v1/groups/create — create named group
- POST /v1/groups/:name/join — join group
- GET /v1/groups/:name — get group info + member list
- GET /v1/groups — list all groups
- POST /v1/groups/:name/send — fan-out encrypted messages to members
- Groups stored in sled, members tracked by fingerprint

Web client:
- /gcreate <name> — create group
- /gjoin <name> — join group
- /g <name> — switch to group chat mode
- /glist — list all groups
- /dm — switch back to DM mode
- Group messages encrypted per-member (ECDH + AES-GCM for each)
- Group tag shown on received messages: "sender [groupname]"

CLI TUI client:
- Same commands: /gcreate, /gjoin, /g, /glist, /dm
- Group messages encrypted per-member (X3DH + Double Ratchet for each)
- Automatic X3DH key exchange with new group members on first message
- Sessions established and persisted per-member

Architecture:
- Client-side fan-out encryption: message encrypted N times (once per member)
- Server stores one copy per recipient in their message queue
- Reuses existing 1:1 encryption — no new crypto primitives needed
- Works for groups ≤ 50 members (per DESIGN.md)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:13:16 +04:00
Siavash Sameni
7b1e0bd162 Full web client with E2E encrypted messaging
Complete single-page web app served at / with:
- Identity generation (random 32-byte seed)
- Identity recovery from hex seed
- Persistent keys in localStorage (survives refresh)
- Auto-load saved identity on page load
- ECDH P-256 key exchange via Web Crypto API
- AES-256-GCM message encryption (iv prepended)
- Key registration with /v1/keys/register
- Send encrypted messages via /v1/messages/send
- Poll for messages every 2s with auto-decrypt
- Peer fingerprint input in header (saved to localStorage)
- Color-coded messages (green=self, orange=peer, cyan=system)
- Lock icon on received encrypted messages
- Commands: /info, /clear, /quit
- Graceful handling of CLI client messages (shows warning)
- Dark theme, responsive, mobile-friendly

Note: web-to-web E2E works. Web-to-CLI interop requires WASM
build of warzone-protocol (Phase 2) since crypto primitives
differ (P-256/AES-GCM vs X25519/ChaCha20).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:05:51 +04:00
Siavash Sameni
a298c9430c TUI chat interface with real-time E2E encrypted messaging
`warzone chat [peer-fp] -s <server>` launches an interactive terminal UI:
- Header: your fingerprint, peer fingerprint, server URL
- Message area: color-coded (green=you, yellow=peer, cyan=system)
- Input bar with cursor at bottom
- Background polling every 2s for incoming messages
- Full X3DH + Double Ratchet on send/receive
- Session persistence across messages

Commands in TUI:
- /peer <fingerprint> — set who you're chatting with
- /info — show your fingerprint
- /quit or /q or Esc or Ctrl+C — exit

Usage:
  warzone chat "6baf:6d0b:4541:9cae:f06b:83da:69bc:05ee" -s http://localhost:7700

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:59:08 +04:00
Siavash Sameni
6d4a09a0c6 Fetch-and-delete: server deletes messages after poll delivery
poll_messages now collects all queued messages, returns them,
then deletes them from sled. No more duplicate delivery.

This is correct for store-and-forward: once the client receives
the messages, the server's job is done. If the client crashes
before processing, the messages are lost — acceptable for Phase 1.
Phase 2 can add explicit ack-based delivery if needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:55:50 +04:00
Siavash Sameni
8a6eebabfd Fix axum route params: use :param syntax (not {param}) for axum 0.7
Axum 0.7 uses :param for path parameters. {param} is axum 0.8+ syntax.
Routes were silently not matching, causing 404 on all key lookups.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:48:19 +04:00
Siavash Sameni
bc64afcb05 Add request tracing, debug /v1/keys/list endpoint
- TraceLayer logs every HTTP request (method, path, status, duration)
- Default log level info, tower_http=debug (no RUST_LOG needed)
- GET /v1/keys/list shows all registered fingerprints
- Helps debug key registration and lookup issues

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:43:54 +04:00
Siavash Sameni
8dd45b1bfe Normalize fingerprints everywhere: strip colons from URLs and DB keys
Client: strip colons before putting fingerprints in URL paths
(colons in URLs confuse axum path matching).

Server: normalize fingerprints in message routes too.

All fingerprint storage and lookup is now hex-only, case-insensitive.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:41:26 +04:00
Siavash Sameni
de118371de Fix bundle lookup: normalize fingerprints, handle 404 gracefully
Server: normalize fingerprints by stripping colons and lowercasing
before storing/looking up in sled. Adds tracing for register/lookup.

Client: check HTTP status before parsing JSON response body.
Shows clear error when user is not registered instead of parse error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:37:41 +04:00
Siavash Sameni
cf7e935250 Display full 16-byte fingerprint (8 groups instead of 4)
Was showing xxxx:xxxx:xxxx:xxxx (8 bytes) but from_hex expected
16 bytes, causing parse failure. Now displays all 16 bytes:
xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx

Users need to re-init to see the full fingerprint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:34:40 +04:00
Siavash Sameni
2efd355983 Fix init output to show actual data directory path
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:29:55 +04:00
Siavash Sameni
722441c391 Add WARZONE_HOME env var for separate user data directories
All data paths now use keystore::data_dir() which checks
WARZONE_HOME first, falls back to ~/.warzone.

This avoids the HOME override hack that breaks rustup/cargo.

Usage: WARZONE_HOME=/tmp/bob warzone init

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:27:49 +04:00
Siavash Sameni
94b845eb5b Fix all compiler warnings across server and client
- Remove unused ServerConfig struct (config via CLI args)
- Remove unused otpks field from Database (not yet needed)
- Wire AppError into message routes with proper error propagation
- Remove unused imports in send.rs (Seed, MessageContent, etc.)
- Suppress dead_code on BundleResponse.fingerprint (needed by serde)

Zero warnings, 17/17 tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:16:11 +04:00
Siavash Sameni
60a7006ed9 Add documentation: protocol spec, server admin, client guide
docs/PROTOCOL.md (520 lines):
- Identity model (seed → Ed25519 + X25519 via HKDF)
- X3DH key exchange (4 DH operations, ASCII flow diagram)
- Double Ratchet (chain/DH ratchet, skipped keys, state machine)
- KDF chains with domain separation strings
- AEAD (ChaCha20-Poly1305)
- Wire format (WireMessage enum, bincode serialization)
- Pre-key bundle format and lifecycle

docs/SERVER.md (429 lines):
- Build and run instructions
- Full API reference with request/response examples
- Database structure (sled trees)
- Deployment (nginx reverse proxy, systemd unit)
- Security considerations
- Backup and recovery

docs/CLIENT.md (507 lines):
- Quick start guide
- All CLI commands with examples
- Identity management and mnemonic backup
- Web client usage and limitations
- Session and pre-key management
- Threat model table
- Troubleshooting guide

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:59:19 +04:00
Siavash Sameni
82f5061aa1 Wire E2E messaging: send, recv, session persistence, auto-registration
CLI client (warzone):
- `warzone init` now generates pre-key bundle (1 SPK + 10 OTPKs),
  stores secrets in local sled DB, saves bundle for server registration
- `warzone register -s <url>` registers bundle with server
- `warzone send <fp> <msg> -s <url>` full E2E flow:
  - Auto-registers bundle on first use
  - Fetches recipient's pre-key bundle
  - Performs X3DH key exchange (first message) or uses existing session
  - Encrypts with Double Ratchet
  - Sends WireMessage envelope to server
- `warzone recv -s <url>` polls and decrypts:
  - Handles KeyExchange messages (X3DH respond + ratchet init as Bob)
  - Handles Message (decrypt with existing ratchet session)
  - Saves session state after each decrypt

Wire protocol (WireMessage enum):
- KeyExchange variant: sender identity, ephemeral key, OTPK id, ratchet msg
- Message variant: sender fingerprint + ratchet message

Session persistence:
- Ratchet state serialized with bincode, stored in sled (~/.warzone/db)
- Pre-key secrets stored in sled, OTPKs consumed on use
- Sessions keyed by peer fingerprint

Networking (net.rs):
- register_bundle, fetch_bundle, send_message, poll_messages
- JSON API over HTTP, bundles serialized with bincode + base64

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:40:21 +04:00
Siavash Sameni
e364f437a2 Add .gitignore, remove target/ from tracking
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:33:13 +04:00
Siavash Sameni
7451ad69bc Fix X3DH + add web client served by warzone-server
X3DH fix:
- Added identity_encryption_key (X25519) to PreKeyBundle
- initiate() and respond() now use correct DH operations per Signal spec:
  DH1=IK_a*SPK_b, DH2=EK_a*IK_b, DH3=EK_a*SPK_b, DH4=EK_a*OPK_b
- All 17 tests pass including x3dh_shared_secret_matches

Web client (served at /):
- Identity generation with seed (stored in localStorage)
- Recovery from hex-encoded seed
- Auto-load saved identity on page load
- Fingerprint display (same format as CLI: xxxx:xxxx:xxxx:xxxx)
- Key registration with server via /v1/keys/register
- Chat UI with message polling (5s interval)
- Commands: /help, /info, /seed
- Dark theme matching warzone aesthetic

Both clients (CLI + Web) now exist:
- CLI: warzone init, warzone info, warzone recover
- Web: http://localhost:7700/ (served by warzone-server)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:32:46 +04:00
Siavash Sameni
651396fa13 Scaffold Rust workspace: warzone-protocol, server, client, mule
4 crates, all compile. 16/17 tests pass.

warzone-protocol (core crypto):
- Seed-based identity (Ed25519 + X25519 from 32-byte seed via HKDF)
- BIP39 mnemonic encode/decode (24 words)
- Fingerprint type (SHA-256 truncated, displayed as xxxx:xxxx:xxxx:xxxx)
- ChaCha20-Poly1305 AEAD encrypt/decrypt with random nonce
- HKDF-SHA256 key derivation
- Pre-key bundle generation with Ed25519 signatures
- X3DH key exchange (simplified, needs X25519 identity key in bundle)
- Double Ratchet: full implementation with DH ratchet, chain ratchet,
  out-of-order message handling via skipped keys cache
- Message format (WarzoneMessage envelope + RatchetHeader)
- Session type with ratchet state
- Storage trait definitions (PreKeyStore, SessionStore, MessageQueue)

warzone-server (axum):
- sled database (keys, messages, one-time pre-keys)
- Routes: /v1/health, /v1/keys/register, /v1/keys/{fp},
  /v1/messages/send, /v1/messages/poll/{fp}, /v1/messages/{id}/ack

warzone-client (CLI):
- `warzone init` — generate seed, show mnemonic, save to ~/.warzone/
- `warzone recover <words>` — restore from mnemonic
- `warzone info` — show fingerprint and keys
- Seed storage at ~/.warzone/identity.seed (600 perms)
- Stubs for send, recv, chat commands

warzone-mule: Phase 4 placeholder

Known issue: X3DH test fails (initiate/respond use different DH ops
due to missing X25519 identity key in bundle). Fix in next step.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:27:48 +04:00
Siavash Sameni
1e2a83402d DESIGN.md: DNS-based key transparency, resolve remaining questions
- Key transparency via DNS TXT records with self-signatures
  (server can't MITM because it can't forge user's signature)
- Per-device ratchet sessions (Signal model), cross-device sync via seed
- LoRa deferred to later phases, not Phase 1
- Sealed sender before onion routing
- Phase 3 updated to include key transparency alongside federation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:55:15 +04:00
Siavash Sameni
fa20607e35 DESIGN.md: resolve open questions, add transport layer architecture
Decisions: Sender Keys for groups, optional onion routing, deniability
by default, Bluetooth + LoRa transports, no tokenization.

New sections: transport abstraction (HTTPS/WS/BT/LoRa/Wi-Fi Direct/USB),
LoRa compact binary format, sealed sender vs onion routing discussion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:44:47 +04:00
Siavash Sameni
b7aa1a10e8 Add DESIGN.md: warzone messenger architecture and roadmap
Covers: seed-based identity, Signal protocol (X3DH + Double Ratchet),
DNS federation, mule delivery protocol, Rust rewrite plan, ntfy integration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:34:54 +04:00
Siavash Sameni
93c8c84de1 Click on DM lock icon to pre-fill /dm @username in input
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 17:04:31 +04:00
Siavash Sameni
811dd2c008 v14: /reply and /r command to quick-reply to last DM peer
- /reply <msg> or /r <msg> sends encrypted DM to last person
- lastDmPeer set when sending a DM or receiving one
- Shows error if no prior DM conversation exists

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 16:46:35 +04:00
Siavash Sameni
93be964d52 v14: persistent E2E keys - browser localStorage + server keys.json
Browser:
- ECDH key pair saved to localStorage (chat-key-priv, chat-key-pub)
- Loaded on reconnect, only generated once
- Re-registers public key with server on every connect
- Corrupted keys auto-regenerate

Server:
- Keys saved to keys.json on disk after each registration
- Loaded on startup, survives restarts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 16:21:20 +04:00
Siavash Sameni
04482faa6a Fix header commands readability: lighter text + styled code tags
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 16:17:32 +04:00
Siavash Sameni
03d91cb844 v13: E2E encrypted DMs via ECDH + AES-256-GCM (Web Crypto API)
Server:
- /keys POST: register ECDH public key (JWK) for a username
- /keys GET: list users with registered keys
- /keys/<user> GET: get user's public key
- /dm POST: relay encrypted DM blob to recipient
- SSE streams now register for DM delivery via name param
- Server never sees plaintext - only ciphertext passes through

Web UI:
- Auto-generates ECDH P-256 key pair on load (no setup needed)
- /dm @username message - sends E2E encrypted DM
- /users - list users with registered keys
- DMs shown with lock icon, pink color, direction arrows
- Decryption happens entirely in browser
- Key re-registered on name change
- Derived AES keys cached per peer

Protocol:
- ECDH key exchange: each client exports JWK public key
- Shared secret derived via ECDH P-256
- Messages encrypted with AES-256-GCM + random 12-byte nonce
- Ciphertext + nonce sent as base64 through server

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 16:14:28 +04:00
Siavash Sameni
c97a3834d1 v12: group chat with optional passwords
- /group/<name> URL creates/joins a group (auto-created on first visit)
- / and /chat redirect to /group/lobby (default group)
- Each group has isolated history, clients, and SSE streams
- /setpass <password> sets a password for the current group
- /clearpass removes the password
- Password prompt modal in web UI, stored in sessionStorage
- SSE sends auth-fail event if wrong password, triggers re-prompt
- Group name shown as tag in header
- TCP clients use lobby group by default

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:33:28 +04:00
Siavash Sameni
087334ffe9 v11: PWA support for mobile - installable app with offline fallback
- Web manifest with standalone display mode
- SVG chat bubble icon (no external assets needed)
- Service worker for install + offline page
- iOS meta tags: apple-mobile-web-app-capable, status bar style
- Mobile-optimized layout: safe-area insets, dvh units, rounded inputs
- Name input moved to header, file button + send in bottom bar
- 16px font on input (prevents iOS zoom)
- Name persisted to localStorage on mobile
- Keyboard-aware scroll (visualViewport resize listener)
- Install banner with prompt for Android Chrome

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:14:38 +04:00
Siavash Sameni
fe6ea164bf v10: /color command to reshuffle user colors (web + terminal)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:08:12 +04:00
Siavash Sameni
1d0b87b509 v9: multi-destination tunnel support (parspack, mequ, alipi)
Server:
- /tunnel/<dest> routes: parspack (185.208.174.152:22),
  mequ (188.213.68.133:2022), alipi (10.66.66.2:22)
- /tunnel without dest defaults to parspack

Client (tunnel.py):
- --destination / -d flag to pick target
- Lists available destinations in --help

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 15:03:23 +04:00
Siavash Sameni
6aa2717560 v8: notifications - browser push notifications and terminal bell/title
Web UI:
- Requests browser notification permission on load
- Shows desktop notification for messages from others when tab unfocused
- Tab title shows unread count: "(3) Chat"
- Resets on focus

Terminal client:
- Bell (\a) on messages from others
- Terminal title updates to show sender and preview
- Title resets when user types

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:51:26 +04:00
Siavash Sameni
d55b65db1f v7: file storage limits - 1MB per file, 50MB total, FIFO eviction
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:43:20 +04:00
Siavash Sameni
229223168e v6: markdown rendering, file uploads, colored TUI, multiline support
Web UI:
- Textarea replaces input: Shift+Enter for newline, Enter to send
- Pasted text preserves newlines, tabs, whitespace
- Markdown: ```code blocks```, `inline code`, **bold**, *italic*, auto-links
- File upload button (paperclip icon), files stored in memory with download links

Python CLI client:
- Colored usernames: green for self, cyan for system, unique color per other user
- /file <path> command to upload files
- Multiline messages displayed with continuation indent
- JSON protocol for multiline + file support (backwards compatible)

Server:
- POST /chat/upload for multipart file uploads
- GET /files/<id>/<name> for file downloads
- TCP protocol accepts JSON packets for multiline text and file transfers
- Falls back to plain text for old clients

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:40:57 +04:00
Siavash Sameni
8d6d50a2e4 v5: stable chat server with web UI, SSH tunnel, and nginx proxy
- chat.py: multi-user chat server (stdlib only, single port)
  - Web UI at /chat with SSE real-time messaging
  - Per-user colors (green for self, palette for others)
  - Curses TUI client with scroll support
  - WebSocket SSH tunnel at /tunnel -> 185.208.174.152:22
  - /version endpoint for deployment verification
  - /tunnel.py download endpoint
- tunnel.py: SSH-over-WebSocket client with custom DNS support
- nginx: Kubernetes manifests (Deployment + Service + Ingress)
  - Reverse proxy to chat.py at 188.213.68.133:9997
  - SSE buffering disabled, WebSocket upgrade for /tunnel
- nginx.txt: alternate nginx deployment with different ingress host
- apache: Bitnami Apache Helm values (initial attempt, replaced by nginx)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:38:36 +04:00