From dd388061d1705125b3639ba4de488b26eb8b7ae0 Mon Sep 17 00:00:00 2001 From: Robert Goodall Date: Sat, 25 Jul 2026 08:17:20 -0400 Subject: [PATCH] Auto-join #nosterm on startup; add mission/roadmap; chat-window banner --- .env.example | 4 ++ .gitignore | 3 ++ Dockerfile | 2 + README.md | 62 ++++++++++++++++++++--- assets/nosterm-banner.svg | 102 ++++++++++++++++++++++++++++---------- docker-compose.yml | 1 + src/lib/config.ts | 8 +++ src/routes/+page.svelte | 12 ++++- 8 files changed, 161 insertions(+), 33 deletions(-) diff --git a/.env.example b/.env.example index 6335751..dc7f35a 100644 --- a/.env.example +++ b/.env.example @@ -26,6 +26,10 @@ PUBLIC_DEFAULT_RELAYS= # Default UI theme id. PUBLIC_DEFAULT_THEME=nord +# Channel auto-joined on startup, on the first default relay (bare name, no '#'). +# Leave blank to disable auto-join. +PUBLIC_DEFAULT_CHANNEL=nosterm + # Max lines kept in the terminal scrollback. PUBLIC_MAX_TERMINAL_ENTRIES=1000 diff --git a/.gitignore b/.gitignore index 668c3d2..b137c69 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ playwright-report/ !.env.example .DS_Store + +# local AI assistant instructions +CLAUDE.local.md diff --git a/Dockerfile b/Dockerfile index 21a2673..827a6b5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,11 +29,13 @@ RUN npm ci COPY . ./ ARG PUBLIC_DEFAULT_RELAYS="" ARG PUBLIC_DEFAULT_THEME="nord" +ARG PUBLIC_DEFAULT_CHANNEL="nosterm" ARG PUBLIC_MAX_TERMINAL_ENTRIES="1000" ARG PUBLIC_MAX_MESSAGE_LENGTH="2000" ARG PUBLIC_MAX_CACHED_MESSAGES_PER_ROOM="500" ENV PUBLIC_DEFAULT_RELAYS=$PUBLIC_DEFAULT_RELAYS \ PUBLIC_DEFAULT_THEME=$PUBLIC_DEFAULT_THEME \ + PUBLIC_DEFAULT_CHANNEL=$PUBLIC_DEFAULT_CHANNEL \ PUBLIC_MAX_TERMINAL_ENTRIES=$PUBLIC_MAX_TERMINAL_ENTRIES \ PUBLIC_MAX_MESSAGE_LENGTH=$PUBLIC_MAX_MESSAGE_LENGTH \ PUBLIC_MAX_CACHED_MESSAGES_PER_ROOM=$PUBLIC_MAX_CACHED_MESSAGES_PER_ROOM diff --git a/README.md b/README.md index 586654c..fc4f767 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,53 @@ Open a pull request against `main` — see [CONTRIBUTING.md](CONTRIBUTING.md). E 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 ``` @@ -82,13 +129,14 @@ secrets) — change one → rebuild the image. **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_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 | +| 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 diff --git a/assets/nosterm-banner.svg b/assets/nosterm-banner.svg index 5b41e82..1b9c7cf 100644 --- a/assets/nosterm-banner.svg +++ b/assets/nosterm-banner.svg @@ -1,8 +1,9 @@ - - Nosterm — a terminal-style Nostr chat application + + Nosterm — a terminal-style Nostr chat window with user and relay lists - + @@ -11,34 +12,85 @@ - + - - + + - - - - - - nosterm — /home/you + + + + + + nosterm — #general + + + - - - >_ - nosterm + + + + 09:42 + @satoshi + gm — anyone on a home relay? + + 09:43 + @you + yeah: ws://localhost/relay + + 09:44 + @nostrich + clean. nostr, in a terminal. + + 09:45 + @hodl + /theme nord + + + 09:46 + @satoshi + 🔥 no backend, just relays + + 09:47 + @you + signed & broadcast. gg + - - # a terminal-style Nostr chat client + home relay - - - $ - /join - #general - + + + > + /msg #general + + + + USERS · 4 + + + satoshi + + nostrich + + you + + hodl + + + + + + + RELAYS · 3 + + + relay.damus.io + + nos.lol + + relay.snort.social + diff --git a/docker-compose.yml b/docker-compose.yml index 2037126..7ca5825 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,7 @@ services: # Non-relay tunables are still baked in at build time. Relays are set at # runtime via NOSTERM_DEFAULT_RELAYS below (no rebuild to change them). PUBLIC_DEFAULT_THEME: ${PUBLIC_DEFAULT_THEME:-nord} + PUBLIC_DEFAULT_CHANNEL: ${PUBLIC_DEFAULT_CHANNEL:-nosterm} PUBLIC_MAX_TERMINAL_ENTRIES: ${PUBLIC_MAX_TERMINAL_ENTRIES:-1000} PUBLIC_MAX_MESSAGE_LENGTH: ${PUBLIC_MAX_MESSAGE_LENGTH:-2000} PUBLIC_MAX_CACHED_MESSAGES_PER_ROOM: ${PUBLIC_MAX_CACHED_MESSAGES_PER_ROOM:-500} diff --git a/src/lib/config.ts b/src/lib/config.ts index f802ad3..a13a9cf 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -20,6 +20,14 @@ export const DEFAULT_RELAYS: string[] = (env.PUBLIC_DEFAULT_RELAYS ?? '') export const DEFAULT_THEME_ID: string = (env.PUBLIC_DEFAULT_THEME ?? 'nord').trim() || 'nord'; +/** + * channel auto-joined on startup, on the first default relay. bare group name + * (no leading '#'). set to empty to disable auto-join. + */ +export const DEFAULT_CHANNEL: string = (env.PUBLIC_DEFAULT_CHANNEL ?? 'nosterm') + .trim() + .replace(/^#/, ''); + export const MAX_TERMINAL_ENTRIES: number = parseIntOr(env.PUBLIC_MAX_TERMINAL_ENTRIES, 1000); export const MAX_MESSAGE_LENGTH: number = parseIntOr(env.PUBLIC_MAX_MESSAGE_LENGTH, 2000); diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 3d11a64..660bd4f 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -14,6 +14,8 @@ import { uiStore } from '$lib/stores/ui-store'; import { pluginHost } from '$lib/plugins/plugin-host'; import { loadDefaultRelays } from '$lib/runtime-config'; + import { DEFAULT_CHANNEL } from '$lib/config'; + import { roomService } from '$lib/nostr/room-service'; import { BRAND } from '$lib/brand'; const { dispatcher, registry } = createAppDispatcher(); @@ -35,11 +37,19 @@ // relays come from runtime /config.json (per-deployment, no rebuild) // and fall back to the build-time defaults. const relays = await loadDefaultRelays(); - if (relays.length > 0) { + const [firstRelay] = relays; + if (firstRelay) { terminalStore.system(`Connecting to ${relays.length} relay(s)…`, 'system'); for (const url of relays) { void relayService.connect(url); } + + // auto-join the default channel on the first relay so the app opens on a + // room instead of an empty terminal. join() handles waiting for the relay + // to connect; a NIP-29 join request needs no login (reading is open). + if (DEFAULT_CHANNEL) { + void roomService.join({ relayUrl: firstRelay, groupId: DEFAULT_CHANNEL }); + } } else { terminalStore.system('No default relays configured. Use /connect .', 'system'); } -- 2.54.0