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.