`tools/` is for libraries (arti-build); shell-based interop harnesses
that drive the `amy` binary belong with the CLI module. New layout:
cli/tests/
├── lib.sh # shared logging + result tracking
├── headless/helpers.sh # shared amy_a / amy_json / assertions
├── marmot/ # MLS group-messaging interop (vs whitenoise-rs)
│ ├── marmot-interop.sh
│ ├── marmot-interop-headless.sh
│ ├── setup.sh
│ ├── tests-create.sh
│ ├── tests-manage.sh
│ ├── tests-extras.sh
│ └── patches/
└── dm/ # NIP-17 DM interop (amy ↔ amy)
├── dm-interop-headless.sh
├── setup.sh
└── tests-dm.sh
Per-suite changes:
- Each suite owns its own setup.sh; previously the Marmot setup was at
headless/setup.sh and the DM setup at headless/setup-dm.sh, which
implied shared infra they don't actually share.
- `cli/tests/lib.sh` and `cli/tests/headless/helpers.sh` are the only
shared bits across suites.
- The DM harness still reuses Marmot's `start_local_relay` /
`stop_local_relay` (sourced via `../marmot/setup.sh`) — same relay
binary, no duplication.
- All sourcing paths updated; REPO_ROOT now climbs three levels (was
two when the harness lived under `tools/`); patches/ is now a sibling
of marmot/setup.sh rather than under marmot/headless/.
- `.gitignore` updated to cover both suites' state dirs.
cli/ROADMAP.md and cli/tests/README.md updated to point at the new paths.
No behavioural change — every test runs the same code; only the file
layout and source paths moved.
5.3 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".
- How the CLI is used today: README.md
- How to implement an item: 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 note publish TEXT) |
🆕 | Needs a commons/ builder wrapper. |
NIP-01 feed read (amy feed home, amy feed hashtag #X, amy feed profile NPUB) |
🆕 | Extract FeedFilter usage from amethyst/ui/dal/ into commons/ entry points. |
| 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 | 🆕 | |
| 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) |
⚠️ | Blocked on event-renderer plan in commons/plans/. |
Thread view (amy thread show EVENT_ID) |
⚠️ | Same. |
| Notifications feed | 🆕 | |
| Search (NIP-50) | 🆕 |
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 note publish/amy note show/amy note react— smallest end-to-end write+read loop outside Marmot.amy feed home|profile|hashtag|threadreading through the renderer.amy follow add|remove|list(NIP-02) — proves extraction of list-building logic fromamethyst/model/.amy dm send|list(NIP-17) — reuses the gift-wrap path already 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.