Apps

Nostr

# Nostr — TUI Client A workspace panel that reads and publishes NIP-01 events over a WebSocket relay pool. Built on the native `http/ws-*` plugin (wss via mbedTLS), `json/*`, `sqlite/*`, and the BIP-340 Schnorr support in `crypto/*`. Layout: `src/nostr.dft` is the app entry (the Deftlet panel, discovered by the launcher as the package default module); `src/lib.dft` is the headless core library; `src/nip19.dft` and `src/bech32.dft` are the NIP-19 helpers. ## Getting started Open the app from the launcher (`nostr`) or launch the file directly. On first start it generates a keypair and writes: - `.deft/nostr.dft` — `%{ :nsec :name :relays }` (chmod 600, best-effort) - `.deft/nostr.db` — SQLite cache (WAL) of events and profiles Set `DEFT_NOSTR_HOME=<dir>` to keep both under `<dir>` instead. Set `DEFT_NOSTR_NSEC` to override the stored key for a session (accepts `nsec1…` or 64-char hex). The default relays are `wss://relay.damus.io`, `wss://nos.lol`, and `wss://relay.primal.net`; add or remove them from the Relays view. ## Views | View | Contents | |------|----------| | Feed | kind 1 notes and kind 7 reactions from every relay, newest first | | Relays | per-relay connection state; add/remove via the menu | | Identity | npub (shareable), hex pubkey, keyfile note | ## Keys (feed view) | Key | Action | |-----|--------| | `Enter` | publish the typed note (kind 1) | | `Escape` | cancel an armed reply | | `Ctrl+Up` / `Ctrl+Down` | move the selection through the feed | | `Ctrl+R` | reply to the selected note (NIP-10 root/reply + p tags) | | `Ctrl+T` | react `"+"` (kind 7) to the selected note | | `PgUp` / `PgDn` | scroll a viewport | | `Home` / `End` (`Ctrl+Home`/`Ctrl+End`) | jump to the top / bottom | | `Ctrl+M` | expand/collapse the selected note's metadata | | `Ctrl+E` | load replies referencing the selected note (`#e` filter) | | `Ctrl+O` | toggle the image dock for the selected note | | `Ctrl+V` | cycle Feed → Relays → Identity | The menu (`Nostr`) mirrors Publish / Reply / React / Open thread / Toggle metadata / Toggle image, and has `View`, `Relays` (add / remove / reconnect) and `Identity` (show npub). ## Feed layout Each note is a `deft/ui/components/collapsible` section whose body holds the full metadata (author, npub, timestamp, id) and is collapsed by default, so the feed shows just the message; click the `▸` chevron (or `Ctrl+M`) to reveal it. The feed is a row-scrollable region: the mouse wheel, `PgUp`/`PgDn` and `Home`/`End` scroll it by rows, and a scrollbar is drawn at the right edge. Clicking a note's metadata row toggles it; clicking anywhere else selects that note (the selection drives Reply/React/Open thread and the image dock). `Ctrl+Up`/`Ctrl+Down` move the selection note by note. ## Profile and NIP-05 Identity → Edit profile… prompts for display name, NIP-05 identifier (`name@domain`, blank clears) and about, then publishes a kind-0 metadata event for your key (and caches it locally). The Identity view shows your current profile fields. Profiles' `nip05` identifiers are verified against `https://<domain>/.well-known/nostr.json` (NIP-05), cached for 24h in `.deft/nostr.db` and refreshed at startup for cached profiles. Each note's expanded metadata shows `name@domain` with `✓` (verified), `✗` (mismatch) or `…` (checking). ## Images There is no inline-image syntax in Nostr: media is either a bare image URL in the content or a NIP-92 `imeta` tag. Both are surfaced in the feed as `[img] <url>` lines (with the `alt` text when present), and `Ctrl+O` opens a dock that downloads the selected note's first image into the media cache (`<DEFT_NOSTR_HOME>/media`, else `.deft/nostr-media`, sha256-named) and draws it — raster on the GL hosts, half-block cells on ANSI. Images are fetched on demand, never for every note. ## Library `import "deft/nostr/lib" as nostr` gives headless access to everything the panel uses:
set signed [nostr/sign-event [nostr/new-event 1 "hello" @{}] $seckey]
set pool   [nostr/pool-create @{ "wss://nos.lol" }
            {|m| [handle $m]} {|s| [handle-status $s]}]
[nostr/pool-start $pool]
[nostr/pool-subscribe $pool "feed" @{ %{ :kinds @{ 1 } :limit 50 } }]
[nostr/pool-publish $pool $signed]
Key functions: `sign-event` / `verify-event` / `event-id`, `reply-tags` / `reply-refs-of`, `tag-values`, `metadata-of` / `contacts-of`, the `pool-*` relay pool, `keys-ensure` / `keys-save`, and `db-open` / `db-save-any` / `db-recent` / `db-profiles`. NIP-19 helpers (`npub`, `nsec`, `note`, `decode-entity`, `encode-entity`) live in `deft/nostr/nip19`. ## Scope NIP-01 (relay protocol, events, filters, BIP-340 signing) and NIP-19 (bech32 entities), with kind 0 profiles, kind 1 notes, kind 3 contact lists, kind 7 reactions, NIP-10 reply tags, and NIP-92 image attachments plus bare image URLs. Encrypted DMs (NIP-04/44) are not implemented.