Introduce a canonical, i18n-free event-kind registry in quartz:
`com.vitorpamplona.quartz.kinds.KindNames` maps each of the 145 known
kinds to an English label + the defining NIP. The data is ported from
Amethyst's relay-view `kindDisplayName` mapping (the NIP derived from
each event class's package), so the "what is kind N" knowledge now lives
once in quartz instead of only in the Android UI.
`amy kind <N|NAME>` looks a kind up by number (label + NIP) or searches
labels by name — a thin wrapper over KindNames, dispatched statelessly
(no account/network).
i18n split: quartz holds the canonical English (quartz is intentionally
translation-free); localized front ends overlay their own strings and can
fall back to KindNames.nameFor() for kinds they don't translate. amy
prints the English directly.
Verified: kind 1/0/30023/1059/24133 labels+NIPs, name search ("podcast"
→ 4), unknown → known:false, text + JSON output.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011SapGdtAc1j7woifoCZ9fY
9.7 KiB
Amy Roadmap
The live checklist for growing amy from a Marmot test-bed into a
full command-line mirror of Amethyst.
How to use this file: update it in the same PR that ships a feature. Move rows between tables, adjust ordering, add non-goals. This is the single source of truth for "what's left".
- What amy is + how to use it: README.md
- The public contract + how to extend amy: DEVELOPMENT.md
- Ongoing design plans: plans/
- Shared work consumed here: ../commons/plans/
North-star goal
For every feature of the Amethyst Android app, there is a way to exercise it through
amy, with byte-identical on-relay behaviour.
Why:
- Interop tests against the ~100 other Nostr clients need a reproducible harness that does not require running Android.
- Agents and LLMs can script real Amethyst flows without a GUI.
- Regressions in shared logic (signing, encryption, filter building, event parsing) become shell-scriptable.
- Power users get a command-line Amethyst for free.
Non-goal: Amy is not a second Nostr client implementation. It is
a thin assembly layer over quartz + commons. Protocol and business
logic in cli/ are bugs.
Parity matrix
Status legend: ✅ shipped · 📦 logic lives in commons/, needs a command ·
🆕 needs extraction from amethyst/ first · ⚠️ blocked (see Notes).
| Area | Status | Notes |
|---|---|---|
Identity create / import (nsec, ncryptsec, mnemonic, npub, nprofile, hex, NIP-05) |
✅ | LoginCommand + Quartz NIP-05 / NIP-06 / NIP-49 |
| Account bootstrap (nine events) | ✅ | commons/account/AccountBootstrapEvents.kt |
| Relay config + NIP-65 / NIP-10050 publish | ✅ | RelayCommands |
| MLS KeyPackage publish + fetch | ✅ | commons/marmot/MarmotManager |
| Marmot group create / add / rename / promote / demote / remove / leave | ✅ | commons/marmot/ |
| Marmot message send / list | ✅ | commons/marmot/ |
await polling (KP / group / member / admin / message / rename / epoch) |
✅ | AwaitCommands |
NIP-01 note publish (amy notes post TEXT) |
✅ | PostCommand — outbox via RelayCommands configured set. |
NIP-01 feed read (amy notes feed [--following | --author NPUB]) |
✅ | FeedCommand. Hashtag / community feeds still pending. |
| NIP-02 follow list add / remove / list | 🆕 | Logic in amethyst/model/nip02FollowLists/. |
| NIP-09 event deletion | 🆕 | Builder exists in quartz. |
| NIP-17 DMs send / list / await | ✅ | DmCommands — reuses Quartz NIP17Factory + RecipientRelayFetcher; filter extracted to commons/relayClient/nip17Dm/. Plan: cli/plans/2026-04-23-nip17-dm.md. |
| NIP-18 reposts / quotes | 🆕 | |
| NIP-25 reactions | ✅ in groups · 🆕 elsewhere | marmot message react covers MLS group reactions; outer-event reactions still pending. |
| NIP-51 lists (bookmarks, mute, follow sets) | 🆕 | amethyst/model/nip51Lists/ |
| NIP-57 zaps (send + verify) | 🆕 | Needs LN-URL plumbing; amethyst/service/lnurl/. |
| NIP-65 outbox model queries | 🆕 | |
| NIP-72 communities | 🆕 | |
| NIP-78 app-specific data (settings sync) | 🆕 | |
| Long-form (NIP-23) publish / read | 🆕 | |
| Live activities / chess (NIP-53 / NIP-64) | 🆕 | |
| Blossom uploads (NIP-B7) | 🆕 | |
| NIP-47 Wallet Connect | 🆕 | |
| NIP-46 bunker signer | 🆕 | Needs a signers abstraction in Amy. |
Profile view (amy profile show NPUB) + edit |
✅ | ProfileCommands. Cache-first; --refresh forces a relay drain. |
Thread view (amy thread show EVENT_ID) |
⚠️ | Same. |
| Notifications feed | 🆕 | |
| Search (NIP-50) | 🆕 |
nak parity — army-knife primitives
Tracking fiatjaf/nak's command surface. amy
adapts the verbiage where its own conventions differ (req → one-shot fetch
vs streaming subscribe). Stateless verbs run with no account or network.
| nak command | amy verb | Status | Notes |
|---|---|---|---|
decode |
amy decode |
✅ | NIP-19/21 → JSON. Quartz Nip19Parser. |
encode |
amy encode |
✅ | npub/nsec/note/nevent/nprofile/naddr. |
verify / validate |
amy verify |
✅ | id-hash + signature, reported separately. |
key |
amy key generate|public|encrypt|decrypt |
✅ | generate/derive + NIP-49 encrypt/decrypt (bidirectionally nak-verified). expand/combine/validate/default still 🆕. |
event |
amy event |
✅ | build/sign an arbitrary event, optional --publish/--relay. |
publish |
amy publish |
✅ | broadcast a pre-made event JSON (verified first). |
req (one-shot) |
amy fetch |
✅ | filter → collect-until-EOSE, dedupe, sort, cap. |
req (stream) |
amy subscribe |
✅ | filter → live NDJSON stream to stdout. |
count |
amy count |
✅ | NIP-45, per-relay counts. |
encrypt / decrypt |
amy encrypt|decrypt |
✅ | raw NIP-44 (default) / NIP-04. |
gift |
amy gift wrap|unwrap |
✅ | NIP-59 seal+wrap / unwrap+unseal. |
relay (NIP-11) |
amy relay info |
✅ | stateless NIP-11 doc fetch. |
outbox |
amy outbox |
✅ | NIP-65 read/write relays, cache-first. |
filter |
amy filter |
✅ | stateless — assemble + print a filter JSON. |
blossom |
amy blossom |
✅ | upload/download/list/delete/check/mirror (reuses commons BlossomClient). |
nip |
amy nip |
✅ | repo-first lookup + Nostr wiki/long-form fallback; nip list. |
kind |
amy kind |
✅ | quartz KindNames registry (kind → English label + NIP), ported from the Android relay view; number lookup + name search. |
sync |
amy sync |
✅ | NIP-77 Negentropy reconcile with the local store (down/up/both). |
git |
amy git |
✅ in part | NIP-34 repo announce/list/show/issue. clone/push (packfile transport) out of scope. |
podcast |
amy podcast |
✅ | NIP-F4 show metadata (10154) + episode publish (54) + list. |
bunker |
amy bunker[ connect] + amy login bunker:///--nostrconnect |
✅ | NIP-46 remote signer + login, both the bunker:// and nostrconnect:// flows, each direction, plus auth_url challenge handling (client surfaces the URL + keeps waiting). Interop-verified vs real nak. |
serve / admin / wallet / mcp / fs / spell |
— | 🆕 (tier 2/3) | larger/niche; some pull new deps. |
Full nak comparison (introspected both binaries)
nak has 34 functional commands. Coverage:
- Full / equivalent (19):
event,req(→fetch+subscribe),filter,count,decode,encode,verify,relay,bunker(+nostrconnect+auth_url),encrypt,decrypt,gift,publish,sync,profile,podcast,nip,kind,blossom. Protocol-sensitive ones (bunker,sync,keyNIP-49,encode/decode) are interop-verified against the realnakbinary. - Partial / adapted (4):
key(noexpand/combine/validate/default),git(NIP-34 events only — no packfile transport),outbox(shows NIP-65 vs nak's hints DB),fetch(filter-based, not nip19-hint resolution). - Missing (11):
admin(NIP-86),serve(geode is a standalone relay),dekey(NIP-4E),wallet(NIP-60 Cashu),mcp,curl(NIP-98),fs(FUSE),group/nip29(NIP-29 — amy has MLS/Marmot instead),spell(MuSig2/FROST),validate(RoK schema).
Design differences (not gaps): amy is a stateful client (accounts,
~/.amy/, shared event store) with a stable JSON contract; nak is a stateless
per-invocation tool that prints bare values for shell substitution. amy also has
a large surface nak lacks: Marmot/MLS, NIP-17 DMs, zaps, CLINK offer/debit,
NIP-02 follow, NIP-50 search, napplets, profile edit, store management, account
management.
Cheap remaining wins: the key expand/combine/validate/default sub-verbs.
Order of operations
Proposed sequencing. Each step is one PR. Each step should extract
at least one file from amethyst/ into commons/; if it doesn't
move anything, re-audit — you're probably duplicating logic.
- Event rendering core in
commons/commonMain/.../rendering/with renderers for kinds 0 / 1 / 3 / 6 / 7 / 10002 / 10050. Unblocks all the 🆕 and ⚠️ read-path rows below. Design:commons/plans/2026-04-21-event-renderer.md. amy notes post/amy notes show/amy notes react— smallest end-to-end write+read loop outside Marmot. Post + feed ✅ shipped;notes showand outer-eventreactstill pending.amy notes feed home|profile|hashtag|threadreading through the renderer.--followingand--author NPUB✅; hashtag/thread variants still pending.amy follow add|remove|list(NIP-02) — proves extraction of list-building logic fromamethyst/model/.amy dm send|list(NIP-17) — ✅ shipped. Reuses the gift-wrap path also exercised by Marmot.amy list bookmarks|mute|pin …(NIP-51).amy zap send|verify(NIP-57).- Distribution — Homebrew + Scoop +
.debin the same release pipeline as desktop. Plan:cli/plans/2026-04-21-cli-distribution.md. - Test suite — end-to-end against a local relay. Marmot interop is
covered by
cli/tests/marmot/marmot-interop-headless.sh; NIP-17 DM interop between twoamyclients is covered bycli/tests/dm/dm-interop-headless.sh(text + file + strict 10050 + fallback + cursor-advance). Neither runs in CI yet (both need Rust + ~3 min cold relay build). - Everything else in the matrix.
Non-goals
- Interactive TUI or REPL.
- Native image (GraalVM) until Quartz has a pure-Kotlin signer
fallback —
secp256k1-kmp-jni-*needs JNI today. - A Gradle dependency on
:amethystor:desktopApp. Ever. - Re-implementing any Nostr protocol piece that's already in
quartz/or in another client's library.