From f628783186c0892b7648188f7f74ccee874a7060 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 14 Jul 2026 22:43:44 +0000 Subject: [PATCH] refactor(cli): rename `amy wot` -> `amy fof` (follows-of-follows) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `wot` claimed the whole web-of-trust concept for what is actually a cheap single-hop metric — the count of your follows who also follow X. The real computed web of trust is `graperank`. Renamed the command (and WotCommand -> FofCommand) to `fof`, reframing its KDoc/usage away from "trust," and kept `wot` as a deprecation alias that warns and points at both `fof` and `graperank`. Also documents the command for the first time: `amy wot` had no --help block and no README row. Added both (help block + three README rows for get/list/sync), so the follows-of-follows score and its relationship to graperank are now discoverable. JSON output shape is unchanged. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_013WSzVX9RoUxyV3nT3dfc56 --- cli/README.md | 3 +++ .../com/vitorpamplona/amethyst/cli/Main.kt | 19 +++++++++++++-- .../commands/{WotCommand.kt => FofCommand.kt} | 24 ++++++++++++------- 3 files changed, 35 insertions(+), 11 deletions(-) rename cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/{WotCommand.kt => FofCommand.kt} (90%) diff --git a/cli/README.md b/cli/README.md index 925c456afc..468211a712 100644 --- a/cli/README.md +++ b/cli/README.md @@ -400,6 +400,9 @@ HTTP endpoint. Reuses quartz's `Nip86Client` and the shared `Nip86Retriever` | `amy graperank register [PROVIDER] [--service KIND:TAG] [--relay URL]` | Declare a NIP-85 provider in your kind:10040 so clients can discover it (default: self as the `30382:rank` provider). | | `amy graperank unregister PROVIDER [--service KIND:TAG] [--relay URL]` | The inverse of `register`: remove matching entries (public + private) from your kind:10040 and re-publish it. `--service`/`--relay` narrow the match; without them every entry for that provider key is dropped. | | `amy graperank providers [USER]` | List a user's declared NIP-85 trusted providers (public + your own private entries). | +| `amy fof get USER` | Follows-of-follows social proof: how many accounts you follow also follow USER. Single-hop, cheap — **not** the computed web of trust (that's `graperank`). Read from the local store; run `fof sync` first to freshen it. | +| `amy fof list [--threshold N] [--limit N]` | Rank accounts by that social-proof score — who's most-followed inside your network (discovery). Defaults: `--threshold 1`, `--limit 50`. | +| `amy fof sync [--timeout SECS]` | Pull your follows' latest kind:3 from the index relays so the next `get`/`list` is current. (`amy wot …` remains as a deprecation alias for all three.) | #### GrapeRank scores are persisted locally, then published (NIP-85) diff --git a/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/Main.kt b/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/Main.kt index a3bfeb8f28..e5f29ecbd0 100644 --- a/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/Main.kt +++ b/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/Main.kt @@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.cli.commands.EncryptCommand import com.vitorpamplona.amethyst.cli.commands.EventCommand import com.vitorpamplona.amethyst.cli.commands.FetchCommand import com.vitorpamplona.amethyst.cli.commands.FilterCommand +import com.vitorpamplona.amethyst.cli.commands.FofCommand import com.vitorpamplona.amethyst.cli.commands.FollowCommand import com.vitorpamplona.amethyst.cli.commands.GiftCommands import com.vitorpamplona.amethyst.cli.commands.GitCommands @@ -70,7 +71,6 @@ import com.vitorpamplona.amethyst.cli.commands.SubscribeCommand import com.vitorpamplona.amethyst.cli.commands.SyncCommand import com.vitorpamplona.amethyst.cli.commands.UseCommand import com.vitorpamplona.amethyst.cli.commands.VerifyCommand -import com.vitorpamplona.amethyst.cli.commands.WotCommand import com.vitorpamplona.amethyst.cli.commands.ZapCommand import com.vitorpamplona.amethyst.cli.commands.cashu.CashuCommands import com.vitorpamplona.amethyst.cli.commands.cashu.CashuMintCommands @@ -292,7 +292,14 @@ private suspend fun dispatch(argv: Array): Int { "podcast" -> PodcastCommands.dispatch(dataDir, tail) "podcast20" -> Podcast20Commands.dispatch(dataDir, tail) "bunker" -> BunkerCommand.run(dataDir, tail) - "wot" -> WotCommand.dispatch(dataDir, tail) + "fof" -> FofCommand.dispatch(dataDir, tail) + // `wot` overclaimed the whole web-of-trust concept for a cheap + // single-hop follower count; renamed to `fof` (follows-of-follows). + // Kept as a warning alias — the real WoT engine is `graperank`. + "wot" -> { + System.err.println("[amy] `wot` is deprecated — use `fof` (follows-of-follows). The computed web of trust is `graperank`.") + FofCommand.dispatch(dataDir, tail) + } else -> { System.err.println("unknown subcommand: $head") printUsage() @@ -645,6 +652,14 @@ private fun printUsage() { | -> service-key. (default: status) | graperank probe alias for `relay probe` (the relay census). | + |Follows-of-follows (social proof — the cheap counterpart to graperank): + | fof get USER USER's score: how many accounts you follow also + | follow them (single-hop social proof, not trust). + | fof list [--threshold N] [--limit N] accounts ranked by that score — who's most + | followed inside your network (default N: 1 / 50). + | fof sync [--timeout SECS] refresh your follows' kind:3 from the index relays + | so the next get/list is current (`wot` = alias). + | |Zaps (NIP-57): | zap user USER SATS build a profile zap-request, fetch a BOLT11 | [--comment X] [--anon|--private] invoice from the recipient's LN service diff --git a/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/WotCommand.kt b/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/FofCommand.kt similarity index 90% rename from cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/WotCommand.kt rename to cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/FofCommand.kt index c81ce32c4b..a10d700fbc 100644 --- a/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/WotCommand.kt +++ b/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/FofCommand.kt @@ -38,15 +38,21 @@ import kotlinx.coroutines.cancel import java.util.Collections /** - * `amy wot ` — Web-of-Trust score queries. + * `amy fof ` — follows-of-follows social-proof scores. * * The score for a pubkey X is the count of accounts in the active user's - * kind-3 follow set who also follow X. `get` and `list` are read-only — - * they hydrate the score map from whatever kind-3 events already live in - * the local event store. `sync` pulls fresh kind-3 events from the + * kind:3 follow set who also follow X — cheap single-hop social proof, NOT + * the computed web of trust (that's `amy graperank`). `get` and `list` are + * read-only — they hydrate the score map from whatever kind:3 events already + * live in the local event store. `sync` pulls fresh kind:3 events from the * configured relay pool so the next `get` / `list` is up to date. + * + * - `fof get USER` — X's score (how many of your follows follow X). + * - `fof list` — accounts ranked by score (discovery: who's most + * followed inside your network). + * - `fof sync` — refresh your follows' kind:3 from relays. */ -object WotCommand { +object FofCommand { suspend fun dispatch( dataDir: DataDir, rest: Array, @@ -61,13 +67,13 @@ object WotCommand { } } - private fun usage(): Int = Output.error("bad_args", "wot ") + private fun usage(): Int = Output.error("bad_args", "fof ") private suspend fun get( dataDir: DataDir, rest: Array, ): Int { - if (rest.isEmpty()) return Output.error("bad_args", "wot get ") + if (rest.isEmpty()) return Output.error("bad_args", "fof get ") val userArg = rest[0] Context.open(dataDir).use { ctx -> ctx.prepare() @@ -148,8 +154,8 @@ object WotCommand { // Amy's store lookup is suspending; can't do // it here. The dispatcher then falls through // to Phase 1 discovery for every author, which - // matches the old `amy wot sync` behaviour of - // always re-asking. A future optimisation + // matches the `fof sync` behaviour of always + // re-asking. A future optimisation // could pre-populate a `Map` before dispatch. null