Merge pull request 'Auto-join #nosterm on startup; add mission/roadmap; chat-window banner' (#1) from feat/auto-join-and-readme into main
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -15,3 +15,6 @@ playwright-report/
|
||||
!.env.example
|
||||
|
||||
.DS_Store
|
||||
|
||||
# local AI assistant instructions
|
||||
CLAUDE.local.md
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
```
|
||||
@@ -83,9 +130,10 @@ 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_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 |
|
||||
|
||||
+77
-25
@@ -1,8 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="820" height="240" viewBox="0 0 820 240" role="img" aria-label="Nosterm — a terminal-style Nostr chat application">
|
||||
<title>Nosterm — a terminal-style Nostr chat application</title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="820" height="320" viewBox="0 0 820 320" role="img" aria-label="Nosterm — a terminal-style Nostr chat window with user and relay lists">
|
||||
<title>Nosterm — a terminal-style Nostr chat window with user and relay lists</title>
|
||||
|
||||
<!-- Nord palette: bg #2e3440, surface #3b4252, frame #434c5e,
|
||||
cyan #88c0d0, snow #eceff4, comment #616e88, green #a3be8c -->
|
||||
<!-- Nord palette: bg #2e3440, surface #3b4252, frame #434c5e, muted #4c566a,
|
||||
comment #616e88, snow #d8dee9/#eceff4, frost #81a1c1/#88c0d0,
|
||||
aurora red #bf616a, yellow #ebcb8b, green #a3be8c, purple #b48ead -->
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#3b4252" />
|
||||
@@ -11,34 +12,85 @@
|
||||
</defs>
|
||||
|
||||
<!-- outer canvas -->
|
||||
<rect width="820" height="240" rx="14" fill="#2e3440" />
|
||||
<rect width="820" height="320" rx="14" fill="#2e3440" />
|
||||
|
||||
<!-- terminal window -->
|
||||
<rect x="40" y="34" width="740" height="172" rx="10" fill="url(#bg)" stroke="#434c5e" stroke-width="1.5" />
|
||||
<!-- chat window -->
|
||||
<rect x="30" y="26" width="760" height="268" rx="10" fill="url(#bg)" stroke="#434c5e" stroke-width="1.5" />
|
||||
|
||||
<!-- title bar -->
|
||||
<rect x="40" y="34" width="740" height="34" rx="10" fill="#434c5e" />
|
||||
<rect x="40" y="58" width="740" height="10" fill="#434c5e" />
|
||||
<circle cx="64" cy="51" r="5.5" fill="#bf616a" />
|
||||
<circle cx="84" cy="51" r="5.5" fill="#ebcb8b" />
|
||||
<circle cx="104" cy="51" r="5.5" fill="#a3be8c" />
|
||||
<text x="410" y="55" text-anchor="middle" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="13" fill="#616e88">nosterm — /home/you</text>
|
||||
<rect x="30" y="26" width="760" height="34" rx="10" fill="#434c5e" />
|
||||
<rect x="30" y="50" width="760" height="10" fill="#434c5e" />
|
||||
<circle cx="54" cy="43" r="5.5" fill="#bf616a" />
|
||||
<circle cx="74" cy="43" r="5.5" fill="#ebcb8b" />
|
||||
<circle cx="94" cy="43" r="5.5" fill="#a3be8c" />
|
||||
<text x="410" y="47" text-anchor="middle" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="13" fill="#616e88">nosterm — #general</text>
|
||||
|
||||
<!-- sidebar divider -->
|
||||
<line x1="598" y1="60" x2="598" y2="294" stroke="#434c5e" stroke-width="1.5" />
|
||||
|
||||
<!-- terminal body -->
|
||||
<g font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace">
|
||||
<!-- wordmark: prompt + name -->
|
||||
<text x="70" y="126" font-size="46" font-weight="700" fill="#88c0d0">>_</text>
|
||||
<text x="152" y="126" font-size="46" font-weight="700" fill="#eceff4">nosterm</text>
|
||||
<!-- ===== chat transcript ===== -->
|
||||
<g font-size="13">
|
||||
<!-- msg 1 -->
|
||||
<text x="50" y="92" fill="#616e88">09:42</text>
|
||||
<text x="104" y="92" fill="#88c0d0">@satoshi</text>
|
||||
<text x="190" y="92" fill="#d8dee9">gm — anyone on a home relay?</text>
|
||||
<!-- msg 2 -->
|
||||
<text x="50" y="120" fill="#616e88">09:43</text>
|
||||
<text x="104" y="120" fill="#a3be8c">@you</text>
|
||||
<text x="190" y="120" fill="#d8dee9">yeah: ws://localhost/relay</text>
|
||||
<!-- msg 3 -->
|
||||
<text x="50" y="148" fill="#616e88">09:44</text>
|
||||
<text x="104" y="148" fill="#ebcb8b">@nostrich</text>
|
||||
<text x="190" y="148" fill="#d8dee9">clean. nostr, in a terminal.</text>
|
||||
<!-- msg 4 -->
|
||||
<text x="50" y="176" fill="#616e88">09:45</text>
|
||||
<text x="104" y="176" fill="#b48ead">@hodl</text>
|
||||
<text x="190" y="176" fill="#d8dee9">/theme nord</text>
|
||||
<text x="286" y="176" fill="#a3be8c">✓</text>
|
||||
<!-- msg 5 -->
|
||||
<text x="50" y="204" fill="#616e88">09:46</text>
|
||||
<text x="104" y="204" fill="#88c0d0">@satoshi</text>
|
||||
<text x="190" y="204" fill="#d8dee9">🔥 no backend, just relays</text>
|
||||
<!-- msg 6 -->
|
||||
<text x="50" y="232" fill="#616e88">09:47</text>
|
||||
<text x="104" y="232" fill="#a3be8c">@you</text>
|
||||
<text x="190" y="232" fill="#d8dee9">signed & broadcast. gg</text>
|
||||
</g>
|
||||
|
||||
<!-- tagline as a shell comment -->
|
||||
<text x="72" y="162" font-size="16" fill="#616e88"># a terminal-style Nostr chat client + home relay</text>
|
||||
|
||||
<!-- a sample command line -->
|
||||
<text x="72" y="190" font-size="16" fill="#a3be8c">$</text>
|
||||
<text x="92" y="190" font-size="16" fill="#d8dee9">/join</text>
|
||||
<text x="150" y="190" font-size="16" fill="#88c0d0">#general</text>
|
||||
<rect x="238" y="177" width="9" height="17" fill="#88c0d0">
|
||||
<!-- input line -->
|
||||
<line x1="46" y1="256" x2="566" y2="256" stroke="#434c5e" stroke-width="1" />
|
||||
<text x="50" y="282" font-size="15" fill="#88c0d0">></text>
|
||||
<text x="68" y="282" font-size="15" fill="#eceff4">/msg #general </text>
|
||||
<rect x="200" y="270" width="8" height="16" fill="#88c0d0">
|
||||
<animate attributeName="opacity" values="1;1;0;0" dur="1.1s" repeatCount="indefinite" />
|
||||
</rect>
|
||||
|
||||
<!-- ===== sidebar: users ===== -->
|
||||
<text x="618" y="88" font-size="11" letter-spacing="1.5" fill="#616e88">USERS · 4</text>
|
||||
<g font-size="13">
|
||||
<circle cx="624" cy="110" r="4" fill="#a3be8c" />
|
||||
<text x="640" y="114" fill="#d8dee9">satoshi</text>
|
||||
<circle cx="624" cy="134" r="4" fill="#a3be8c" />
|
||||
<text x="640" y="138" fill="#d8dee9">nostrich</text>
|
||||
<circle cx="624" cy="158" r="4" fill="#88c0d0" />
|
||||
<text x="640" y="162" fill="#88c0d0">you</text>
|
||||
<circle cx="624" cy="182" r="4" fill="#4c566a" />
|
||||
<text x="640" y="186" fill="#616e88">hodl</text>
|
||||
</g>
|
||||
|
||||
<!-- sidebar divider between sections -->
|
||||
<line x1="614" y1="204" x2="774" y2="204" stroke="#434c5e" stroke-width="1" />
|
||||
|
||||
<!-- ===== sidebar: relays ===== -->
|
||||
<text x="618" y="228" font-size="11" letter-spacing="1.5" fill="#616e88">RELAYS · 3</text>
|
||||
<g font-size="12">
|
||||
<circle cx="624" cy="250" r="4" fill="#a3be8c" />
|
||||
<text x="640" y="254" fill="#d8dee9">relay.damus.io</text>
|
||||
<circle cx="624" cy="272" r="4" fill="#a3be8c" />
|
||||
<text x="640" y="276" fill="#d8dee9">nos.lol</text>
|
||||
<circle cx="624" cy="290" r="4" fill="#bf616a" />
|
||||
<text x="640" y="294" fill="#616e88">relay.snort.social</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 4.4 KiB |
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
|
||||
+11
-1
@@ -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 <relay-url>.', 'system');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user