Add Namecoin NIP-05 resolution to the amy CLI as a stateless verb group, matching the Android and Desktop apps' resolution surface. amy namecoin resolve IDENT [--server URL[,URL]] [--timeout SECS] amy namecoin servers IDENT accepts the same shapes the apps accept: raw `d/` / `id/` names, bare `.bit` domains, and `alice@example.bit` NIP-05-style local-parts. Output is the resolved Nostr pubkey + relay list (+ the resolved Namecoin name + matched local-part) as machine-readable JSON (with `--json`) or human-readable text. The verb is stateless — no account, no `~/.amy/`, no relays — so it dispatches alongside `decode`/`encode`/`verify`/`nip`/`kind` before account resolution and the secret store. Zero new logic in cli/: the implementation is a thin command-file wrapper around quartz's existing `NamecoinNameResolver` + `ElectrumXClient` + the canonical `DEFAULT_ELECTRUMX_SERVERS` set the apps already ship with, including the pinned trust store for the self-signed Namecoin ElectrumX ecosystem. amy is headless so no UI piece is wired in. The `--server` flag accepts `host`, `host:port`, `tcp://`, `tls://`, `ssl://` per entry (defaults to TLS on 50002); empty / malformed entries fail with `bad_args` rather than silently using the default set, so a fat- fingered override can't go unnoticed. Outcomes from `NamecoinResolveOutcome` map to amy error codes: Success -> emit JSON, exit 0 NameNotFound -> error not_found NoNostrField -> error no_nostr_field MalformedRecord -> error malformed_record (+ namecoin_name extra) ServersUnreachable-> error servers_unreachable InvalidIdentifier -> error invalid_identifier Timeout -> error timeout Smoke-tested end-to-end on macOS arm64 against the live ElectrumX fleet: $ amy --json namecoin resolve d/testls {"identifier":"d/testls","namecoin_name":"d/testls", "local_part":"_","pubkey":"460c25e6…","relays":[]} $ amy namecoin servers count: 6 servers: - host: electrumx.testls.space port: 50002 tls: yes … No new runtime deps. The "no Compose UI in the amy image" CI assertion still passes — `NamecoinNameResolver` + `ElectrumXClient` are pure JVM (kotlinx.coroutines + kotlinx.serialization, both already on the CLI classpath via :quartz). Tests: the resolver, ElectrumX client, identifier parser, and the default server set already have JVM tests under `quartz/src/jvmTest/.../namecoin/` — no new core code in this PR, so the existing coverage applies. CLI verbs are exercised via the shell harnesses in `cli/tests/`; a Namecoin harness fits the same pattern but isn't included here. Parity matrix in `cli/ROADMAP.md` flags `name_history` and the Namecoin Core JSON-RPC backend as pending separate PRs — both already exist on Android and Desktop but aren't on upstream main yet (open PRs against this repo carry them).
12 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-60 / 61 Cashu wallet + nutzaps | ✅ | Full surface: cashu wallet {create,show,export-key,destroy}, mint {ping,info}, balance, receive {ln,complete,resume,token,nutzap-sweep}, send {ln,token,nutzap}, maintenance {scrub,restore,migrate-keysets}, mint-rec {show,add,remove} — all on shared commons CashuWalletOps + CashuWalletReader (the exact path the Android wallet runs). Interop harness pending. Plan: cli/plans/2026-05-28-cashu-cli.md. |
| 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) | 🆕 | |
Namecoin NIP-05 resolve (amy namecoin resolve .bit|d/|id/) |
✅ | NamecoinCommand — reuses Quartz NamecoinNameResolver + ElectrumXClient + the default ElectrumX server set the Android/Desktop apps ship with. Stateless. On-chain name_history + Core RPC backend pending separate PRs. |
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|validate |
✅ | generate/derive + NIP-49 encrypt/decrypt (bidirectionally nak-verified) + validate (npub/hex parse check). expand/combine(MuSig2)/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. Also accepts a nip19/nip05 code and resolves relays via the outbox model (code hints + author's NIP-65 write relays), like nak's fetch. |
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 fallback (NipText kind:30817, wiki:30818, long-form:30023); nip list. |
kind |
amy kind |
✅ | quartz KindNames registry (kind → English label + NIP) covering every event kind quartz defines (280 entries); 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. |
admin |
amy admin RELAY METHOD |
✅ | NIP-86 Relay Management over NIP-98 HTTP auth — full method set (ban/allow pubkey + event, kinds, IP block, change name/desc/icon, list-*). Reuses quartz Nip86Client + shared commons Nip86Retriever. Interop-verified against amy serve. |
serve |
amy serve |
✅ | Embeds geode (the standalone Ktor relay on quartz's relay-server code) — in-memory by default, --db FILE for SQLite, account is admin so amy admin works against it. NIP-86 + NIP-77 included. |
wallet (NIP-60 Cashu) |
amy cashu |
✅ | See the Cashu row above — full NIP-60/61 wallet + nutzaps. |
mcp / fs / spell |
— | 🆕 (niche) | MCP server, FUSE mount, MuSig2/FROST; some pull new deps. |
Full nak comparison (introspected both binaries)
nak has 34 functional commands (introspected from nak --help). Coverage:
- Full / equivalent (24):
event,req(→fetch+subscribe),fetch(nip19/nip05-hint resolution),filter,count,decode,encode,verify,relay,bunker(+nostrconnect+auth_url),encrypt,decrypt,gift,publish,sync,profile,podcast,nip,kind,blossom,nsite(NIP-5A),admin(NIP-86),serve(geode),wallet(NIP-60/61 Cashu). Protocol-sensitive ones (bunker,sync,keyNIP-49,encode/decode,admin) are interop-verified against the realnakbinary oramy serve. - Partial / adapted (3):
key(noexpand/combine(MuSig2)/default),git(NIP-34 events only — no packfile transport),outbox(NIP-65 model vs nak's local hints DB). - Missing (7):
dekey(NIP-4E),mcp,curl(NIP-98),fs(FUSE),spell(MuSig2/FROST),validate(event-schema validation), andgroup/nip29(NIP-29 — amy ships MLS/Marmot instead, an intentional divergence rather than a gap).
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 (hex left-pad) and default
(print the active account's key) sub-verbs. key combine needs MuSig2.
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.