refactor(cli): rename default data-dir to ./amy and nest MLS state under marmot/

Two on-disk layout changes that only affect freshly-created data-dirs (no
migration shim since `amethyst-cli-data` was never tagged or released):

- Default `--data-dir` is now `./amy` instead of `./amethyst-cli-data` —
  shorter, matches the binary name, and still overridable via the flag or
  `$AMETHYST_CLI_DATA`.
- MLS-only files (`groups/` and `keypackages.bundle`) move under a new
  `marmot/` subdirectory so the top-level root stays tidy as more
  non-Marmot state lands (notes, profile caches, etc). Existing
  top-level files (`identity.json`, `relays.json`, `state.json`) are
  unchanged.

README tree diagram and DEVELOPMENT.md test table updated to match.
This commit is contained in:
Claude
2026-04-24 19:15:58 +00:00
parent 825e2fd911
commit 8cbe8c67fe
3 changed files with 16 additions and 14 deletions
+1 -1
View File
@@ -196,7 +196,7 @@ Amy-specific layer still needs its own coverage:
| Argument parsing (`Args`, flag forms, `--data-dir=…` vs `--data-dir …`) | Plain JVM unit tests in `cli/src/test/kotlin/`. | | Argument parsing (`Args`, flag forms, `--data-dir=…` vs `--data-dir …`) | Plain JVM unit tests in `cli/src/test/kotlin/`. |
| Error / exit-code contract (bad args → 2, await timeout → 124, runtime → 1) | Table-driven tests invoking `main(argv)` with captured stdout/stderr. | | 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) | Snapshot tests: run a command against a throwaway data-dir, assert the JSON matches a golden file. | | JSON output shape (each command's keys and types) | Snapshot tests: run a command against a throwaway data-dir, assert the JSON matches a golden file. |
| File layout on disk (`identity.json`, `relays.json`, `groups/*.mls`, `keypackages.bundle`) | Structural assertions after a command sequence. | | File layout on disk (`identity.json`, `relays.json`, `marmot/groups/*.mls`, `marmot/keypackages.bundle`) | Structural assertions after a command sequence. |
| Round-trip between two data-dirs on a local relay | End-to-end shell harnesses under `cli/tests/`. Each harness spins up a local `nostr-rs-relay`, bootstraps two or more fresh identities in their own `--data-dir`s, and drives a scenario via `amy` (+ `wn` for Marmot interop against whitenoise-rs). Today there are two suites: `cli/tests/marmot/` (13 MLS scenarios vs whitenoise-rs) and `cli/tests/dm/` (NIP-17 DM round-trips between two `amy` clients). | | Round-trip between two data-dirs on a local relay | End-to-end shell harnesses under `cli/tests/`. Each harness spins up a local `nostr-rs-relay`, bootstraps two or more fresh identities in their own `--data-dir`s, and drives a scenario via `amy` (+ `wn` for Marmot interop against whitenoise-rs). Today there are two suites: `cli/tests/marmot/` (13 MLS scenarios vs whitenoise-rs) and `cli/tests/dm/` (NIP-17 DM round-trips between two `amy` clients). |
| 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/`. | | 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/`. |
+10 -9
View File
@@ -40,7 +40,7 @@ What every caller — user, script, agent, CI — can rely on:
- **Data-dir is the whole world.** All state (identity, relays, MLS - **Data-dir is the whole world.** All state (identity, relays, MLS
epochs, message archives, run cursors) lives under `--data-dir PATH`. epochs, message archives, run cursors) lives under `--data-dir PATH`.
Delete to reset; copy to move; `AMETHYST_CLI_DATA` env var overrides Delete to reset; copy to move; `AMETHYST_CLI_DATA` env var overrides
the default `./amethyst-cli-data`. the default `./amy`.
The rationale behind each of these lives in The rationale behind each of these lives in
[DEVELOPMENT.md](./DEVELOPMENT.md). Breaking any of them is a breaking [DEVELOPMENT.md](./DEVELOPMENT.md). Breaking any of them is a breaking
@@ -152,7 +152,7 @@ itself crashed".
### Global flags ### Global flags
- `--data-dir PATH` — defaults to `./amethyst-cli-data` or - `--data-dir PATH` — defaults to `./amy` or
`$AMETHYST_CLI_DATA`. Always an absolute path after resolution. `$AMETHYST_CLI_DATA`. Always an absolute path after resolution.
- `--help` / `-h` — usage summary. - `--help` / `-h` — usage summary.
@@ -189,13 +189,14 @@ events.
``` ```
<data-dir>/ <data-dir>/
├── identity.json # nsec/npub/hex — the account ├── identity.json # nsec/npub/hex — the account
├── relays.json # nip65 / inbox / key_package buckets ├── relays.json # nip65 / inbox / key_package buckets
├── state.json # sync cursors (giftWrapSince, groupSince) ├── state.json # sync cursors (giftWrapSince, groupSince)
── keypackages.bundle # MLS KeyPackage bundles (NostrSignerInternal) ── marmot/
└── groups/ ├── keypackages.bundle # MLS KeyPackage bundles (NostrSignerInternal)
── <gid>.mls # MLS group state per group ── groups/
── <gid>.log # decrypted inner events (one JSON per line) ── <gid>.mls # MLS group state per group
└── <gid>.log # decrypted inner events (one JSON per line)
``` ```
All files are plain JSON or framed binary — human-inspectable, easy to All files are plain JSON or framed binary — human-inspectable, easy to
@@ -132,7 +132,7 @@ data class RunState(
/** /**
* Root of the on-disk layout. Any absolute path chosen by `--data-dir` (or * Root of the on-disk layout. Any absolute path chosen by `--data-dir` (or
* `$AMETHYST_CLI_DATA`) defaults to `./amethyst-cli-data`. * `$AMETHYST_CLI_DATA`) defaults to `./amy`.
*/ */
class DataDir( class DataDir(
val root: File, val root: File,
@@ -140,8 +140,9 @@ class DataDir(
val identityFile = File(root, "identity.json") val identityFile = File(root, "identity.json")
val relaysFile = File(root, "relays.json") val relaysFile = File(root, "relays.json")
val stateFile = File(root, "state.json") val stateFile = File(root, "state.json")
val groupsDir = File(root, "groups") val marmotDir = File(root, "marmot")
val keyPackageBundleFile = File(root, "keypackages.bundle") val groupsDir = File(marmotDir, "groups")
val keyPackageBundleFile = File(marmotDir, "keypackages.bundle")
init { init {
root.mkdirs() root.mkdirs()
@@ -169,7 +170,7 @@ class DataDir(
companion object { companion object {
fun resolve(flag: String?): DataDir { fun resolve(flag: String?): DataDir {
val envPath = System.getenv("AMETHYST_CLI_DATA") val envPath = System.getenv("AMETHYST_CLI_DATA")
val path = flag ?: envPath ?: "./amethyst-cli-data" val path = flag ?: envPath ?: "./amy"
return DataDir(File(path).absoluteFile) return DataDir(File(path).absoluteFile)
} }
} }