Files
nostr_terminal/plans/planning.md
T

20 KiB

Nostr Terminal (nt) C99 Port - Planning Document

Overview

Recreating the Node.js-based nt (Nostr Terminal) in C99, leveraging nostr_core_lib for core Nostr functionality. The original implementation is in resources/NostrTerminal_5/nt.mjs.

Core Design Constraint: No Persistent Relay Pool

Every operation follows: connect → act → disconnect.

  • No background sockets between commands
  • No reconnection state machine
  • Each menu action opens the relays it needs, performs the work, and closes them
  • Live feeds are an explicit mode — entering it opens sockets; leaving it closes them
  • Local SQLite is the only thing that persists between operations

This shapes every design choice below: there is no "pool object" to keep alive, no event loop running idle, no shared connection state across menus.


Architecture Overview

Core Dependencies

Component Purpose Source
nostr_core_lib Nostr protocol, crypto, relay pool, NIPs resources/nostr_core_lib/
cJSON JSON parsing Bundled in nostr_core_lib/cjson/
OpenSSL TLS for WebSockets System library
SQLite3 Local event/relay caching System library
ncurses Terminal UI System library
libcurl HTTP requests (AI APIs, NIP-11) System library

Global State (from original)

  • OBJ_KEYS - User keypair (nsec/npub hex/bech32)
  • KIND_0 - User metadata
  • KIND_3 - Follows list
  • KIND_10002 - Relay list metadata
  • KIND_10096 - Blossom media server list
  • KIND_17375 - Cashu wallet metadata
  • KIND_PREFS (30078) - User preferences (AI model, mints)
  • OBJ_PROOFS - Cashu proofs by mint
  • SQLite DB at ~/.nostr/nostr.db

Priority Legend

  • 🔴 P0 - Critical: Must-have for MVP (basic Nostr client functionality)
  • 🟠 P1 - High: Important features that complete the core experience
  • 🟡 P2 - Medium: Nice-to-have, enhances usability
  • 🟢 P3 - Low: Optional/experimental features

Status Legend

  • ✅ Available in nostr_core_lib
  • 🟦 Partially available (needs glue code)
  • ❌ Missing from nostr_core_lib (needs new implementation or external lib)

Menu Item Breakdown

🔴 P0 - [L]og in

Function: LogIn() Description: Accept seed phrase, nsec, nsecHex, npub, bunker URL, or create new account. Derives keys via BIP39/BIP32 (path m/44'/1237'/account'/change/index).

Sub-feature nostr_core_lib Status Notes
Generate mnemonic ✅ nip006.h See mnemonic_generation.c
Validate mnemonic ✅ nip006.h
Derive keys from seed ✅ nip006.h See mnemonic_derivation.c
nsec/npub bech32 encoding ✅ nip019.h
NIP-46 bunker login ✅ nip046.h See nip46_remote_signer.c
Load user info from relays ✅ Via relay pool Need to fetch KIND 0, 3, 10002, 10096, 17375, 30078

🔴 P0 - [P]rofile

Function: Profile() Description: Display and edit user metadata (KIND 0). Fields: name, displayname, about, picture, banner, website, lud16, nip05.

Sub-feature nostr_core_lib Status Notes
Read KIND 0 ✅ Via relay pool
Publish KIND 0 ✅ nip001.h
NIP-05 verification ✅ nip005.h

🔴 P0 - [R]elays

Function: EditRelays() Description: Manage relay list (KIND 10002). Add/delete/modify relays, set read/write flags, update from nostr.watch, run kind-1 acceptance tests.

Sub-feature nostr_core_lib Status Notes
Connect to relays ✅ core_relay_pool.c See relay_pool.c
Publish KIND 10002 ✅
Fetch NIP-11 info ✅ nip011.h
Test relay (publish kind 1) 🟦 Need wrapper: timed publish + response capture
Fetch from nostr.watch API ❌ Need libcurl HTTP GET
Store relays in SQLite ❌ Custom SQLite layer needed

🔴 P0 - [T]weet

Function: Tweet() Description: Quick post a KIND 1 note. Optional vipe editor launch.

Sub-feature nostr_core_lib Status Notes
Sign + publish KIND 1 ✅ nip001.h
Launch external editor ❌ Need fork()/exec() wrapper for vipe

🔴 P0 - [F]ollows

Function: Follows() Description: Display/edit follows list (KIND 3). Add/delete follows, load follow profile info, view profiles.

Sub-feature nostr_core_lib Status Notes
Read/Write KIND 3 ✅
Fetch KIND 0 for follows ✅ Via relay pool
npub <-> hex conversion ✅ nip019.h

🟠 P1 - Li[v]e Feeds

Function: LiveFeed() Description: Real-time event subscriptions. Sub-menu: Follows feed, all from/about follows, notifications, mentions, firehose.

Sub-feature nostr_core_lib Status Notes
Subscribe to events ✅ core_relay_pool.c
Multi-filter subscriptions ✅
Live event callbacks ✅
Cache events to SQLite ❌ Custom layer

🟠 P1 - [N]otifications

Function: Notifications() Description: Show recent events tagging the user (#p). Reactions (kind 7) and replies (kind 1) over last 3 days.

Sub-feature nostr_core_lib Status Notes
Query events by #p tag ✅ Via relay pool filters
Sort/group by date ❌ Application logic

🟠 P1 - [W]rite

Function: Write() Description: Launch vipe editor, then choose: save to diary (kind 30024), post new (kind 30023/30024), append to existing post, tweet (kind 1), or discard.

Sub-feature nostr_core_lib Status Notes
Long-form notes (30023/30024) ✅ Via generic publish
NIP-04 encryption ✅ nip004.h
Editor integration ❌ fork()/exec() for vipe

🟠 P1 - P[o]sts

Function: EditNotes() Description: Browse/edit user's notes by kind. Sub-menu: private blog (30024), public blog (30023), encrypted data (30078), all posts.

Sub-feature nostr_core_lib Status Notes
Query addressable events (30000-39999) ✅
NIP-04 encrypt/decrypt content + tags ✅ nip004.h
Delete event (kind 5) ✅ Via generic publish

🟠 P1 - Direct [M]essage

Function: nip04Messaage() Description: Send/receive encrypted DMs. Currently uses NIP-04, should use NIP-17 for new messages.

Sub-feature nostr_core_lib Status Notes
NIP-04 DMs (legacy) ✅ nip004.h
NIP-17 sealed DMs ✅ nip017.h See send_nip17_dm.c
NIP-44 encryption ✅ nip044.h
NIP-59 gift wrap ✅ nip059.h

🟡 P2 - To[d]o

Function: Todo() Description: Personal todo list stored as encrypted KIND 30078 event with d=todo. Add, complete, reorder, delete items.

Sub-feature nostr_core_lib Status Notes
Read/write KIND 30078 ✅
NIP-04 encrypt JSON content ✅ nip004.h

🟡 P2 - D[i]ary

Function: Diary() Description: Daily encrypted diary entry. KIND 30024 with d=YYYYMMDD, t=DD. Launches vipe.

Sub-feature nostr_core_lib Status Notes
Same as Posts/Write ✅

🟡 P2 - [E]cash (Cashu Wallet)

Function: CWallet() Description: Chaumian eCash wallet via Cashu. Add/remove mints, request mint quotes, send/receive eCash tokens, manage proofs (KIND 7375), wallet metadata (KIND 17375).

Sub-feature nostr_core_lib Status Notes
Cashu mint client ✅ cashu_mint.h See cashu_wallet.c
NIP-60 wallet (kind 17375) ✅ nip060.h
NIP-61 nutzaps ✅ nip061.h
NIP-44 encrypt proofs ✅ nip044.h
QR code generation ❌ Need libqrencode or port
Cashu token encode/decode (V4) 🟦 Verify in cashu_mint.c

🟡 P2 - [A]I

Function: AI() Description: Chat with AI models. Choose source (Venice.ai or local Ollama), list models, send prompts.

Sub-feature nostr_core_lib Status Notes
HTTP POST to Venice/Ollama ❌ Need libcurl wrapper
Store prefs (KIND 30078, d=prefs) ✅
NIP-04 encrypt prefs ✅ nip004.h

🟢 P3 - [C]ircus (AI Bot Troop)

Function: Circus() Description: Generate N AI personas (using BIP32 derivation change=1), publish their KIND 0/3, have them tweet via AI prompts. Posts to local circus relay.

Sub-feature nostr_core_lib Status Notes
Multi-account derivation ✅ nip006.h
Publish to specific relay set ✅
AI prompt fetch ❌ Same as AI menu

🟢 P3 - Bl[o]ssom (Media Servers)

Function: Blossom() Description: Manage Blossom media server list (KIND 10096). Add/delete/modify servers.

Sub-feature nostr_core_lib Status Notes
Blossom client ✅ blossom_client.h
Publish KIND 10096 ✅

🟢 P3 - [DB] (Database Maintenance)

Function: DB() Description: Bulk operations. Sub-menu: fetch new relays from nostr.watch, get NIP-11 info for all relays, run kind-1 tests, fetch info on all known people, fetch follows' kind-1 notes, fetch replies to follows.

Sub-feature nostr_core_lib Status Notes
All bulk relay/event fetching ✅ Via relay pool
SQLite bulk inserts ❌ Custom layer

🟢 P3 - [B]rowser

Function: LaunchBrowser() Description: Launches brave-browser --app=https://localhost:PORT/nostr to view companion web UI.

Sub-feature nostr_core_lib Status Notes
Launch external process ❌ fork()/exec()
HTTPS server ❌ Optional - skip unless web UI is desired

🟢 P3 - [K]inds (Debug View)

Function: Inline in MainMenu() case "k" Description: Dump all loaded KIND_* globals to screen for debugging.

Sub-feature nostr_core_lib Status Notes
Print state N/A Trivial

🔴 P0 - [Q]uit

Trivial - just exit cleanly.


Cross-Cutting Infrastructure (Required for ALL menus)

🔴 P0 - Terminal UI Layer

Status: ❌ Not in nostr_core_lib Description: Replicate getLine(), getKey(), ClearScreen(), cl() from utilities.mjs. Custom rendering of ^_X^: highlighted hotkeys. Library: ncurses or raw VT100 escape codes.

🔴 P0 - SQLite Layer

Status: ❌ Not in nostr_core_lib Description: Replicate schema from BuildNostrSQLiteDB():

  • regular_events, replacable_events, addressable_events, ephemeral_events
  • relays (with NIP-11 fields, test results)
  • relay_posts (publish history)
  • local (key-value state)
  • people (cached pubkeys/names)

🔴 P0 - HTTP Client

Status: 🟦 Partial - nostr_http.h exists Description: Verify it supports arbitrary GET/POST with custom headers. May need libcurl for Venice/Ollama JSON APIs.

🔴 P0 - Per-Action Network Helpers (no global event loop)

Status: 🟦 nostr_core_lib provides the building blocks Description: Because of the connect → act → disconnect rule, we do not run a global event loop multiplexing background sockets. Instead we provide small synchronous helpers that each:

  1. Open the required relay sockets
  2. Send the request (EVENT/REQ)
  3. Pump messages until done (OK/EOSE/timeout)
  4. Close the sockets and return

Helpers to build:

  • nt_publish(event, relays, timeout) — connect → EVENT → wait OK → close
  • nt_query_sync(filter, relays, timeout, &events) — connect → REQ → collect until EOSE → CLOSE → close socket
  • nt_get_first(filter, relays, timeout, &event) — same, returns first match
  • nt_live_feed(filters, relays, on_event_cb) — the one exception: blocking call used only inside the explicit "Live Feeds" menu; returns (and closes sockets) when the user hits a key

The TUI is purely synchronous — getLine() blocks on stdin between actions; no concurrent socket pumping while the user is typing.

🟡 P2 - External Process Spawning

Status: ❌ Not in nostr_core_lib Description: Wrapper around fork()/exec()/pipe() for launching vipe editor and capturing stdout.

🟢 P3 - QR Code Rendering

Status: ❌ Not in nostr_core_lib Description: For Cashu wallet (mint quotes, send tokens). Use libqrencode or port a small QR library to render to terminal.

🟢 P3 - HTTPS/WebSocket Server

Status: ❌ Not in nostr_core_lib Description: For companion web UI (/nostr endpoint, WSS broadcast). Skip for MVP.


Missing from nostr_core_lib (Roadmap Items)

Based on nostr_core_lib/todo.md, the following are not yet complete in nostr_core_lib but needed:

  1. NIP-01 Event Validation - Currently in progress (per todo.md):

    • nostr_validate_event_structure() - 🚧 declared, not implemented
    • nostr_verify_event_signature() - 🚧 declared, not implemented
    • nostr_validate_event() - 🚧 declared, not implemented
    • Impact: We need to validate incoming events from relays. Could work around by using Schnorr verify directly, but a complete API is preferred.
  2. Higher-level relay query helpers - The original pool.querySync() and pool.get() patterns aren't directly mirrored. We may need to build:

    • nt_query_sync(relays, filter, timeout, &events)
    • nt_get_first(relays, filter, timeout, &event)
  3. Event deduplication for replaceable/addressable kinds - The original picks the latest by created_at. Likely needs application-side logic.

  4. NIP-42 AUTH integration in pool - Per plans/nip42_relay_pool_plan.md, this is planned but unverified.


Implementation Roadmap

Phase 1: Foundation (P0 Infrastructure)

  • Set up CMake build with linkage to nostr_core_lib (static lib) — see CMakeLists.txt
  • Implement TUI layer with ncurses — see include/tui.h and src/tui.c (raw VT100/termios, not ncurses, but functionally equivalent)
  • Implement SQLite layer (db.c/h) with partial schema — see include/db.h and src/db.c (events / relays / people / local / relay_posts present; full original schema split into regular/replacable/addressable/ephemeral still pending)
  • Replace event-loop concept with synchronous per-action network helpers — see include/net.h (nt_publish, nt_query_sync, nt_get_first all implemented)
  • Implement HTTP wrapper (libcurl) — nt_fetch_nip11() is currently a stub returning -1; needed for NIP-11, nostr.watch, Venice/Ollama

Phase 2: Core Nostr Client (P0 Menus)

  • [L]ogin (mnemonic) — see src/menu_login.c (existing-account path: seed phrase / nsec / nsecHex / npub; new-account path; loads KIND 0/3/10002/10096/17375/30078)
  • [L]ogin bunker (NIP-46) sub-path — not yet wired
  • [P]rofile (view + modify + publish KIND 0) — see src/menu_profile.c
  • [R]elays (list / add / delete / modify / publish KIND 10002) — see src/menu_relays.c (NIP-11 fetch is stubbed; nostr.watch sync and kind-1 acceptance test pending)
  • [T]weet (sign + publish KIND 1) — see src/menu_tweet.c (no vipe integration yet)
  • [F]ollows (list / add / delete / publish KIND 3) — see src/menu_follows.c (Profile sub-view from follows list pending)
  • [Q]uit
  • [K]inds debug dump — implemented in src/main.c

Phase 3: Reading & Interaction (P1 Menus)

  • Li[v]e Feeds — needs a long-running nt_live_feed() helper added to src/net.c
  • [N]otifications — query events with #p tag, group/sort
  • [W]rite (with vipe integration) — needs external editor wrapper
  • P[o]sts (browse / edit user notes by kind, NIP-04 decrypt for 30078)
  • Direct [M]essage (NIP-17 send/receive, NIP-04 receive for legacy)

Phase 4: Advanced Features (P2 Menus)

  • To[d]o (encrypted KIND 30078, d=todo)
  • D[i]ary (KIND 30024 daily entries via vipe)
  • [E]cash wallet (NIP-60/61, Cashu mint client, QR rendering)
  • [A]I integration (Venice/Ollama via libcurl, prefs in KIND 30078)

Phase 5: Optional/Experimental (P3 Menus)

  • [C]ircus (multi-account derivation + AI-driven posts)
  • Bl[o]ssom (KIND 10096 manage media servers)
  • [DB] maintenance (bulk relay + people + notes refresh)
  • [B]rowser launcher
  • HTTPS/WSS companion server

Current Status Snapshot (auto-summary)

Done — MVP usable: Build, TUI, SQLite scaffold, synchronous net helpers, login (seed-based), profile, relays (without NIP-11/test), tweet, follows, kinds dump, quit.

Immediate gaps blocking the rest of P0:

  1. HTTP client — nt_fetch_nip11() is a stub. Without it, the relay menu can't show NIP-11 info, can't query nostr.watch, and the AI menu can't talk to Venice/Ollama. This is the single biggest unblocker.
  2. Relay acceptance test — small wrapper around nt_publish to publish a throwaway kind-1 to a single relay with a tight timeout; needed for the Relay menu's test feature.
  3. NIP-46 bunker login — alternate auth path inside src/menu_login.c.

Next P1 tickets in recommended order:

  1. nt_live_feed(filters, relays, on_event_cb) in src/net.c — blocking subscription that exits on any keypress; foundation for both Live Feeds and Notifications menus.
  2. External editor wrapper (fork/exec/pipe to vipe, return captured stdout) — unlocks Write, Diary, and richer Tweet.
  3. [N]otifications menu — straight nt_query_sync over #p filter on read relays, then group/sort.
  4. P[o]sts menu — query addressable kinds (30023/30024/30078); NIP-04 decrypt for 30078.
  5. Li[v]e Feeds menu — built on nt_live_feed; sub-options for follows/firehose/mentions.
  6. Direct [M]essage — NIP-17 send via existing nip017.h, plus inbox query.