From fa48d7188f87ef1e2a1f58eeff712e8a1dba3892 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Apr 2026 15:48:32 +0000 Subject: [PATCH] docs(cli): slim README, refresh DEVELOPMENT + ROADMAP for USAGE.md split MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit USAGE.md is now the single home for "how do I use amy" — install, quick start, examples, command reference, troubleshooting. README was carrying all of that PLUS the public-contract material; with USAGE in place it can collapse to just the contract. README.md (338 → 176 lines): - Keep: 1-paragraph intro (three audiences), output contract, local event-store explainer, relay-routing rules, on-disk layout, cross- references to USAGE/DEVELOPMENT/ROADMAP. - Drop: install, quick start, command reference table, global flags table, account-management verbs, troubleshooting (all in USAGE). DEVELOPMENT.md: - Architecture diagram updated to reflect new files (Aliases.kt, UseCommand.kt, ProfileCommands.kt, DmCommands.kt, NotesCommands / PostCommand / FeedCommand, MarmotResetCommand, StoreCommands, SecureFileIO, secrets/ subtree). - "Keep three things in sync" pointers redirected from README's command table to USAGE.md. - Testing table loses the duplicate "Interop with other clients" row (already covered by the harness row above). - Cross-reference list at the top now mentions USAGE.md. ROADMAP.md: - Cross-reference list adds USAGE.md. - Parity matrix marked ✅ for items shipped on this branch: notes post + feed (PostCommand/FeedCommand), profile show+edit (ProfileCommands), DMs (already ✅). - Reactions split into "✅ in groups, 🆕 elsewhere" since marmot message react is shipped but outer-event reactions aren't. - Order-of-operations entries marked ✅ where relevant. --- cli/DEVELOPMENT.md | 39 ++++--- cli/README.md | 254 ++++++++------------------------------------- cli/ROADMAP.md | 25 +++-- 3 files changed, 83 insertions(+), 235 deletions(-) diff --git a/cli/DEVELOPMENT.md b/cli/DEVELOPMENT.md index 54e421c96..7eda47423 100644 --- a/cli/DEVELOPMENT.md +++ b/cli/DEVELOPMENT.md @@ -2,7 +2,8 @@ How to touch the `cli/` module without breaking its public contract. -- What Amy is and what's already shipped: [README.md](./README.md). +- What Amy is and the public contract: [README.md](./README.md). +- How to use it: [USAGE.md](./USAGE.md). - What to build next and in what order: [ROADMAP.md](./ROADMAP.md). - Plans for cross-cutting work: see this module's `plans/` folder and `commons/plans/` for shared-code work consumed by Amy. @@ -46,25 +47,31 @@ cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/ ├── Main.kt # argv → subcommand dispatch ├── Args.kt # tiny flag parser (no framework) ├── Output.kt # text/json mode emitter (--json flag) -├── Config.kt # Identity, RunState, DataDir +├── Aliases.kt # per-account aliases.json read/write +├── Config.kt # Identity, RunState, DataDir (~/.amy layout) ├── Context.kt # per-run wiring: signer + NostrClient + │ # MarmotManager + publish/drain/sync helpers +├── SecureFileIO.kt # 0600/0700 atomic writes, perm tighten ├── stores/FileStores.kt # File-backed MLS / KP / message stores +├── secrets/ # SecretStore backends (keychain / ncryptsec / plaintext) └── commands/ - ├── Commands.kt # dispatcher + ├── Commands.kt # dispatcher tables + ├── UseCommand.kt # `amy use NAME` — pin active account ├── InitCommands.kt # init, whoami ├── CreateCommand.kt # full bootstrap (→ commons/account/) ├── LoginCommand.kt # nsec/ncryptsec/mnemonic/npub/nprofile/hex/nip05 ├── RelayCommands.kt # add/list/publish-lists + ├── ProfileCommands.kt # profile show / edit (kind:0) + ├── NotesCommands.kt + PostCommand.kt + FeedCommand.kt # kind:1 + ├── DmCommands.kt # NIP-17 dm send / send-file / list / await ├── KeyPackageCommands.kt # marmot key-package publish / check - ├── GroupCommands.kt # marmot group create/list/show/… - ├── GroupCreateCommand.kt - ├── GroupReadCommands.kt - ├── GroupAddMemberCommand.kt - ├── GroupMembershipCommands.kt - ├── GroupMetadataCommands.kt - ├── MessageCommands.kt # marmot message send / list - └── AwaitCommands.kt # poll-until-condition helpers + ├── GroupCommands.kt + GroupCreateCommand.kt + │ GroupReadCommands.kt + GroupAddMemberCommand.kt + │ GroupMembershipCommands.kt + GroupMetadataCommands.kt + ├── MessageCommands.kt # marmot message send / list / react / delete + ├── MarmotResetCommand.kt # destructive wipe of MLS state + ├── AwaitCommands.kt # poll-until-condition helpers + └── StoreCommands.kt # store stat / sweep-expired / scrub / compact ``` **Dependencies:** `:quartz` + `:commons` + kotlinx-coroutines + OkHttp @@ -171,8 +178,9 @@ object NoteCommands { ``` Wire it into `Commands.kt`, add a top-level branch in `Main.kt`'s -`dispatch`, and extend `printUsage()`. Keep [README.md](./README.md)'s -command table and [ROADMAP.md](./ROADMAP.md)'s parity matrix in sync. +`dispatch`, and extend `printUsage()`. Keep the command tour in +[USAGE.md](./USAGE.md) and the parity matrix in +[ROADMAP.md](./ROADMAP.md) in sync. ### 4. Output-shape conventions @@ -205,8 +213,7 @@ Amy-specific layer still needs its own coverage: | Error / exit-code contract (bad args → 2, await timeout → 124, runtime → 1) | Table-driven tests invoking `main(argv)` with captured stdout/stderr. | | JSON output shape (each command's keys and types under `--json`) | Snapshot tests: run a command with `--json` against a throwaway `$HOME` (`HOME=$(mktemp -d) amy --account X …`), assert the JSON matches a golden file. The default text render has no shape contract and shouldn't be snapshotted. | | File layout on disk (`identity.json`, `events-store/…`, `marmot/groups/*.mls`, `marmot/keypackages.bundle`) | Structural assertions after a command sequence. | -| Round-trip between two accounts on a local relay | End-to-end shell harnesses under `cli/tests/`. Each harness spins up a local `nostr-rs-relay` and a fresh `$HOME=$STATE_DIR` so amy sees a virgin `~/.amy/`, then bootstraps multiple identities (`--account A`, `--account D`, etc.) sharing the same `~/.amy/shared/events-store/` and drives a scenario through them (+ `wn` for Marmot interop against whitenoise-rs). Today there are two suites: `cli/tests/marmot/` (MLS scenarios vs whitenoise-rs) and `cli/tests/dm/` (NIP-17 DM round-trips between two `amy` accounts). | -| Interop with other clients | Covered by `cli/tests/marmot/marmot-interop-headless.sh` (drives Amy against whitenoise-rs `wn`/`wnd`). Add new scenarios there or start a new sibling under `cli/tests/`. | +| Round-trip between two accounts on a local relay | End-to-end shell harnesses under `cli/tests/`: each spins up a local `nostr-rs-relay` and a fresh `$HOME=$STATE_DIR` so amy sees a virgin `~/.amy/`, then bootstraps multiple accounts (`--account A`, `--account D`, …) sharing one `~/.amy/shared/events-store/` and drives a scenario through them. Today: `cli/tests/dm/` (NIP-17 DMs between two amy accounts) and `cli/tests/marmot/` (MLS scenarios vs whitenoise-rs `wn`/`wnd`). | **What not to test here:** event signing, filter assembly, MLS correctness, NIP-44 encryption. Those belong in `quartz`/`commons`. @@ -241,7 +248,7 @@ a gap — add it to [ROADMAP.md](./ROADMAP.md). - Run `./gradlew spotlessApply` before every commit. - Keep three things in sync: `printUsage()` in `Main.kt`, the command - table in [README.md](./README.md), and the parity matrix in + tour in [USAGE.md](./USAGE.md), and the parity matrix in [ROADMAP.md](./ROADMAP.md). They drift fast. - Never add a Gradle dependency on `:amethyst` or `:desktopApp`. If you need something from there, move it to `commons/` first. diff --git a/cli/README.md b/cli/README.md index 371d39c25..893ff1e89 100644 --- a/cli/README.md +++ b/cli/README.md @@ -15,11 +15,11 @@ Amy exists for three audiences at once: Any flow that is tested in the Amethyst app should be reproducible through `amy` — that's the bar. -> Today Amy covers identity, relay config, account bootstrap, and -> Marmot / MLS group chat (MIP-00 / NIP-445). Everything else from the -> Android app is on the roadmap — see [ROADMAP.md](./ROADMAP.md). -> -> To extend Amy, see [DEVELOPMENT.md](./DEVELOPMENT.md). +This file documents the **public contract** and the on-disk layout. For +hands-on instructions and worked examples, see +[USAGE.md](./USAGE.md). To extend amy, see +[DEVELOPMENT.md](./DEVELOPMENT.md). For what's coming, see +[ROADMAP.md](./ROADMAP.md). --- @@ -30,9 +30,8 @@ What every caller — user, script, agent, CI — can rely on: - **Default stdout is human-readable text.** A YAML-ish render of the underlying result map. Friendly at a terminal; no shape promises. - **`--json` is the machine contract. One line. One object.** Stable - snake_case keys. Pipe it into `jq`, parse it from Python, hand it - to an agent. Pass `--json` anywhere before the subcommand: - `amy --json whoami`, `amy --account alice --json marmot group list`. + snake_case keys. Pipe it into `jq`, parse it from Python, hand it to + an agent. Pass `--json` anywhere before the subcommand. - **stderr is for humans.** Progress, warnings, per-relay ACK traces. Safe to discard. Errors land here too: `error: : ` by default, or JSON `{"error":"…","detail":"…"}` under `--json`. @@ -43,12 +42,11 @@ What every caller — user, script, agent, CI — can rely on: - `124` — `await` timed out - **No interactive prompts, ever.** Passwords, names, keys — all flags. - **`~/.amy/` is the whole world.** Per-account dirs hold identity, - cursors, MLS state, and aliases at `~/.amy//`; every - observed Nostr event lands in `~/.amy/shared/events-store/` (one - cache for every account on the machine). Delete to reset; copy to - move. Tests isolate by overriding `$HOME` for the amy subprocess - (`HOME=/tmp/run.123 amy --account alice …`) — same convention `git`, - `gpg`, and `npm` use. + cursors, MLS state, and aliases at `~/.amy//`; every observed + Nostr event lands in `~/.amy/shared/events-store/`. Delete to reset; + copy to move. Tests isolate by overriding `$HOME` for the amy + subprocess (`HOME=/tmp/run.123 amy --account alice …`) — same + convention `git`, `gpg`, and `npm` use. Only the `--json` shape and the exit codes are public API. The default text format is allowed to change between releases. The rationale lives @@ -58,25 +56,25 @@ in [DEVELOPMENT.md](./DEVELOPMENT.md). ## Local event store — the source of truth -Every Nostr event Amy observes is verified (NIP-01 id + signature +Every Nostr event amy observes is verified (NIP-01 id + signature check) and persisted to a file-backed store at `~/.amy/shared/events-store/` (one store per machine, shared across every account in `~/.amy/`). That includes: -- events received from any relay subscription (`amy feed`, `amy dm - list`, `amy keypackage publish`, group sync, …), -- events Amy generates and publishes itself, +- events received from any relay subscription (`amy notes feed`, + `amy dm list`, `amy marmot key-package publish`, group sync, …), +- events amy generates and publishes itself, - inner events unwrapped from NIP-59 gift wraps. Malformed events are dropped before reaching command code. Persistence -is best-effort — if the store fails (full disk, permissions), the -relay subscription still works, but the event is not cached. +is best-effort — if the store fails (full disk, permissions), the relay +subscription still works, but the event is not cached. -The store is the authoritative cache of everything Amy has seen: +The store is the authoritative cache of everything amy has seen: profile metadata, relay lists (NIP-65 and NIP-02), gift wraps, group -events, follow lists, etc. Commands that need any of these should read -from the store first and only fall back to a relay fetch on miss. -Three convenience helpers exist on `Context`: +events, follow lists, etc. Commands that need any of these read from +the store first and only fall back to a relay fetch on miss. Three +convenience helpers exist on `Context`: ```kotlin ctx.profileOf(pubKey) // latest kind:0 (NIP-01) @@ -87,177 +85,25 @@ ctx.keyPackageRelaysOf(pubKey) // latest kind:10051 (MIP-00 KP relays) ctx.cachedRelayListsOf(pubKey) // RecipientRelayFetcher.Lists from cache ``` -Commands that already read these cache-first: - -- `amy profile show` — `--refresh` to bypass. -- `amy feed --following` — local kind:3 served via slot lookup; falls - back to a relay drain on first run. -- `amy dm send` — recipient's kind:10050 / 10051 / 10002 served from - cache before falling back to `RecipientRelayFetcher`. -- `amy marmot key-package check` and `amy marmot await key-package` - — same recipient-relay lookup, cache-first. -- `amy marmot group add` — invitee relay lists served from cache. - The store implements every feature of the Quartz SQLite store — NIP-01 replaceable / addressable uniqueness, NIP-09 deletion tombstones, NIP-40 expiration, NIP-50 search, NIP-62 right-to-vanish, -NIP-91 multi-tag AND. See `cli/plans/2026-04-24-file-event-store-*.md` -for the design and `quartz/.../store/fs/FsEventStore.kt` for the -implementation. The on-disk layout is plain JSON files under shard -directories, intentionally inspectable with `ls`, `cat`, `jq`, -`grep`, `find`, `rsync`, and `git`. - -To manage the store directly: - -```sh -# raw inspection -find ~/.amy/shared/events-store/events -name '*.json' | head -jq . ~/.amy/shared/events-store/replaceable/0/.json - -# delete a specific event (tombstone NOT installed — see below) -rm ~/.amy/shared/events-store/events///.json -``` - -Deleting an event file is treated as a deliberate "I never saw this" -by Amy. The store tolerates external edits: dangling index entries are -skipped at query time and can be cleaned up with `compact()` / -`scrub()` from the API. - ---- - -## Install - -Until Amy ships as a signed native binary (see -[cli/plans/2026-04-21-cli-distribution.md](./plans/2026-04-21-cli-distribution.md)), -run it from source: - -```bash -# One-shot run — positional args go after `--args`, quoted as one string -./gradlew :cli:run --quiet --args="whoami" - -# Or build a runnable distribution and use the generated launch script -./gradlew :cli:installDist -./cli/build/install/amy/bin/amy whoami -``` - -The `installDist` tree under `cli/build/install/amy/` is self-contained -(JVM launcher + jars) and is what downstream packaging will wrap. - -**Requirements:** JDK 21. - ---- - -## Quick start - -```bash -# 1. Create the alice account (~/.amy/alice/) with a full Amethyst-style -# bootstrap: keypair, default NIP-65 / inbox / key-package relays, -# nine bootstrap events. -amy --account alice create - -# 2. Publish a fresh MLS KeyPackage so others can invite you. -amy --account alice marmot key-package publish - -# 3. Create a group, invite someone, send a message. -amy --account alice marmot group create --name "Test Group" -amy --account alice marmot group add npub1...bob -amy --account alice marmot message send "hello" - -# 4. On the receiving side, in ~/.amy/bob/. -amy --account bob marmot await group --name "Test Group" --timeout 60 -amy --account bob marmot message list -``` - -With one account you can drop the flag (auto-pick); with several, pin -one with `amy use alice` and the same commands work flagless. Compose -with `jq` by adding `--json` to switch stdout from human text to a -parseable single-line JSON object: - -```bash -GID=$(amy --account alice --json marmot group create --name "Test" | jq -r .group_id) -``` - -For an interop-test script template, see -[DEVELOPMENT.md § Testing](./DEVELOPMENT.md#testing). The runnable -harnesses live under [`cli/tests/`](./tests/README.md) — -`cli/tests/marmot/` for MLS group messaging vs whitenoise-rs, -`cli/tests/dm/` for NIP-17 DMs between two `amy` clients. - ---- - -## Command reference - -Run `amy --help` for the canonical list. As of today: - -| Verb | Summary | -|---|---| -| `init [--nsec NSEC]` | Create or import a bare identity. Does not publish anything. | -| `create [--name NAME]` | Provision a full account + publish the nine Amethyst bootstrap events. | -| `login KEY [--password X] [--private]` | Import `nsec` / `ncryptsec` / BIP-39 mnemonic / `npub` / `nprofile` / hex / NIP-05. Read-only when no secret material is supplied. | -| `whoami` | Print the active account's name + npub. | -| `use NAME` | Pin NAME as the active account (`~/.amy/current`). `use --clear` removes the pin; `use` prints the pin and the available accounts. | -| `profile show [PUBKEY] [--refresh] [--timeout SECS]` | Print kind:0 metadata. Default reads from the local store (cache-first); `--refresh` forces a relay drain. PUBKEY accepts `npub` / `nprofile` / hex / `name@domain.tld`; omit for self. | -| `profile edit --name X [--display-name X] …` | Build + publish a new kind:0 starting from the current cached metadata (or fetched if missing). | -| `relay add URL [--type T]` | `T = nip65 \| inbox \| key_package \| all`. | -| `relay list` | Dump configured relays by bucket. | -| `relay publish-lists` | Publish kind:10002 (NIP-65) + kind:10050 (DM inbox) + kind:10051 (KeyPackage relay list). | -| `marmot key-package publish` | Publish a fresh MLS KeyPackage (kind:30443) to the configured `key_package` bucket (fallback: NIP-65 outbox). | -| `marmot key-package check NPUB` | Look up NPUB's kind:10051 / kind:10002 on bootstrap relays, then fetch their KeyPackage from those relays. | -| `marmot group create [--name NAME]` | New empty group with you as sole admin. | -| `marmot group list` | All groups you're a member of. | -| `marmot group show GID` | Full group state (members, admins, epoch, metadata). | -| `marmot group members GID` | Members only. | -| `marmot group admins GID` | Admins only. | -| `marmot group add GID NPUB [NPUB…]` | Fetch KeyPackages for the npubs and commit an add. | -| `marmot group rename GID NAME` | Commit a metadata change. | -| `marmot group promote GID NPUB` | Make an existing member an admin. | -| `marmot group demote GID NPUB` | Revoke admin. | -| `marmot group remove GID NPUB` | Remove a member. | -| `marmot group leave GID` | Self-remove. | -| `marmot message send GID TEXT` | Publish a kind:9 inner event into the group. | -| `marmot message list GID [--limit N]` | Decrypted inner events, oldest first. | -| `marmot await key-package NPUB` | Block until a KeyPackage is seen on NPUB's advertised relays (kind:10051 / kind:10002). | -| `marmot await group --name NAME` | Block until we're added to a group with that name. | -| `marmot await member GID NPUB` | Block until NPUB is in GID's member set. | -| `marmot await admin GID NPUB` | Block until NPUB is an admin of GID. | -| `marmot await message GID --match TEXT` | Block until a message containing `TEXT` lands. | -| `marmot await rename GID --name NAME` | Block until GID's name matches. | -| `marmot await epoch GID --min N` | Block until GID's MLS epoch is ≥ N. | -| `dm send RECIPIENT TEXT [--allow-fallback]` | Send a NIP-17 gift-wrapped text DM (kind:14 inside kind:1059). Default delivers only to the recipient's kind:10050 (per NIP-17); pass `--allow-fallback` to fall back to kind:10002 read marker → bootstrap pool. | -| `dm send-file RECIPIENT --file PATH --server URL [--mime-type M] [--allow-fallback]` | Encrypt the local file with a fresh AES-GCM cipher, upload the ciphertext to the Blossom server, then publish a kind:15 NIP-17 file message referencing the returned URL. The auto-detected hash, size, dimensions, and blurhash from the upload are folded into the event. The response also surfaces the encryption key + nonce so the same blob can be re-shared without re-uploading. | -| `dm send-file RECIPIENT URL --key HEX --nonce HEX [--mime-type M] [--hash H] [--original-hash H] [--size N] [--dim WxH] [--blurhash S] [--allow-fallback]` | Reference-mode variant: the file is already uploaded; `--key`/`--nonce` carry the AES-GCM material that recipients use to decrypt the bytes at `URL`. Useful when the upload happened elsewhere or to re-publish a previously-uploaded blob. | -| `dm list [--peer NPUB] [--since TS] [--limit N] [--timeout SECS]` | Drain and decrypt gift wraps on our inbox relays. Returns kind:14 (text) and kind:15 (file) messages with a `type` discriminator. With neither `--peer` nor `--since` the gift-wrap cursor in `state.json` is advanced to the newest message seen. | -| `dm await --peer NPUB --match TEXT [--timeout SECS]` | Block until a DM from NPUB containing TEXT arrives (matches text content for kind:14, URL for kind:15). Timeout exits 124. | - -All `await` verbs accept `--timeout SECS` (default 30). Timeout exits 124 -so scripts can distinguish "condition never happened" from "the command -itself crashed". - -### Global flags - -- `--account ACCOUNT` — pick which account under `~/.amy//` to - operate on. Required only when more than one account exists; with a - single account amy auto-picks. Override with `amy use NAME` to - pin one as the default. ACCOUNT must match `[a-zA-Z0-9_-]{1,64}`. -- `--json` — switch stdout to the machine-readable contract. -- `--secret-backend auto|keychain|ncryptsec|plaintext` — where to - persist the private key. -- `--passphrase-file PATH` — passphrase for the `ncryptsec` backend. -- `--help` / `-h` — usage summary. - -### Account-management verbs - -- `amy use NAME` — pin NAME as the active account (`~/.amy/current`). -- `amy use --clear` — remove the pin. -- `amy use` — print the current pin and the list of available accounts. +NIP-91 multi-tag AND. See +[`cli/plans/2026-04-24-file-event-store-*.md`](./plans/) for the design +and `quartz/.../store/fs/FsEventStore.kt` for the implementation. The +on-disk layout is plain JSON files under shard directories, +intentionally inspectable with `ls`, `cat`, `jq`, `grep`, `find`, +`rsync`, and `git`. Deleting an event file is treated as a deliberate +"I never saw this" by amy; dangling indexes are skipped at query time +and can be cleaned up with `amy store scrub` / `amy store compact`. --- ## Relay routing -Amy follows the Marmot protocol's per-event routing rules so two users +amy follows the Marmot protocol's per-event routing rules so two users with completely disjoint relay configurations can still marmot each -other. No event ever ships blindly to "our configured relays" — Amy +other. No event ever ships blindly to "our configured relays" — amy looks up the right relay set per event per recipient. | Event | Publish to | Fetch from | @@ -270,12 +116,12 @@ looks up the right relay set per event per recipient. | kind:1059 gift wraps addressed to us | — | Our kind:10050 | | kind:445 Group Event (Commit / Proposal / chat) | Group's MIP-01 `relays` field | Same | -**Bootstrap pool**: when Amy needs to discover a user it's never talked +**Bootstrap pool**: when amy needs to discover a user it's never talked to, it queries `configured relays ∪ Amethyst's default NIP-65 set ∪ Amethyst's default DM-inbox set`. These defaults come from `commons.defaults.AmethystDefaults` and match what the Android/Desktop UI publishes to on first run, so any fresh Amethyst account is -reachable via the bootstrap pool even before Amy has seen any of their +reachable via the bootstrap pool even before amy has seen any of their events. --- @@ -312,27 +158,19 @@ get re-stored per account. The local relay configuration (kind:10002 / 10050 / 10051) is **not** a separate file — it lives in the shared `events-store/` as signed events owned by the account that wrote them. `amy relay add` builds + signs + -ingests a new relay-list event; `amy relay list` reads URLs straight out -of the latest event for each kind; `amy relay publish-lists` broadcasts -those events to upstream relays. There is no `relays.json`. +ingests a new relay-list event; `amy relay list` reads URLs straight +out of the latest event for each kind; `amy relay publish-lists` +broadcasts those events to upstream relays. There is no `relays.json`. --- -## Troubleshooting +## Where to go next -- **`no identity`** — run `init`, `create`, or `login` first, or pass a - different `--account`. -- **`no account at ~/.amy`** — there's no account dir yet; create one - with `amy --account init`. -- **`multiple accounts in ~/.amy (...)`** — disambiguate with `--account` - on the next call, or pin a default via `amy use NAME`. -- **`not_member`** — the group GID is unknown to this account. Run - `marmot group list` to confirm, or `marmot await group --name …` to - wait for an invite to arrive. -- **Hang on a network verb** — Amy connects to the relays advertised - in your local kind:10002 / 10050 / 10051 events; inspect with - `amy relay list`. Every network-bound operation has a timeout — use - `--timeout` for `await`, or wrap the whole command in `timeout(1)` - if you're scripting. -- **Nothing seems to publish** — inspect stderr; each publish prints - per-relay `OK` / `REJECT` via the `[cli] …` traces. +- **[USAGE.md](./USAGE.md)** — install, quick start, worked examples, + the command reference, troubleshooting. +- **[DEVELOPMENT.md](./DEVELOPMENT.md)** — design principles, + architecture, how to add a command without breaking the contract. +- **[ROADMAP.md](./ROADMAP.md)** — north-star goal and the parity + matrix tracking what's left to extract from the Android app. +- **[`plans/`](./plans/)** — design docs for cross-cutting work + (CLI distribution, file-backed event store, NIP-17 DMs, …). diff --git a/cli/ROADMAP.md b/cli/ROADMAP.md index 8237e15c0..907bd95d4 100644 --- a/cli/ROADMAP.md +++ b/cli/ROADMAP.md @@ -7,7 +7,8 @@ full command-line mirror of Amethyst. 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](./README.md) +- What amy is + the public contract: [README.md](./README.md) +- How to use it (examples + walkthroughs): [USAGE.md](./USAGE.md) - How to implement an item: [DEVELOPMENT.md](./DEVELOPMENT.md) - Ongoing design plans: [plans/](./plans/) - Shared work consumed here: [../commons/plans/](../commons/plans/) @@ -48,13 +49,13 @@ Status legend: ✅ shipped · 📦 logic lives in `commons/`, needs a command · | 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-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`](./plans/2026-04-23-nip17-dm.md). | | NIP-18 reposts / quotes | 🆕 | | -| NIP-25 reactions | 🆕 | | +| 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 | 🆕 | | @@ -65,7 +66,7 @@ Status legend: ✅ shipped · 📦 logic lives in `commons/`, needs a command · | 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/`. | +| 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) | 🆕 | | @@ -82,14 +83,16 @@ move anything, re-audit — you're probably duplicating logic. 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`. -2. **`amy note publish` / `amy note show` / `amy note react`** — - smallest end-to-end write+read loop outside Marmot. -3. **`amy feed home|profile|hashtag|thread`** reading through the - renderer. +2. **`amy notes post` / `amy notes show` / `amy notes react`** — + smallest end-to-end write+read loop outside Marmot. Post + feed + ✅ shipped; `notes show` and outer-event `react` still pending. +3. **`amy notes feed home|profile|hashtag|thread`** reading through the + renderer. `--following` and `--author NPUB` ✅; hashtag/thread + variants still pending. 4. **`amy follow add|remove|list`** (NIP-02) — proves extraction of list-building logic from `amethyst/model/`. -5. **`amy dm send|list`** (NIP-17) — reuses the gift-wrap path already - exercised by Marmot. +5. **`amy dm send|list`** (NIP-17) — ✅ shipped. Reuses the gift-wrap + path also exercised by Marmot. 6. **`amy list bookmarks|mute|pin …`** (NIP-51). 7. **`amy zap send|verify`** (NIP-57). 8. **Distribution** — Homebrew + Scoop + `.deb` in the same release