From c7c8a3a4126827d021d1e7cc1de9dbf548c86707 Mon Sep 17 00:00:00 2001 From: naliyi <154817482+naliyi@users.noreply.github.com> Date: Thu, 17 Sep 2026 08:52:25 +0000 Subject: [PATCH 1/9] docs: audit noisy CLI narration text with before/after table Catalogs every stderr progress line across cli/ and client/ that's transient noise or redundant with a nearby line, per AGENTS.md's stdout=result/stderr=narration convention. No behavior changed yet. --- CLI_NOISE_AUDIT.md | 90 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 CLI_NOISE_AUDIT.md diff --git a/CLI_NOISE_AUDIT.md b/CLI_NOISE_AUDIT.md new file mode 100644 index 0000000..bfa1f61 --- /dev/null +++ b/CLI_NOISE_AUDIT.md @@ -0,0 +1,90 @@ +# CLI narration noise audit + +Every progress line printed to stderr while a command runs (per AGENTS.md's +stdout=result / stderr=narration convention), checked against what it +actually needs to say. 22 lines across 8 files. Lines not listed here are +kept as-is (final results, errors, genuine tips). + +## cli/reindex/command.go — worst offender (8 lines) + +Every reindex phase announces its own start, and the search path reports +its final count twice. + +| Line | Before | After | +|---|---|---| +| 40 | `"connecting to search backend..."` | removed | +| 50 | `"starting search reindexing..."` | removed | +| 102 | `"progress..."` (field `indexed`) | `"indexed"` | +| 114 | `"final batch indexed"` (field `indexed`) | removed — duplicates line 124's `total` | +| 122 | `"waiting for verification worker to finish remaining jobs..."` | `"waiting on verification worker"` | +| 128 | `"fetching events..."` | removed | +| 149 | `"starting zap reindexing..."` | removed | +| 157 | `"progress..."` (field `zaps_indexed`) | `"indexed"` | + +## cli/relay/service.go — server lifecycle (5 lines) + +| Line | Before | After | +|---|---|---| +| 46 | `"server config check"` | removed — fold `pubkey`/`port` fields into line 247 | +| 247 | `"listening..."` (no fields) | `"listening"` with `pubkey`/`port` fields | +| 266 | `"stopping server gracefully"` | removed — outcome already logged by line 280 or 285 | +| 288 | `"stopping verification workers..."` | removed — paired with "verification workers stopped" | +| 292 | `"stopping events store..."` | removed — paired with "events store stopped" | + +## cli/relay/admin.go + command.go — duplicated across both (2 lines) + +| Line | Before | After | +|---|---|---| +| admin.go:172 | `"using config file"` (field `config`) | `"config"` | +| command.go:286 | `"using config file"` (field `config`) | `"config"` | + +## client/client.go — per-target query noise (4 lines) + +| Line | Before | After | +|---|---|---| +| 149 | `"querying %s"` (local path, in mergeEventsFromTargets) | removed | +| 156 | `"querying %s"` (remote host, in mergeEventsFromTargets) | removed | +| 225 | `"querying %s"` (local path, in Find) | removed | +| 232 | `"querying %s"` (remote host, in Find) | removed | + +## client/ping.go — start-of-run announcement (1 line) + +| Line | Before | After | +|---|---|---| +| 123 | `"Checking connectivity for %d %s"` | removed — the real result is the "%d of %d reachable" summary at line 167 | + +Separate plumbing note, not a wording fix: `ping`'s per-relay +`"connectivity OK"`/`"connectivity check failed"` lines and the final +`"%d of %d reachable"` summary are `ping`'s actual *result* in text mode, +but they go out via `log.Info`/`log.Error` (stderr) instead of stdout — +`cli/ncli/ping.go` itself prints nothing to stdout. Worth revisiting +separately from this noise pass. + +## cli/ncli/id.go — vault save (3 lines) + +| Line | Before | After | +|---|---|---| +| 204 | `"unlocking vault..."` | removed — redundant with the "Vault password:" prompt right after | +| 206 | `"creating vault identity..."` | removed — redundant with the "Set a vault password:" prompt right after | +| 223 | `"saving identity..."` | removed — superseded by "identity saved to vault (label: %s)" right after | + +## cli/ncli/miner.go — progress tick (1 line) + +| Line | Before | After | +|---|---|---| +| 177 | `"mining... %s hashes tried, %s elapsed, %s across %d worker(s)"` | `"%s hashes, %s, %s, %d workers"` | + +## cli/delegate/command.go — redundant header (1 line) + +| Line | Before | After | +|---|---|---| +| 141 | `"Delegation token generated."` | removed — restates the fields printed right below it | + +## Clean (audited, zero noise) + +`cli/blossom` (all), `cli/bunker/command.go`, `cli/relay/context.go` + +`context_run.go`, `cli/common/errors.go`, `client/publish.go`, +`client/recovery.go`, `client/stream.go`, `client/miner.go`, +`cli/ncli/apply.go`, `dump.go`, `find.go`, `ping.go`, `filters.go`, +`query.go`. `cli/bunker/board.go` and `client/neg_sync.go` are live-TUI +chrome, out of scope here. From 0c0f3653efa1ef80e98360c48d69617ac7e25951 Mon Sep 17 00:00:00 2001 From: naliyi <154817482+naliyi@users.noreply.github.com> Date: Thu, 17 Sep 2026 09:56:15 +0000 Subject: [PATCH 2/9] docs: reconcile CLI noise audit line-by-line against source Full re-read (not grep) of every file the audit touches or claims clean. All 22 original NOISE/REDUNDANT rows match source verbatim, zero drift. Adds ~38 files that were never actually checked to the Clean list, and a new stdout/stderr plumbing section for 3 commands whose real result goes out on stderr via log.Info/log.Error instead of stdout. --- CLI_NOISE_AUDIT.md | 52 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/CLI_NOISE_AUDIT.md b/CLI_NOISE_AUDIT.md index bfa1f61..20c22f0 100644 --- a/CLI_NOISE_AUDIT.md +++ b/CLI_NOISE_AUDIT.md @@ -5,6 +5,13 @@ stdout=result / stderr=narration convention), checked against what it actually needs to say. 22 lines across 8 files. Lines not listed here are kept as-is (final results, errors, genuine tips). +Reconciled 2026-09-17 against current source: every file below was read in +full (not re-grepped) and every row cross-checked line-by-line. Result: all +22 rows below still match source verbatim, zero drift, zero reclassified. +The reconciliation pass did surface two things the first pass missed — a +recurring stdout/stderr plumbing bug (see its own section below) and ~38 +files that had never actually been checked (folded into "Clean" below). + ## cli/reindex/command.go — worst offender (8 lines) Every reindex phase announces its own start, and the search path reports @@ -53,19 +60,12 @@ its final count twice. |---|---|---| | 123 | `"Checking connectivity for %d %s"` | removed — the real result is the "%d of %d reachable" summary at line 167 | -Separate plumbing note, not a wording fix: `ping`'s per-relay -`"connectivity OK"`/`"connectivity check failed"` lines and the final -`"%d of %d reachable"` summary are `ping`'s actual *result* in text mode, -but they go out via `log.Info`/`log.Error` (stderr) instead of stdout — -`cli/ncli/ping.go` itself prints nothing to stdout. Worth revisiting -separately from this noise pass. - ## cli/ncli/id.go — vault save (3 lines) | Line | Before | After | |---|---|---| -| 204 | `"unlocking vault..."` | removed — redundant with the "Vault password:" prompt right after | -| 206 | `"creating vault identity..."` | removed — redundant with the "Set a vault password:" prompt right after | +| 204 | `"unlocking vault..."` | removed — redundant with the "Vault password:" prompt right after (confirmed live in `cli/keyresolve/resolve.go`) | +| 206 | `"creating vault identity..."` | removed — redundant with the "Set a vault password:" prompt right after (same) | | 223 | `"saving identity..."` | removed — superseded by "identity saved to vault (label: %s)" right after | ## cli/ncli/miner.go — progress tick (1 line) @@ -80,11 +80,31 @@ separately from this noise pass. |---|---|---| | 141 | `"Delegation token generated."` | removed — restates the fields printed right below it | -## Clean (audited, zero noise) +## Stdout/stderr plumbing gaps (separate issue — not wording) + +Three commands report their actual text-mode *result* — not narration — +through `log.Info`/`log.Error`, which AGENTS.md routes to stderr. Nothing +wrong with the wording; the fix is which stream it goes to. + +| File | Lines | Text | Command affected | +|---|---|---|---| +| `client/ping.go` | ~167, ~214–222 | `"%d of %d %s reachable"`, per-relay `"connectivity OK"`/`"connectivity check failed"` | `ping`'s entire text-mode result lives on stderr | +| `cli/ncli/prefs.go` | 60, 62, 93, 95, 146 | `"added"`, `"already configured"`, `"removed"`, `"not configured"`, `"cleared"` | `prefs relays add`/`remove`/`clear` | +| `cli/relay/context_run.go` | 146, 189 | `"relay context created"`, `"new identity saved to vault"` | `relay --context `'s auto-create path | + +## Clean (audited in full, zero noise) + +Every file below was read end to end, not sampled by grep. + +- **cli/blossom** (all 9): `command.go`, `download.go`, `list.go`, `mirror.go`, `report.go`, `rm.go`, `servers.go`, `shared.go`, `upload.go` — no progress narration anywhere, including upload/download/mirror's transfer loops. +- **cli/bunker** (all 16 non-TUI files): `client.go`, `clipboard.go`, `command.go`, `daemon.go`, `eventlog.go`, `grantspec.go`, `handler.go`, `identity.go`, `ipc_client.go`, `ipc_server.go`, `policy.go`, `queue.go`, `spawn.go`, `spawn_unix.go`, `spawn_windows.go`, `uri.go`. `daemon.go`'s custom `d.log()` method never reaches CLI stdout/stderr — it only feeds an in-memory TUI log panel or a rotating `daemon.log` file on disk. +- **cli/relay**: `context.go`, `context_run.go` (noise-wise; see plumbing gap above), `service_membership.go`. +- **cli/common** (all 10): `errors.go`, `appdir.go`, `args.go`, `auth.go`, `config.go`, `logging.go`, `logging_unix.go`, `logging_windows.go`, `prompt.go`, `version.go`. +- **cli/keyresolve/resolve.go**, **cli/reindex/state.go**. +- **cli/ncli** (11): `apply.go`, `decode.go`, `dump.go`, `filters.go`, `find.go`, `id_sign.go`, `ping.go`, `publish.go`, `query.go`, `root.go`, `version.go`. (`prefs.go` has the plumbing gap above instead.) +- **client/** (12): `decode.go`, `event_export.go`, `identity.go`, `inspect.go`, `inspect_store.go`, `miner.go`, `prefs.go`, `publish.go`, `recovery.go`, `spec.go`, `stream.go`, `vault.go`. -`cli/blossom` (all), `cli/bunker/command.go`, `cli/relay/context.go` + -`context_run.go`, `cli/common/errors.go`, `client/publish.go`, -`client/recovery.go`, `client/stream.go`, `client/miner.go`, -`cli/ncli/apply.go`, `dump.go`, `find.go`, `ping.go`, `filters.go`, -`query.go`. `cli/bunker/board.go` and `client/neg_sync.go` are live-TUI -chrome, out of scope here. +Out of scope, not "clean" — confirmed to have no headless code path at all, +so there's no console narration to audit: `cli/bunker/board.go` (live TUI +board) and `client/neg_sync.go` (`Client.init()` rejects a `SyncSpec` +without a TUI attached). From 901bd3b6a13e2c7997123f14574cfcc1aeec4d99 Mon Sep 17 00:00:00 2001 From: naliyi <154817482+naliyi@users.noreply.github.com> Date: Thu, 17 Sep 2026 11:22:48 +0000 Subject: [PATCH 3/9] docs: add CLI help-text audit with onboarding example tables Catalogs every human-facing command's Use/Short/Long and proposes an example-use-case table for each -- zero cobra Example fields exist anywhere in the codebase today. Lands in docs/private/ (gitignored, scratch) as a checkpoint: the root-command duplication fix and the actual Example: field additions are separate, later steps, not started here. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index a1b8735..9ff52eb 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,6 @@ vendor/ # Local scratch drafts, not meant to be tracked /.drafts/ + +# Local-only planning/audit docs, not meant to be tracked +/docs/private/ From f338a995726874243c65c3453072d044b9a3772e Mon Sep 17 00:00:00 2001 From: naliyi <154817482+naliyi@users.noreply.github.com> Date: Thu, 17 Sep 2026 13:41:56 +0000 Subject: [PATCH 4/9] feat: add onboarding Example: text to every command's --help Unifies the root ncli command (was defined identically twice, in cli/ncli/root.go and cmd/ncli/main.go) into a single definition, then adds a cobra Example block to all 71 human-facing commands across cli/ncli, cli/relay, cli/bunker, cli/delegate, and cli/blossom -- previously zero existed anywhere in the codebase. Content transcribed from docs/private/CLI_HELP_TEXT_AUDIT.md's per-command use-case tables. No command's Use/Short/Long/behavior changes; go build/vet/test all pass, and --help output was spot-checked across every code path (leaf, leaf-with-flags, nested leaf, group, root). --- cli/blossom/command.go | 3 +- cli/blossom/download.go | 2 ++ cli/blossom/list.go | 2 ++ cli/blossom/mirror.go | 1 + cli/blossom/report.go | 1 + cli/blossom/rm.go | 1 + cli/blossom/servers.go | 29 +++++++++------- cli/blossom/upload.go | 3 ++ cli/bunker/command.go | 61 ++++++++++++++++++++------------- cli/delegate/command.go | 2 ++ cli/ncli/apply.go | 2 ++ cli/ncli/decode.go | 2 ++ cli/ncli/dump.go | 2 ++ cli/ncli/find.go | 3 ++ cli/ncli/id.go | 9 +++-- cli/ncli/id_sign.go | 1 + cli/ncli/miner.go | 13 ++++--- cli/ncli/ping.go | 2 ++ cli/ncli/prefs.go | 47 +++++++++++++++----------- cli/ncli/publish.go | 2 ++ cli/ncli/root.go | 12 ++++--- cli/ncli/version.go | 1 + cli/relay/admin.go | 75 +++++++++++++++++++++++++---------------- cli/relay/command.go | 2 ++ cli/relay/context.go | 41 ++++++++++++---------- cmd/ncli/main.go | 28 ++++----------- cmd/ncli/main_test.go | 9 ++--- 27 files changed, 215 insertions(+), 141 deletions(-) diff --git a/cli/blossom/command.go b/cli/blossom/command.go index 8a59672..ed0385a 100644 --- a/cli/blossom/command.go +++ b/cli/blossom/command.go @@ -23,7 +23,8 @@ per (item, server) pair, and exiting non-zero if any pair failed. "download" tries the configured servers in order, stopping at the first that answers; "list" queries one server by default, or every server with --all.`, - RunE: common.RequireSubcommand, + Example: ` ncli blossom upload ./photo.jpg --identity mylabel`, + RunE: common.RequireSubcommand, } cmd.PersistentFlags().String("identity", "", "Identity to sign with -- vault label, nsec, npub, hex, nprofile, or nip-05") diff --git a/cli/blossom/download.go b/cli/blossom/download.go index 5dbe30e..eec2e0b 100644 --- a/cli/blossom/download.go +++ b/cli/blossom/download.go @@ -34,6 +34,8 @@ server URL ending in a hash -- tries the configured servers in order Writes to --output, or "." in the current directory if omitted, or streams to stdout with "-o -" (suppressing the summary line).`, + Example: ` ncli blossom download + ncli blossom download -o -`, Args: common.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { ctx, cancel := signal.NotifyContext(cmd.Context(), syscall.SIGINT, syscall.SIGTERM) diff --git a/cli/blossom/list.go b/cli/blossom/list.go index 47e95db..b8d22be 100644 --- a/cli/blossom/list.go +++ b/cli/blossom/list.go @@ -32,6 +32,8 @@ deduped by hash. identifier may be a vault label, nsec, npub, hex pubkey, nprofile, or nip-05 address, resolved to a hex pubkey; defaults to --identity's resolved pubkey when omitted.`, + Example: ` ncli blossom list --identity mylabel + ncli blossom list --identity mylabel --all`, Args: common.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { ctx, cancel := signal.NotifyContext(cmd.Context(), syscall.SIGINT, syscall.SIGTERM) diff --git a/cli/blossom/mirror.go b/cli/blossom/mirror.go index 50e53e3..4038a97 100644 --- a/cli/blossom/mirror.go +++ b/cli/blossom/mirror.go @@ -18,6 +18,7 @@ func newMirrorCommand() *cobra.Command { (--server, or the configured default list) -- each server fetches source-url itself; no bytes pass through ncli. Reports a result per server.`, + Example: ` ncli blossom mirror https://example.com/file.jpg --identity mylabel`, Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { return common.UsageError(cmd, fmt.Errorf("exactly one source URL is required")) diff --git a/cli/blossom/report.go b/cli/blossom/report.go index c681239..1f8049e 100644 --- a/cli/blossom/report.go +++ b/cli/blossom/report.go @@ -17,6 +17,7 @@ func newReportCommand() *cobra.Command { Long: `Sign and submit a kind:1984 report event to a server's PUT /report -- authenticated by its own signature, not a BUD-11 token. Targets one server: --server, or the first configured default.`, + Example: ` ncli blossom report --identity mylabel`, Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { return common.UsageError(cmd, fmt.Errorf("exactly one hash is required")) diff --git a/cli/blossom/rm.go b/cli/blossom/rm.go index 8cb4a3f..4777b06 100644 --- a/cli/blossom/rm.go +++ b/cli/blossom/rm.go @@ -23,6 +23,7 @@ func newRmCommand() *cobra.Command { Long: `Sign a hash-scoped BUD-11 authorization and DELETE the blob from every target server (--server, or the configured default list), reporting a result per server. Requires --yes in a non-interactive session.`, + Example: ` ncli blossom rm --identity mylabel --yes`, Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { return common.UsageError(cmd, fmt.Errorf("exactly one hash is required")) diff --git a/cli/blossom/servers.go b/cli/blossom/servers.go index b7e9bba..14daf44 100644 --- a/cli/blossom/servers.go +++ b/cli/blossom/servers.go @@ -18,10 +18,11 @@ import ( func newServersCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "servers", - Short: "Manage the default Blossom server list", - Long: `Manage the server list "ncli blossom" commands fall back to when not given explicit --server flags.`, - RunE: common.RequireSubcommand, + Use: "servers", + Short: "Manage the default Blossom server list", + Long: `Manage the server list "ncli blossom" commands fall back to when not given explicit --server flags.`, + Example: ` ncli blossom servers list`, + RunE: common.RequireSubcommand, } cmd.AddCommand(newServersAddCommand()) @@ -81,8 +82,9 @@ func newServersAddCommand() *cobra.Command { var publish bool cmd := &cobra.Command{ - Use: "add ", - Short: "Add a server to the default list", + Use: "add ", + Short: "Add a server to the default list", + Example: ` ncli blossom servers add https://blossom.example.com`, Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { return common.UsageError(cmd, fmt.Errorf("exactly one server url is required")) @@ -150,8 +152,9 @@ func newServersRemoveCommand() *cobra.Command { var publish bool cmd := &cobra.Command{ - Use: "remove ", - Short: "Remove a server from the default list", + Use: "remove ", + Short: "Remove a server from the default list", + Example: ` ncli blossom servers remove https://blossom.example.com`, Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { return common.UsageError(cmd, fmt.Errorf("exactly one server url is required")) @@ -213,9 +216,10 @@ func newServersRemoveCommand() *cobra.Command { func newServersListCommand() *cobra.Command { return &cobra.Command{ - Use: "list", - Short: "List the default servers", - Args: common.NoArgs, + Use: "list", + Short: "List the default servers", + Example: ` ncli blossom servers list`, + Args: common.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { jsonMode, _ := cmd.Flags().GetBool("json") @@ -266,7 +270,8 @@ declares. Unlike "servers add/remove/list", which manage your own default list, this looks up someone else's published servers.`, - Args: common.ExactArgs(1), + Example: ` ncli blossom servers discover npub1...`, + Args: common.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { ctx, cancel := signal.NotifyContext(cmd.Context(), syscall.SIGINT, syscall.SIGTERM) defer cancel() diff --git a/cli/blossom/upload.go b/cli/blossom/upload.go index b591837..b60108e 100644 --- a/cli/blossom/upload.go +++ b/cli/blossom/upload.go @@ -28,6 +28,9 @@ func newUploadCommand() *cobra.Command { Pass --optimize to request server-side transcoding/optimization (BUD-05's PUT /media) instead of a byte-for-byte store.`, + Example: ` ncli blossom upload ./photo.jpg --identity mylabel + ncli blossom upload ./photo.jpg --identity mylabel --optimize + ncli blossom upload ./photo.jpg --identity mylabel --server https://blossom.example.com`, Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return common.UsageError(cmd, fmt.Errorf("at least one file is required")) diff --git a/cli/bunker/command.go b/cli/bunker/command.go index 8fcef7f..2252729 100644 --- a/cli/bunker/command.go +++ b/cli/bunker/command.go @@ -43,6 +43,8 @@ On Linux/macOS this starts (or reattaches to) a background daemon that keeps running after the TUI is closed with "b" or "q" -- reattach any time with "ncli bunker attach". On Windows the TUI runs directly with no background support.`, + Example: ` ncli bunker + ncli bunker --relay wss://relay.example.com`, RunE: func(cmd *cobra.Command, args []string) error { if err := requireInteractive(cmd); err != nil { return err @@ -94,6 +96,7 @@ func newAttachCommand() *cobra.Command { Long: `Reconnect the interactive TUI to a bunker daemon already started with "ncli bunker" and left running in the background. Never starts one itself -- fails if none is running (use "ncli bunker" for that).`, + Example: ` ncli bunker attach`, RunE: func(cmd *cobra.Command, args []string) error { if err := requireInteractive(cmd); err != nil { return err @@ -115,8 +118,9 @@ itself -- fails if none is running (use "ncli bunker" for that).`, func newStatusCommand() *cobra.Command { return &cobra.Command{ - Use: "status", - Short: "Show whether a bunker daemon is running", + Use: "status", + Short: "Show whether a bunker daemon is running", + Example: ` ncli bunker status`, RunE: func(cmd *cobra.Command, args []string) error { jsonMode, _ := cmd.Flags().GetBool("json") @@ -198,8 +202,9 @@ func printIdentityAndRelays(st StatusInfo) { func newStopCommand() *cobra.Command { return &cobra.Command{ - Use: "stop", - Short: "Stop the running bunker daemon", + Use: "stop", + Short: "Stop the running bunker daemon", + Example: ` ncli bunker stop`, RunE: func(cmd *cobra.Command, args []string) error { jsonMode, _ := cmd.Flags().GetBool("json") @@ -246,14 +251,16 @@ func newStopCommand() *cobra.Command { func newSessionsCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "sessions", - Short: "Manage remembered per-app permissions", - RunE: common.RequireSubcommand, + Use: "sessions", + Short: "Manage remembered per-app permissions", + Example: ` ncli bunker sessions list`, + RunE: common.RequireSubcommand, } cmd.AddCommand(&cobra.Command{ - Use: "list", - Short: "List every app with a remembered permission", + Use: "list", + Short: "List every app with a remembered permission", + Example: ` ncli bunker sessions list`, RunE: func(cmd *cobra.Command, args []string) error { jsonMode, _ := cmd.Flags().GetBool("json") @@ -283,9 +290,10 @@ func newSessionsCommand() *cobra.Command { }) cmd.AddCommand(&cobra.Command{ - Use: "revoke ", - Short: "Revoke every remembered permission for one app", - Args: common.ExactArgs(1), + Use: "revoke ", + Short: "Revoke every remembered permission for one app", + Example: ` ncli bunker sessions revoke `, + Args: common.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { jsonMode, _ := cmd.Flags().GetBool("json") @@ -312,9 +320,10 @@ func newSessionsCommand() *cobra.Command { }) cmd.AddCommand(&cobra.Command{ - Use: "rename ", - Short: "Set (or clear, with \"\") a trusted app's display name", - Args: common.ExactArgs(2), + Use: "rename ", + Short: "Set (or clear, with \"\") a trusted app's display name", + Example: ` ncli bunker sessions rename "My Wallet"`, + Args: common.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { jsonMode, _ := cmd.Flags().GetBool("json") @@ -341,9 +350,10 @@ func newSessionsCommand() *cobra.Command { }) cmd.AddCommand(&cobra.Command{ - Use: "grants ", - Short: "List one trusted app's remembered permissions individually", - Args: common.ExactArgs(1), + Use: "grants ", + Short: "List one trusted app's remembered permissions individually", + Example: ` ncli bunker sessions grants `, + Args: common.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { jsonMode, _ := cmd.Flags().GetBool("json") @@ -384,9 +394,10 @@ func newSessionsCommand() *cobra.Command { }) revokeGrantCmd := &cobra.Command{ - Use: "revoke-grant ", - Short: "Revoke one remembered permission for an app, leaving the rest", - Args: common.ExactArgs(1), + Use: "revoke-grant ", + Short: "Revoke one remembered permission for an app, leaving the rest", + Example: ` ncli bunker sessions revoke-grant --method sign_event`, + Args: common.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { jsonMode, _ := cmd.Flags().GetBool("json") method, _ := cmd.Flags().GetString("method") @@ -434,8 +445,9 @@ func newSessionsCommand() *cobra.Command { // "undo a past decision" action to give it a group for. func newHistoryCommand() *cobra.Command { return &cobra.Command{ - Use: "history", - Short: "List recently resolved requests (approved/rejected/expired)", + Use: "history", + Short: "List recently resolved requests (approved/rejected/expired)", + Example: ` ncli bunker history`, RunE: func(cmd *cobra.Command, args []string) error { jsonMode, _ := cmd.Flags().GetBool("json") @@ -500,6 +512,9 @@ pairing instead, blocking until the client confirms or it times out. declared set of permissions (see examples/bunker/ for the YAML shape), instead of prompting interactively on first use. "ncli bunker sessions grants " shows what actually landed once paired.`, + Example: ` ncli bunker connect + ncli bunker connect nostrconnect://... + ncli bunker connect --grants grants.yaml`, Args: common.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { jsonMode, _ := cmd.Flags().GetBool("json") diff --git a/cli/delegate/command.go b/cli/delegate/command.go index ff032a8..fd9f224 100644 --- a/cli/delegate/command.go +++ b/cli/delegate/command.go @@ -36,6 +36,8 @@ wizard and generates the token non-interactively instead. pubkey, nprofile, or nip-05 address, and must resolve to a private key -- a pubkey-only identity has nothing to sign or derive a delegatee key from and is rejected.`, + Example: ` ncli id delegate + ncli id delegate --issuer mylabel --delegatee npub1... --kinds 1`, RunE: func(cmd *cobra.Command, args []string) error { // No config reload here: root's InitConfig (cli/ncli/root.go) // already loaded it once via the nearest ancestor's diff --git a/cli/ncli/apply.go b/cli/ncli/apply.go index cb8a3c1..e6c28ab 100644 --- a/cli/ncli/apply.go +++ b/cli/ncli/apply.go @@ -16,6 +16,8 @@ var ( Use: "apply", Short: "Run a client workflow from a config file", Long: `Run a stream, sync, or inspect workflow defined in a YAML config file.`, + Example: ` ncli apply -f sync.yaml + ncli apply -f sync.yaml --strict-pow`, Args: func(cmd *cobra.Command, args []string) error { if err := cmd.ValidateRequiredFlags(); err != nil { return common.UsageError(cmd, err) diff --git a/cli/ncli/decode.go b/cli/ncli/decode.go index 8f87dbb..208a3e4 100644 --- a/cli/ncli/decode.go +++ b/cli/ncli/decode.go @@ -24,6 +24,8 @@ var decodeCmd = &cobra.Command{ A pairing secret is never included in the output, for either of the two connection shapes. --json switches to structured JSON output on stdout.`, + Example: ` ncli decode npub1... + ncli decode nevent1...`, Args: common.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { jsonMode, _ := cmd.Flags().GetBool("json") diff --git a/cli/ncli/dump.go b/cli/ncli/dump.go index fca7103..6684923 100644 --- a/cli/ncli/dump.go +++ b/cli/ncli/dump.go @@ -24,6 +24,8 @@ by event ID across every target. Targets and filters come from --targets (a YAML file), or --relays plus inline filter flags -- pick one, not both. Omitting both falls back to the relays configured via "ncli prefs relays add".`, + Example: ` ncli dump -o events.json + ncli dump -t targets.yaml -o events.json`, Args: func(cmd *cobra.Command, args []string) error { if err := cmd.ValidateRequiredFlags(); err != nil { return common.UsageError(cmd, err) diff --git a/cli/ncli/find.go b/cli/ncli/find.go index 7f479eb..97b1b14 100644 --- a/cli/ncli/find.go +++ b/cli/ncli/find.go @@ -35,6 +35,9 @@ the relays configured via "ncli prefs relays add". Always prints a single JSON array to stdout. --quiet also drops the progress narration on stderr.`, + Example: ` ncli find note1... + ncli find npub1... + ncli find -t targets.yaml --authors npub1...`, Args: func(cmd *cobra.Command, args []string) error { if err := cmd.ValidateRequiredFlags(); err != nil { return common.UsageError(cmd, err) diff --git a/cli/ncli/id.go b/cli/ncli/id.go index 268a067..517dab0 100644 --- a/cli/ncli/id.go +++ b/cli/ncli/id.go @@ -30,6 +30,8 @@ nip-05 address -- resolves and displays it, plus its vault status. from --label, and reads the vault password from NCLI_VAULT_PASSWORD. See "ncli id delegate" and "ncli id sign" for delegation tokens and signing.`, + Example: ` ncli id + ncli id mylabel`, Args: common.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { @@ -40,9 +42,10 @@ See "ncli id delegate" and "ncli id sign" for delegation tokens and signing.`, } var idListCmd = &cobra.Command{ - Use: "list", - Short: "List saved vault identities", - Args: common.NoArgs, + Use: "list", + Short: "List saved vault identities", + Example: ` ncli id list`, + Args: common.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return runIDList(cmd) }, diff --git a/cli/ncli/id_sign.go b/cli/ncli/id_sign.go index f44561a..13b8462 100644 --- a/cli/ncli/id_sign.go +++ b/cli/ncli/id_sign.go @@ -23,6 +23,7 @@ shape, so it chains directly into "ncli publish --events " or Fails if an event already declares a pubkey that conflicts with --identity's resolved pubkey, rather than re-signing under a different key.`, + Example: ` ncli id sign -e events.json -o signed.json --identity mylabel`, Args: func(cmd *cobra.Command, args []string) error { if err := cmd.ValidateRequiredFlags(); err != nil { return common.UsageError(cmd, err) diff --git a/cli/ncli/miner.go b/cli/ncli/miner.go index 3350ec3..b1ce1e1 100644 --- a/cli/ncli/miner.go +++ b/cli/ncli/miner.go @@ -20,10 +20,11 @@ import ( ) var minerCmd = &cobra.Command{ - Use: "miner", - Short: "Mine and verify proof-of-work", - Long: `Mine NIP-13 proof-of-work into an unsigned event, or verify PoW on already-mined events.`, - RunE: common.RequireSubcommand, + Use: "miner", + Short: "Mine and verify proof-of-work", + Long: `Mine NIP-13 proof-of-work into an unsigned event, or verify PoW on already-mined events.`, + Example: ` ncli miner mine -e event.json`, + RunE: common.RequireSubcommand, } var minerMineCmd = &cobra.Command{ @@ -40,6 +41,8 @@ The event comes from --event (a NIP-01 event file), or inline from If --identity resolves to a private key, the mined event is signed automatically before being written. A pubkey-only identity mines but can't sign (logged, not silent).`, + Example: ` ncli miner mine -e event.json -o mined.json + ncli miner mine -e event.json --in-place --workers 4`, Args: func(cmd *cobra.Command, args []string) error { if err := cmd.ValidateRequiredFlags(); err != nil { return common.UsageError(cmd, err) @@ -276,6 +279,8 @@ falls back to the relays configured via "ncli prefs relays add". --identity further narrows live mode to one identity's own events. Exits non-zero if any checked event fails.`, + Example: ` ncli miner check -e events.json + ncli miner check -t targets.yaml`, Args: func(cmd *cobra.Command, args []string) error { if err := cmd.ValidateRequiredFlags(); err != nil { return common.UsageError(cmd, err) diff --git a/cli/ncli/ping.go b/cli/ncli/ping.go index 2660581..2685346 100644 --- a/cli/ncli/ping.go +++ b/cli/ncli/ping.go @@ -26,6 +26,8 @@ Results narrate as plain log lines on stderr by default. --tui shows a live interactive board instead (requires a real terminal; ignored with --json/--quiet). --json prints a structured report to stdout instead of narrating. Exits non-zero if any relay was unreachable.`, + Example: ` ncli ping wss://relay.example.com + ncli ping -t targets.yaml`, Args: func(cmd *cobra.Command, args []string) error { if err := cmd.ValidateRequiredFlags(); err != nil { return common.UsageError(cmd, err) diff --git a/cli/ncli/prefs.go b/cli/ncli/prefs.go index b61dfd7..a0f328e 100644 --- a/cli/ncli/prefs.go +++ b/cli/ncli/prefs.go @@ -14,20 +14,23 @@ var prefsCmd = &cobra.Command{ Short: "Manage persistent ncli preferences", Long: `Manage preferences that persist across projects. Currently just the default relay list that find, dump, and miner check fall back to.`, - RunE: common.RequireSubcommand, + Example: ` ncli prefs relays list`, + RunE: common.RequireSubcommand, } var prefsRelaysCmd = &cobra.Command{ - Use: "relays", - Short: "Manage the default relay list", - Long: `Manage the relay list find, dump, and miner check consult when not given explicit targets.`, - RunE: common.RequireSubcommand, + Use: "relays", + Short: "Manage the default relay list", + Long: `Manage the relay list find, dump, and miner check consult when not given explicit targets.`, + Example: ` ncli prefs relays list`, + RunE: common.RequireSubcommand, } var prefsRelaysAddCmd = &cobra.Command{ - Use: "add ", - Short: "Add a relay to the default list", - Args: common.ExactArgs(1), + Use: "add ", + Short: "Add a relay to the default list", + Example: ` ncli prefs relays add wss://relay.example.com`, + Args: common.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { jsonMode, _ := cmd.Flags().GetBool("json") @@ -66,9 +69,10 @@ var prefsRelaysAddCmd = &cobra.Command{ } var prefsRelaysRemoveCmd = &cobra.Command{ - Use: "remove ", - Short: "Remove a relay from the default list", - Args: common.ExactArgs(1), + Use: "remove ", + Short: "Remove a relay from the default list", + Example: ` ncli prefs relays remove wss://relay.example.com`, + Args: common.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { jsonMode, _ := cmd.Flags().GetBool("json") @@ -99,9 +103,10 @@ var prefsRelaysRemoveCmd = &cobra.Command{ } var prefsRelaysListCmd = &cobra.Command{ - Use: "list", - Short: "List the default relays", - Args: common.NoArgs, + Use: "list", + Short: "List the default relays", + Example: ` ncli prefs relays list`, + Args: common.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { jsonMode, _ := cmd.Flags().GetBool("json") @@ -131,9 +136,10 @@ var prefsRelaysListCmd = &cobra.Command{ } var prefsRelaysClearCmd = &cobra.Command{ - Use: "clear", - Short: "Remove all default relays", - Args: common.NoArgs, + Use: "clear", + Short: "Remove all default relays", + Example: ` ncli prefs relays clear`, + Args: common.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { if err := client.SavePrefs(&client.Prefs{}); err != nil { return common.RuntimeError(cmd, err) @@ -149,9 +155,10 @@ var prefsRelaysClearCmd = &cobra.Command{ } var prefsPathCmd = &cobra.Command{ - Use: "path", - Short: "Print the prefs.yaml file path", - Args: common.NoArgs, + Use: "path", + Short: "Print the prefs.yaml file path", + Example: ` ncli prefs path`, + Args: common.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { path := client.PrefsPath() if jsonMode, _ := cmd.Flags().GetBool("json"); jsonMode { diff --git a/cli/ncli/publish.go b/cli/ncli/publish.go index 20b2fa6..b09f557 100644 --- a/cli/ncli/publish.go +++ b/cli/ncli/publish.go @@ -21,6 +21,8 @@ var publishCmd = &cobra.Command{ every relay, and the full (event, relay) result is reported. Omitting --relays falls back to the relays configured via "ncli prefs relays add". Exits non-zero if any pair fails.`, + Example: ` ncli publish -e signed.json + ncli publish -e signed.json -s wss://relay.example.com`, Args: func(cmd *cobra.Command, args []string) error { if err := cmd.ValidateRequiredFlags(); err != nil { return common.UsageError(cmd, err) diff --git a/cli/ncli/root.go b/cli/ncli/root.go index 7a42f4c..8a8ba5e 100644 --- a/cli/ncli/root.go +++ b/cli/ncli/root.go @@ -32,6 +32,8 @@ var RootCmd = &cobra.Command{ Use: "ncli", Short: "Nostr relay & toolkit CLI", Long: `Run and operate Nostr relays, and manage events: serve, stream, sync, inspect, export, delegate, administer, and mine.`, + Example: ` ncli id + ncli find npub1...`, } func init() { @@ -78,11 +80,11 @@ func resolveConfigFile() string { // InitConfig loads viper config and sets up logging (log dir, crash log // path, console+file writers). It's not wired up as a -// cobra.OnInitialize/PersistentPreRun here because cmd/ncli/main.go -// reparents RootCmd's subcommands onto its own root command, which would -// orphan a PersistentPreRun set on RootCmd; the caller is responsible for -// invoking this from whatever command tree actually gets executed, and for -// letting individual leaf commands (e.g. version) opt out. +// cobra.OnInitialize/PersistentPreRun here because cmd/ncli/main.go sets +// RootCmd.PersistentPreRun itself, once, after also mounting relay/ +// bunker/blossom onto RootCmd -- keeping that assembly in one place. The +// caller is responsible for letting individual leaf commands (e.g. +// version) opt out. func InitConfig() { if err := common.LoadViperConfig(resolveConfigFile()); err != nil { log.Warn().Err(err).Msg("config error") diff --git a/cli/ncli/version.go b/cli/ncli/version.go index 989600c..f366ba2 100644 --- a/cli/ncli/version.go +++ b/cli/ncli/version.go @@ -17,6 +17,7 @@ var versionCmd = &cobra.Command{ reads and writes: the app data directory, prefs file, vault file, and log directory. --json prints the same information as structured JSON, for scripts or an AI agent.`, + Example: ` ncli version`, // version just reads embedded build info, so it skips the root's // PersistentPreRun (config loading, log dir/crash log setup) instead // of inheriting it like every other subcommand. diff --git a/cli/relay/admin.go b/cli/relay/admin.go index c747419..85e31cd 100644 --- a/cli/relay/admin.go +++ b/cli/relay/admin.go @@ -45,28 +45,31 @@ var ( // was flattened away: "ncli relay admin stats" -> "ncli relay stats". func addRemoteAdminCommands(cmd *cobra.Command) { statsCmd := &cobra.Command{ - Use: "stats", - Short: "Display live relay metrics and worker status", - RunE: runStats, + Use: "stats", + Short: "Display live relay metrics and worker status", + Example: ` ncli relay stats --config relay.yaml`, + RunE: runStats, } cmd.AddCommand(statsCmd) reindexCmd := &cobra.Command{ - Use: "reindex", - Short: "Trigger a reindex on the running relay, without restarting it", - RunE: common.RequireSubcommand, + Use: "reindex", + Short: "Trigger a reindex on the running relay, without restarting it", + Example: ` ncli relay reindex search --config relay.yaml`, + RunE: common.RequireSubcommand, } - reindexCmd.AddCommand(&cobra.Command{Use: "search", Short: "Reindex profiles to the search index", RunE: runReindexSearch}) - reindexCmd.AddCommand(&cobra.Command{Use: "zaps", Short: "Reindex zap stats", RunE: runReindexZaps}) + reindexCmd.AddCommand(&cobra.Command{Use: "search", Short: "Reindex profiles to the search index", Example: ` ncli relay reindex search --config relay.yaml`, RunE: runReindexSearch}) + reindexCmd.AddCommand(&cobra.Command{Use: "zaps", Short: "Reindex zap stats", Example: ` ncli relay reindex zaps --config relay.yaml`, RunE: runReindexZaps}) cmd.AddCommand(reindexCmd) clearCmd := &cobra.Command{ - Use: "clear", - Short: "Clear indexes on the running relay, without restarting it", - RunE: common.RequireSubcommand, + Use: "clear", + Short: "Clear indexes on the running relay, without restarting it", + Example: ` ncli relay clear search --config relay.yaml`, + RunE: common.RequireSubcommand, } - clearCmd.AddCommand(&cobra.Command{Use: "search", Short: "Delete all profiles from the search index", RunE: runClearSearch}) - clearCmd.AddCommand(&cobra.Command{Use: "zaps", Short: "Delete all zap counters", RunE: runClearZaps}) + clearCmd.AddCommand(&cobra.Command{Use: "search", Short: "Delete all profiles from the search index", Example: ` ncli relay clear search --config relay.yaml`, RunE: runClearSearch}) + clearCmd.AddCommand(&cobra.Command{Use: "zaps", Short: "Delete all zap counters", Example: ` ncli relay clear zaps --config relay.yaml`, RunE: runClearZaps}) cmd.AddCommand(clearCmd) addMembershipAdminCommands(cmd) @@ -79,41 +82,50 @@ func addRemoteAdminCommands(cmd *cobra.Command) { // the underlying mechanism differs at all. func addMembershipAdminCommands(cmd *cobra.Command) { membersCmd := &cobra.Command{ - Use: "members", - Short: "Manage NIP-43 relay membership", - RunE: common.RequireSubcommand, + Use: "members", + Short: "Manage NIP-43 relay membership", + Example: ` ncli relay members list --config relay.yaml`, + RunE: common.RequireSubcommand, } membersCmd.AddCommand(&cobra.Command{ Use: "list", Short: "List all members", - RunE: runMembersList, + Example: ` ncli relay members list --config relay.yaml`, + RunE: runMembersList, }) membersCmd.AddCommand(&cobra.Command{ Use: "show ", Short: "Show one member's record", - Args: common.ExactArgs(1), RunE: runMembersShow, + Example: ` ncli relay members show --config relay.yaml`, + Args: common.ExactArgs(1), RunE: runMembersShow, }) membersAddCmd := &cobra.Command{ Use: "add ", Short: "Enroll a pubkey as a member", Long: `Enroll a pubkey as a member directly -- bypasses the self-service invite-code join flow, no invite claim required.`, + Example: ` ncli relay members add --config relay.yaml + ncli relay members add --role member --config relay.yaml`, Args: common.ExactArgs(1), RunE: runMembersAdd, } membersAddCmd.Flags().StringArray("role", nil, "role id to assign (repeatable)") membersCmd.AddCommand(membersAddCmd) membersCmd.AddCommand(&cobra.Command{ Use: "remove ", Short: "Remove a member", - Args: common.ExactArgs(1), RunE: runMembersRemove, + Example: ` ncli relay members remove --config relay.yaml`, + Args: common.ExactArgs(1), RunE: runMembersRemove, }) cmd.AddCommand(membersCmd) invitesCmd := &cobra.Command{ - Use: "invites", - Short: "Manage NIP-43 invite codes", - RunE: common.RequireSubcommand, + Use: "invites", + Short: "Manage NIP-43 invite codes", + Example: ` ncli relay invites create --config relay.yaml`, + RunE: common.RequireSubcommand, } invitesCreateCmd := &cobra.Command{ Use: "create", Short: "Issue a new invite code", Long: `Issue a new invite code, for handing out out-of-band (a signup email, a Discord invite flow) before the invitee has a working Nostr client.`, + Example: ` ncli relay invites create --config relay.yaml + ncli relay invites create --ttl 24h --max-uses 10 --config relay.yaml`, RunE: runInvitesCreate, } invitesCreateCmd.Flags().Duration("ttl", 0, "how long the code stays valid (default: relay's configured default)") @@ -122,29 +134,34 @@ Discord invite flow) before the invitee has a working Nostr client.`, invitesCmd.AddCommand(invitesCreateCmd) invitesCmd.AddCommand(&cobra.Command{ Use: "list", Short: "List all invite codes", - RunE: runInvitesList, + Example: ` ncli relay invites list --config relay.yaml`, + RunE: runInvitesList, }) invitesCmd.AddCommand(&cobra.Command{ Use: "revoke ", Short: "Revoke an invite code", - Args: common.ExactArgs(1), RunE: runInvitesRevoke, + Example: ` ncli relay invites revoke --config relay.yaml`, + Args: common.ExactArgs(1), RunE: runInvitesRevoke, }) cmd.AddCommand(invitesCmd) rolesCmd := &cobra.Command{ - Use: "roles", - Short: "Manage NIP-43 role definitions", - RunE: common.RequireSubcommand, + Use: "roles", + Short: "Manage NIP-43 role definitions", + Example: ` ncli relay roles list --config relay.yaml`, + RunE: common.RequireSubcommand, } rolesCmd.AddCommand(&cobra.Command{ Use: "list", Short: "List all role definitions", - RunE: runRolesList, + Example: ` ncli relay roles list --config relay.yaml`, + RunE: runRolesList, }) rolesCreateCmd := &cobra.Command{ Use: "create ", Short: "Create a role definition", Long: `NIP-43 defines no "delete" for a role -- once created, an id can only be superseded (re-run "create" with the same id and new label/description/ color/order), never removed.`, - Args: common.ExactArgs(1), RunE: runRolesCreate, + Example: ` ncli relay roles create moderator --label Moderator --config relay.yaml`, + Args: common.ExactArgs(1), RunE: runRolesCreate, } rolesCreateCmd.Flags().String("label", "", "human-readable role label") rolesCreateCmd.Flags().String("description", "", "role description") diff --git a/cli/relay/command.go b/cli/relay/command.go index 5baa6a3..6cdb14c 100644 --- a/cli/relay/command.go +++ b/cli/relay/command.go @@ -238,6 +238,8 @@ relay that's already running, over NIP-98 authenticated HTTP. "relay context"), creating it on the spot -- a minimal config, backed by a freshly generated identity under .../relays// -- if that name isn't saved yet.`, + Example: ` ncli relay --config relay.yaml + ncli relay --context myrelay`, PreRunE: func(cmd *cobra.Command, args []string) error { ctxName, _ := cmd.Flags().GetString("context") if ctxName != "" { diff --git a/cli/relay/context.go b/cli/relay/context.go index 9c91969..f17edf5 100644 --- a/cli/relay/context.go +++ b/cli/relay/context.go @@ -28,33 +28,37 @@ marking the current one with "*". A context is what every relay command uses when --config is omitted, taking priority over any ncli.yaml/relay.yaml in the working directory. See "list", "add", "remove", and "use" to manage contexts.`, - Args: common.NoArgs, - RunE: runContextList, + Example: ` ncli relay context list`, + Args: common.NoArgs, + RunE: runContextList, } listCmd := &cobra.Command{ - Use: "list", - Short: "List saved relay contexts", - Long: `Same as bare "context": lists saved relay contexts, marking the current one with "*".`, - Args: common.NoArgs, - RunE: runContextList, + Use: "list", + Short: "List saved relay contexts", + Long: `Same as bare "context": lists saved relay contexts, marking the current one with "*".`, + Example: ` ncli relay context list`, + Args: common.NoArgs, + RunE: runContextList, } contextCmd.AddCommand(listCmd) addCmd := &cobra.Command{ - Use: "add ", - Short: "Save a named relay context", - Long: `Save name -> config-path in prefs.yaml. config-path must already exist.`, - Args: common.ExactArgs(2), - RunE: runContextAdd, + Use: "add ", + Short: "Save a named relay context", + Long: `Save name -> config-path in prefs.yaml. config-path must already exist.`, + Example: ` ncli relay context add myrelay relay.yaml`, + Args: common.ExactArgs(2), + RunE: runContextAdd, } contextCmd.AddCommand(addCmd) removeCmd := &cobra.Command{ - Use: "remove ", - Short: "Remove a saved relay context", - Args: common.ExactArgs(1), - RunE: runContextRemove, + Use: "remove ", + Short: "Remove a saved relay context", + Example: ` ncli relay context remove myrelay`, + Args: common.ExactArgs(1), + RunE: runContextRemove, } contextCmd.AddCommand(removeCmd) @@ -64,8 +68,9 @@ contexts.`, Long: `Set name as the current relay context -- every relay command uses its config file when --config is omitted, even if the working directory has its own ncli.yaml/relay.yaml.`, - Args: common.ExactArgs(1), - RunE: runContextUse, + Example: ` ncli relay context use myrelay`, + Args: common.ExactArgs(1), + RunE: runContextUse, } contextCmd.AddCommand(useCmd) diff --git a/cmd/ncli/main.go b/cmd/ncli/main.go index 24813b6..8ffeb6e 100644 --- a/cmd/ncli/main.go +++ b/cmd/ncli/main.go @@ -11,27 +11,11 @@ import ( "github.com/spf13/cobra" ) -var rootCmd = &cobra.Command{ - Use: "ncli", - Short: "Nostr relay & toolkit CLI", - Long: `Run and operate Nostr relays, and manage events: serve, stream, sync, inspect, export, delegate, administer, and mine.`, -} - func init() { - // Flatten ncli subcommands into the root - for _, c := range ncli.RootCmd.Commands() { - ncli.RootCmd.RemoveCommand(c) - rootCmd.AddCommand(c) - } - - // Transfer persistent flags (e.g. --config) from ncli root - rootCmd.PersistentFlags().AddFlagSet(ncli.RootCmd.PersistentFlags()) - // Config loading and logging setup, for every command except those // (e.g. version) that define their own no-op PersistentPreRun to opt - // out. Set here, not on ncli.RootCmd, since its subcommands are - // reparented onto rootCmd above and would no longer reach it there. - rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) { + // out. + ncli.RootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) { ncli.InitConfig() } @@ -39,17 +23,17 @@ func init() { // and "clear" as its own children, for operating a relay that's // already running over NIP-98 authenticated HTTP; see NewRelayCommand. // "delegate" is mounted under "id" instead, in cli/ncli/id.go's init(). - rootCmd.AddCommand(relaycli.NewRelayCommand()) + ncli.RootCmd.AddCommand(relaycli.NewRelayCommand()) // Register the NIP-46 remote-signer ("bunker") command -- mounts // "attach"/"status"/"stop"/"sessions"/"connect" as its own children; // see NewBunkerCommand. - rootCmd.AddCommand(bunker.NewBunkerCommand()) + ncli.RootCmd.AddCommand(bunker.NewBunkerCommand()) // Register the Blossom media-server ("blossom") command -- mounts // "upload"/"download"/"list"/"rm"/"mirror"/"servers"/"report" as its // own children; see NewBlossomCommand. - rootCmd.AddCommand(blossom.NewBlossomCommand()) + ncli.RootCmd.AddCommand(blossom.NewBlossomCommand()) } func main() { @@ -66,7 +50,7 @@ func main() { // resolved subcommand's --json flag -- the single point where every // command's failure is rendered and exited, instead of each command // printing (and exiting) its own way. - cmd, err := rootCmd.ExecuteC() + cmd, err := ncli.RootCmd.ExecuteC() if err != nil { common.EmitError(cmd, err) os.Exit(common.ExitCode(err)) diff --git a/cmd/ncli/main_test.go b/cmd/ncli/main_test.go index 88ce6e9..e7f8f75 100644 --- a/cmd/ncli/main_test.go +++ b/cmd/ncli/main_test.go @@ -3,17 +3,18 @@ package main import ( "testing" + "github.com/ohstr/ncli/cli/ncli" "github.com/spf13/cobra" ) -// resolve walks rootCmd's tree for args (e.g. "relay", "admin", "stats") +// resolve walks ncli.RootCmd's tree for args (e.g. "relay", "admin", "stats") // without executing anything, and fails the test if it doesn't fully // resolve to a command whose own Name() matches the last arg -- guarding // against both a missing command and cobra falling back to a shallower // partial match. func resolve(t *testing.T, args ...string) *cobra.Command { t.Helper() - cmd, _, err := rootCmd.Find(args) + cmd, _, err := ncli.RootCmd.Find(args) if err != nil { t.Fatalf("Find(%v) returned error: %v", args, err) } @@ -50,7 +51,7 @@ func TestCommandTree_RelayFlattensAdmin(t *testing.T) { // can still resolve, so on an absent "admin" it should stop at "relay" // rather than reach an "admin" or "stats" command. func TestCommandTree_AdminRemoved(t *testing.T) { - cmd, _, err := rootCmd.Find([]string{"relay", "admin", "stats"}) + cmd, _, err := ncli.RootCmd.Find([]string{"relay", "admin", "stats"}) if err != nil { t.Fatalf("Find returned error: %v", err) } @@ -161,7 +162,7 @@ func TestCommandTree_IDNestsDelegate(t *testing.T) { // paths. func TestCommandTree_OldFlatPathsRemoved(t *testing.T) { for _, name := range []string{"admin", "reindex", "delegate"} { - for _, c := range rootCmd.Commands() { + for _, c := range ncli.RootCmd.Commands() { if c.Name() == name { t.Errorf("root command %q still exists as a top-level subcommand; expected it to only exist nested (relay %s / id %s)", name, name, name) } From 4c0e1ed84de0bb580ce2db002ebe361bbdb33fbe Mon Sep 17 00:00:00 2001 From: naliyi <154817482+naliyi@users.noreply.github.com> Date: Thu, 17 Sep 2026 14:47:15 +0000 Subject: [PATCH 5/9] fix: correct two Example: commands that failed against real ncli Caught by actually running every command's Example against a live local relay: `miner`'s group-level example omitted --out/--in-place (miner mine requires exactly one), and find's --targets+--authors combo violated the real targets-vs-inline-filters mutual exclusion. Both now use flag combinations verified to actually work. --- cli/ncli/find.go | 2 +- cli/ncli/miner.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/ncli/find.go b/cli/ncli/find.go index 97b1b14..7319189 100644 --- a/cli/ncli/find.go +++ b/cli/ncli/find.go @@ -37,7 +37,7 @@ Always prints a single JSON array to stdout. --quiet also drops the progress narration on stderr.`, Example: ` ncli find note1... ncli find npub1... - ncli find -t targets.yaml --authors npub1...`, + ncli find --authors npub1... -s wss://relay.example.com`, Args: func(cmd *cobra.Command, args []string) error { if err := cmd.ValidateRequiredFlags(); err != nil { return common.UsageError(cmd, err) diff --git a/cli/ncli/miner.go b/cli/ncli/miner.go index b1ce1e1..84058b7 100644 --- a/cli/ncli/miner.go +++ b/cli/ncli/miner.go @@ -23,7 +23,7 @@ var minerCmd = &cobra.Command{ Use: "miner", Short: "Mine and verify proof-of-work", Long: `Mine NIP-13 proof-of-work into an unsigned event, or verify PoW on already-mined events.`, - Example: ` ncli miner mine -e event.json`, + Example: ` ncli miner mine -e event.json -o mined.json`, RunE: common.RequireSubcommand, } From 16b85c6fb59986c7916eb1814c12f3137c812ead Mon Sep 17 00:00:00 2001 From: naliyi <154817482+naliyi@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:58:35 +0000 Subject: [PATCH 6/9] chore: bump github.com/ohstr/nmilat to v0.3.0 Verified: ncli doesn't use nipcash (the one package with a breaking rename in this release), go build/vet/test all pass. The only test failures are pre-existing environment limitations unrelated to this bump -- Docker-based integration tests (TestStreamIntegration et al.) fail because sibling-container port publishing doesn't route to this sandbox's own localhost (confirmed independently with a manual `docker compose up` + curl, outside any Go test), and TestMultiRelaySync hits real public relays and is documented as not run in CI. --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 06243ad..098cf65 100644 --- a/go.mod +++ b/go.mod @@ -69,7 +69,7 @@ require ( github.com/charmbracelet/bubbles v1.0.0 github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/lipgloss v1.1.0 - github.com/ohstr/nmilat v0.2.9 + github.com/ohstr/nmilat v0.3.0 golang.org/x/sync v0.20.0 golang.org/x/sys v0.45.0 golang.org/x/term v0.43.0 diff --git a/go.sum b/go.sum index a2dcf77..4567050 100644 --- a/go.sum +++ b/go.sum @@ -89,8 +89,8 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= -github.com/ohstr/nmilat v0.2.9 h1:ajC/HMGb8N0tqGNdIpEI2SppsZjsOX9Wp1gBj7zSstg= -github.com/ohstr/nmilat v0.2.9/go.mod h1:+6B0CT40RAJmnZESl1sBukc07RkdyPn5/EQ8ROVof0Y= +github.com/ohstr/nmilat v0.3.0 h1:/UW2Fur9uTWqoFO9ta9RIK6+3vJZOyRuiv7JDty0OHY= +github.com/ohstr/nmilat v0.3.0/go.mod h1:+6B0CT40RAJmnZESl1sBukc07RkdyPn5/EQ8ROVof0Y= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= From 8eb3e13ac9276a3eae8116fe79083768ae1d1928 Mon Sep 17 00:00:00 2001 From: naliyi <154817482+naliyi@users.noreply.github.com> Date: Thu, 17 Sep 2026 16:30:44 +0000 Subject: [PATCH 7/9] fix: trim noisy Long text and use a realistic example on ncli id Drops the "See ncli id delegate/sign" pointer sentence -- both are already listed with their own Short descriptions under Available Commands in the same --help output. Also swaps the placeholder "mylabel" Example for "satoshi", closer to how the flag is actually used. --- cli/ncli/id.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/cli/ncli/id.go b/cli/ncli/id.go index 517dab0..93b4dc3 100644 --- a/cli/ncli/id.go +++ b/cli/ncli/id.go @@ -20,18 +20,14 @@ import ( var idCmd = &cobra.Command{ Use: "id [identifier]", Short: "Generate or inspect a Nostr identity", - Long: `With no argument, generates a new Nostr keypair (hex, nsec, npub) and -offers to save it to the local vault. - -With an identifier -- a vault label, npub, hex pubkey, nsec, nprofile, or -nip-05 address -- resolves and displays it, plus its vault status. + Long: `With no argument, generates a new Nostr keypair. With an identifier -- +a vault label, npub, hex pubkey, nsec, nprofile, or nip-05 address -- +resolves and displays it instead. --json disables interactive prompts: saves only with --save, labels only -from --label, and reads the vault password from NCLI_VAULT_PASSWORD. - -See "ncli id delegate" and "ncli id sign" for delegation tokens and signing.`, +from --label, and reads the vault password from NCLI_VAULT_PASSWORD.`, Example: ` ncli id - ncli id mylabel`, + ncli id satoshi`, Args: common.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { From 5931b60b3c14d1c52cb93ae4ed9fe6bb3f523028 Mon Sep 17 00:00:00 2001 From: naliyi <154817482+naliyi@users.noreply.github.com> Date: Thu, 17 Sep 2026 16:38:42 +0000 Subject: [PATCH 8/9] fix: trim redundant help text and placeholder examples across commands Full sweep of all 24 command files' Long/Short/Example text (a followup to the same fix on ncli id): - relay context: drops the "See list/add/remove/use" pointer sentence -- those subcommands already show their own Short descriptions under Available Commands in the same --help output. - find: drops "--authors also accepts nip-05 addresses..." -- the --authors flag's own description already says this. - id sign: drops the Long-text restatement of what --identity's own flag description already says (vault label/nsec required, pubkey-only rejected). - Replaces the generic "mylabel" placeholder with "satoshi" in every Example that names a vault identity (blossom upload/list/mirror/ rm/report, id sign, id delegate), matching the id command's own example. No other Long text found this noisy across the remaining 20 files -- each explains flag interactions or behavior not already stated elsewhere. go build/vet/test all pass. --- cli/blossom/command.go | 2 +- cli/blossom/list.go | 4 ++-- cli/blossom/mirror.go | 2 +- cli/blossom/report.go | 2 +- cli/blossom/rm.go | 2 +- cli/blossom/upload.go | 6 +++--- cli/delegate/command.go | 2 +- cli/ncli/find.go | 2 -- cli/ncli/id_sign.go | 6 ++---- cli/relay/context.go | 3 +-- 10 files changed, 13 insertions(+), 18 deletions(-) diff --git a/cli/blossom/command.go b/cli/blossom/command.go index ed0385a..5fb1d9c 100644 --- a/cli/blossom/command.go +++ b/cli/blossom/command.go @@ -23,7 +23,7 @@ per (item, server) pair, and exiting non-zero if any pair failed. "download" tries the configured servers in order, stopping at the first that answers; "list" queries one server by default, or every server with --all.`, - Example: ` ncli blossom upload ./photo.jpg --identity mylabel`, + Example: ` ncli blossom upload ./photo.jpg --identity satoshi`, RunE: common.RequireSubcommand, } diff --git a/cli/blossom/list.go b/cli/blossom/list.go index b8d22be..cf04292 100644 --- a/cli/blossom/list.go +++ b/cli/blossom/list.go @@ -32,8 +32,8 @@ deduped by hash. identifier may be a vault label, nsec, npub, hex pubkey, nprofile, or nip-05 address, resolved to a hex pubkey; defaults to --identity's resolved pubkey when omitted.`, - Example: ` ncli blossom list --identity mylabel - ncli blossom list --identity mylabel --all`, + Example: ` ncli blossom list --identity satoshi + ncli blossom list --identity satoshi --all`, Args: common.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { ctx, cancel := signal.NotifyContext(cmd.Context(), syscall.SIGINT, syscall.SIGTERM) diff --git a/cli/blossom/mirror.go b/cli/blossom/mirror.go index 4038a97..27dc47b 100644 --- a/cli/blossom/mirror.go +++ b/cli/blossom/mirror.go @@ -18,7 +18,7 @@ func newMirrorCommand() *cobra.Command { (--server, or the configured default list) -- each server fetches source-url itself; no bytes pass through ncli. Reports a result per server.`, - Example: ` ncli blossom mirror https://example.com/file.jpg --identity mylabel`, + Example: ` ncli blossom mirror https://example.com/file.jpg --identity satoshi`, Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { return common.UsageError(cmd, fmt.Errorf("exactly one source URL is required")) diff --git a/cli/blossom/report.go b/cli/blossom/report.go index 1f8049e..ca3c6d8 100644 --- a/cli/blossom/report.go +++ b/cli/blossom/report.go @@ -17,7 +17,7 @@ func newReportCommand() *cobra.Command { Long: `Sign and submit a kind:1984 report event to a server's PUT /report -- authenticated by its own signature, not a BUD-11 token. Targets one server: --server, or the first configured default.`, - Example: ` ncli blossom report --identity mylabel`, + Example: ` ncli blossom report --identity satoshi`, Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { return common.UsageError(cmd, fmt.Errorf("exactly one hash is required")) diff --git a/cli/blossom/rm.go b/cli/blossom/rm.go index 4777b06..47cd912 100644 --- a/cli/blossom/rm.go +++ b/cli/blossom/rm.go @@ -23,7 +23,7 @@ func newRmCommand() *cobra.Command { Long: `Sign a hash-scoped BUD-11 authorization and DELETE the blob from every target server (--server, or the configured default list), reporting a result per server. Requires --yes in a non-interactive session.`, - Example: ` ncli blossom rm --identity mylabel --yes`, + Example: ` ncli blossom rm --identity satoshi --yes`, Args: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { return common.UsageError(cmd, fmt.Errorf("exactly one hash is required")) diff --git a/cli/blossom/upload.go b/cli/blossom/upload.go index b60108e..474953e 100644 --- a/cli/blossom/upload.go +++ b/cli/blossom/upload.go @@ -28,9 +28,9 @@ func newUploadCommand() *cobra.Command { Pass --optimize to request server-side transcoding/optimization (BUD-05's PUT /media) instead of a byte-for-byte store.`, - Example: ` ncli blossom upload ./photo.jpg --identity mylabel - ncli blossom upload ./photo.jpg --identity mylabel --optimize - ncli blossom upload ./photo.jpg --identity mylabel --server https://blossom.example.com`, + Example: ` ncli blossom upload ./photo.jpg --identity satoshi + ncli blossom upload ./photo.jpg --identity satoshi --optimize + ncli blossom upload ./photo.jpg --identity satoshi --server https://blossom.example.com`, Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { return common.UsageError(cmd, fmt.Errorf("at least one file is required")) diff --git a/cli/delegate/command.go b/cli/delegate/command.go index fd9f224..a444792 100644 --- a/cli/delegate/command.go +++ b/cli/delegate/command.go @@ -37,7 +37,7 @@ pubkey, nprofile, or nip-05 address, and must resolve to a private key -- a pubkey-only identity has nothing to sign or derive a delegatee key from and is rejected.`, Example: ` ncli id delegate - ncli id delegate --issuer mylabel --delegatee npub1... --kinds 1`, + ncli id delegate --issuer satoshi --delegatee npub1... --kinds 1`, RunE: func(cmd *cobra.Command, args []string) error { // No config reload here: root's InitConfig (cli/ncli/root.go) // already loaded it once via the nearest ancestor's diff --git a/cli/ncli/find.go b/cli/ncli/find.go index 7319189..256e4f5 100644 --- a/cli/ncli/find.go +++ b/cli/ncli/find.go @@ -27,8 +27,6 @@ identifier is a positional argument: other filters given. With no other filters, defaults to just their profile (kind 0); pass --kinds to widen it. ---authors also accepts nip-05 addresses alongside hex pubkeys. - Targets and filters come from --targets (a YAML file), or --relays plus inline filter flags -- pick one, not both. Omitting both falls back to the relays configured via "ncli prefs relays add". diff --git a/cli/ncli/id_sign.go b/cli/ncli/id_sign.go index 13b8462..b03fd98 100644 --- a/cli/ncli/id_sign.go +++ b/cli/ncli/id_sign.go @@ -13,9 +13,7 @@ import ( var idSignCmd = &cobra.Command{ Use: "sign", Short: "Sign one or more unsigned events with a Nostr identity", - Long: `Sign an unsigned event (or array of them) with the private key behind ---identity -- a vault label or nsec; a pubkey-only identity (npub/hex/ -nprofile/nip-05) has no private key and is rejected. + Long: `Sign an unsigned event (or array of them) with --identity's private key. --events accepts a single event or an array; --out is written in the same shape, so it chains directly into "ncli publish --events " or @@ -23,7 +21,7 @@ shape, so it chains directly into "ncli publish --events " or Fails if an event already declares a pubkey that conflicts with --identity's resolved pubkey, rather than re-signing under a different key.`, - Example: ` ncli id sign -e events.json -o signed.json --identity mylabel`, + Example: ` ncli id sign -e events.json -o signed.json --identity satoshi`, Args: func(cmd *cobra.Command, args []string) error { if err := cmd.ValidateRequiredFlags(); err != nil { return common.UsageError(cmd, err) diff --git a/cli/relay/context.go b/cli/relay/context.go index f17edf5..bf68f6b 100644 --- a/cli/relay/context.go +++ b/cli/relay/context.go @@ -26,8 +26,7 @@ func addContextCommands(cmd *cobra.Command) { Long: `Bare invocation lists saved relay contexts (name -> config file path), marking the current one with "*". A context is what every relay command uses when --config is omitted, taking priority over any ncli.yaml/relay.yaml in -the working directory. See "list", "add", "remove", and "use" to manage -contexts.`, +the working directory.`, Example: ` ncli relay context list`, Args: common.NoArgs, RunE: runContextList, From dc819fe912fd1c3f67235b7a4b3174a640c9e8ea Mon Sep 17 00:00:00 2001 From: naliyi <154817482+naliyi@users.noreply.github.com> Date: Thu, 17 Sep 2026 22:21:14 +0000 Subject: [PATCH 9/9] release: add RC-safety guards and v0.5.0-rc.1 changelog entry .goreleaser.yaml: skip_upload: auto on the Homebrew tap, skip_push: auto on both per-arch docker images and both docker_manifests entries (the manifests would otherwise fail trying to stitch images that skip_push never pushed), and prerelease: auto on the GitHub release -- so tagging a -rc.N build never overwrites the stable formula, :latest image, or GitHub's "Latest release" marker. CHANGELOG.md: new top heading matching the v0.5.0-rc.1 tag, covering everything shipped since v0.4.9. --- .goreleaser.yaml | 8 ++++++++ CHANGELOG.md | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 8e12141..d3bc1fc 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -44,6 +44,7 @@ dockers: - "ghcr.io/ohstr/ncli:latest-amd64" build_flag_templates: - "--platform=linux/amd64" + skip_push: auto - id: ncli-arm64 ids: [ncli] goos: linux @@ -55,16 +56,21 @@ dockers: - "ghcr.io/ohstr/ncli:latest-arm64" build_flag_templates: - "--platform=linux/arm64" + skip_push: auto +# skip_push: auto here too -- otherwise a prerelease build would still try +# to manifest the per-arch tags above, which skip_push: auto never pushed. docker_manifests: - name_template: "ghcr.io/ohstr/ncli:{{ .Version }}" image_templates: - "ghcr.io/ohstr/ncli:{{ .Version }}-amd64" - "ghcr.io/ohstr/ncli:{{ .Version }}-arm64" + skip_push: auto - name_template: "ghcr.io/ohstr/ncli:latest" image_templates: - "ghcr.io/ohstr/ncli:latest-amd64" - "ghcr.io/ohstr/ncli:latest-arm64" + skip_push: auto brews: - name: ncli @@ -80,6 +86,7 @@ brews: bin.install "ncli" test: | system "#{bin}/ncli", "version" + skip_upload: auto # No `changelog:` block here, deliberately -- GoReleaser's changelog pipe # is also the only place that loads a `--release-notes` file into the @@ -101,3 +108,4 @@ release: # place if a Release for the tag already exists (e.g. from a prior # failed/retried run) instead of using --release-notes below. mode: replace + prerelease: auto diff --git a/CHANGELOG.md b/CHANGELOG.md index 32c33b8..e6fc125 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,42 @@ # Changelog +## [0.5.0-rc.1] + +### Added + +- `ncli decode` extended to NIP-CASH tokens (`lokicash1...`, any HRP) and + NIP-CW `circlehub1...` connections. A `cashhub1...` Hub connection is + recognized and rejected -- no local decoder for that format. The + pairing secret embedded in either new format is never surfaced, in any + mode. ([#49](https://github.com/ohstr/ncli/pull/49)) +- Every command's `--help` output now includes an onboarding `Example:` + field, including pure group commands. + ([#51](https://github.com/ohstr/ncli/pull/51)) + +### Changed + +- A local flow's `ensure` policy now defaults to `create` instead of + `exists` when omitted, so a missing local store path is created rather + than failing to load. + ([#50](https://github.com/ohstr/ncli/pull/50)) +- Trimmed redundant/noisy `Long` text and placeholder examples across + commands -- text already covered by a flag's own description no + longer repeats in the command's `Long`, and the generic `mylabel` + placeholder now reads `satoshi`. + ([#51](https://github.com/ohstr/ncli/pull/51)) +- Bumped `github.com/ohstr/nmilat` to v0.3.0 -- adds NIP-34/NIP-22 + support (consumed by the `decode` extension above), fixes + `TransferFromSources` reusing a stale wallet-bound client on its + second call, and fixes silently-dropped `circle_hub`/`circle_wallet` + fee fields on NWC unmarshal. + ([#51](https://github.com/ohstr/ncli/pull/51)) + +### Fixed + +- Two `Example:` commands that didn't actually run as written against a + real `ncli` binary. + ([#51](https://github.com/ohstr/ncli/pull/51)) + ## [0.4.9] ### Fixed