# Nosterm

Nosterm — a terminal-style Nostr chat application

**A terminal-style Nostr chat client.** The Nosterm web client: a terminal-style Nostr chat SPA built with [SvelteKit](https://kit.svelte.dev/) and served as static files by [Caddy](https://caddyserver.com/). Nostr is a client-side protocol, so there is no application backend here — the browser talks directly to relays over WebSocket. Run a relay from the companion [nosterm-relay](https://git.nerdworks.io/nerdworks/nosterm-relay) repository if you want your own home relay. This is the **public, contribution-focused** repository for the Nosterm client. Open a pull request against `main` — see [CONTRIBUTING.md](CONTRIBUTING.md). Each merge to `main` builds the container image and publishes it to ECR Public for anonymous pulls (see [Deploy](#deploy)). ## Mission Nosterm aims to be the premier, full-featured **Nostr Relay Chat (NRC)** client — the terminal-native way to talk over Nostr. We believe private communication is a right, not a feature, so Nosterm is built to advocate for and promote **anonymity, end-to-end encryption, and censorship resistance** by default rather than as opt-in extras. Three principles guide every decision: - **Small footprint.** A static SPA with no application backend, served from a single lightweight container. It should stay auditable, fast to load, and cheap to self-host — no build-time secrets, no telemetry, no lock-in. - **Easy to deploy, anywhere.** One prebuilt image, configured at runtime, that drops cleanly onto different **overlay network protocols** (Tor, I2P, Yggdrasil, Nym, and WireGuard-style meshes) so operators can run it wherever they trust the transport. - **Privacy as the default.** Anonymity, encryption, and anti-censorship are the baseline behavior. The client should make the private path the easy path. ## Roadmap The roadmap turns the mission into concrete direction. Items are aspirational and unordered — contributions that move any of them forward are welcome. - **Full NRC feature set** — channels, threads, DMs, presence, and moderation tools that make Nosterm a complete relay-chat experience, not just a demo. - **Overlay-network first** — first-class, documented deployment recipes for Tor hidden services, I2P, and other overlay transports, keeping the same single runtime-configured image. - **Encryption everywhere** — modern encrypted DMs and private groups (e.g. NIP-17 / gift-wrapped events), with sensible key handling and no plaintext fallbacks that surprise the user. - **Anonymity by design** — minimize metadata leakage, support ephemeral and disposable identities, and avoid any client behavior that fingerprints or phones home. - **Censorship resistance** — graceful multi-relay failover, easy relay discovery and rotation, and resilience when individual relays are blocked or disappear. - **Mature the Nostermd companion relay** — grow the [nostermd-relay](https://git.nerdworks.io/nerdworks/nostermd-relay) home relay into a first-class **NIP-29** (relay-based groups) implementation, deepening its federation and group-chat features so a self-hosted relay is a complete NRC backend for the client. - **Stay small** — hold the line on bundle size, dependency count, and container footprint as features grow. ## Layout ``` nosterm-client/ src/ # SvelteKit source (components, stores, nostr, commands…) static/ # static assets Dockerfile # build the SPA → serve with Caddy Caddyfile # static file server + security headers + optional /relay proxy docker-compose.yml # one-command deploy on port 8080 .env.example # copy to .env and configure package.json # dev/build/test scripts ``` ## Develop ```bash npm install npm run dev # vite dev server npm run check # svelte-check (type-check) npm run lint # prettier --check + eslint npm test # vitest npm run test:e2e # playwright npm run build # static build → ./build ``` ## Deploy ```bash cp .env.example .env # then set NOSTERM_DEFAULT_RELAYS at minimum docker compose up -d --build ``` The client is then served at . To run the prebuilt public image with your own relays — no rebuild, no AWS account (anonymous pull from ECR Public): ```bash docker run -p 8080:80 \ -e NOSTERM_DEFAULT_RELAYS=wss://relay.example.com \ public.ecr.aws/k3k1z1x5/nosterm:latest ``` `NOSTERM_DEFAULT_RELAYS` accepts a comma-separated list. On startup the container's entrypoint writes it to `/config.json`, which the SPA fetches on load — so one shared image serves every deployment with different relays. ## Configuration Relays are configured at **runtime** (`NOSTERM_DEFAULT_RELAYS`), so a single prebuilt image works for any deployment. The remaining `PUBLIC_*` tunables are **build-time** args baked into the browser bundle (client-visible, never secrets) — change one → rebuild the image. **Runtime** (set on the running container, no rebuild): | Variable | Default | Purpose | | ------------------------ | ------------ | ------------------------------------------------------------ | | `NOSTERM_DEFAULT_RELAYS` | _empty_ | Comma-separated relay WebSocket URLs to preload | | `RELAY_UPSTREAM` | `relay:3334` | Upstream for Caddy's optional `/relay` reverse-proxy (below) | **Build-time** (baked into the bundle; rebuild to change): | Variable | Default | Purpose | | ------------------------------------- | --------- | ------------------------------------------------- | | `PUBLIC_DEFAULT_RELAYS` | _empty_ | Fallback relays when no runtime config is set | | `PUBLIC_DEFAULT_THEME` | `nord` | Theme applied before the user picks one | | `PUBLIC_DEFAULT_CHANNEL` | `nosterm` | Channel auto-joined on startup (blank = disabled) | | `PUBLIC_MAX_TERMINAL_ENTRIES` | `1000` | Terminal entries kept in memory / rendered | | `PUBLIC_MAX_MESSAGE_LENGTH` | `2000` | Max outbound message length (characters) | | `PUBLIC_MAX_CACHED_MESSAGES_PER_ROOM` | `500` | Messages retained per room in IndexedDB | ## Talking to a relay The client connects directly to whatever relays are configured in `NOSTERM_DEFAULT_RELAYS` (and any the user adds at runtime). Two common setups: - **External relay** — set `NOSTERM_DEFAULT_RELAYS=wss://relay.example.com`. You can delete the `handle /relay*` block from the `Caddyfile`. - **Same-origin home relay** — run [nosterm-relay](https://git.nerdworks.io/nerdworks/nosterm-relay), set `NOSTERM_DEFAULT_RELAYS=ws://localhost:8080/relay` (or `wss://…` behind TLS), and set `RELAY_UPSTREAM` to the relay's address so Caddy proxies `/relay` to it. Put both services on a shared Docker network for name resolution. ## Production TLS For a real domain, edit the `Caddyfile`: replace `:80` with your hostname (e.g. `nosterm.example.com { … }`) and Caddy will obtain and renew a certificate automatically. Expose ports 80 and 443 in `docker-compose.yml`.