Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,22 @@
unchanged, so a bare group command still exits 2, never 0. `--json` is
untouched: one structured line, never help. (#55)
- Updated `nmilat` to v0.4.0.
- Rewrote every command's `--help` description in a flatter style: each
one now states what the command does, with the rules a caller can't
guess stated plainly, instead of explaining the reasoning behind it.
(#61)

### Fixed

- An unknown flag was reported twice (cobra's own `Error:` plus ncli's
own line) and exited 1 instead of 2. (#55)
- `ncli bunker sessions revoke-grant` with no `--method` exited 1 as
`internal` instead of 2 as `usage`. (#55)
- `ncli relay` with no config reported three alternatives crammed into
one line and no help. It now prints a short error followed by the
command's help, which lists the flags and where the config is read
from. Same for the `relay` admin subcommands missing `nip11.privkey`.
(#61)
- `ncli relay` could freeze until restarted: a `REQ` held its database
read open while sending events, so a write that grew the database file
hung every other `REQ` and `EVENT`, health checks included. Fixed
Expand Down
9 changes: 4 additions & 5 deletions cli/blossom/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@ import (
func NewBlossomCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "blossom",
Short: "Upload, fetch, and manage content on Blossom media servers",
Short: "Manage content on Blossom media servers",
Long: `A client for the Blossom protocol (BUD-01..12): content-addressed blob
storage authenticated with a Nostr identity instead of a login.
storage authenticated with a Nostr identity.

Writes (upload, rm, mirror) fan out to every configured server and exit
non-zero if any one failed; download tries them in order until one
answers.`,
upload, rm and mirror write to every configured server and exit non-zero
if any fails. download reads from them in order until one answers.`,
Example: ` ncli blossom upload ./photo.jpg --identity satoshi
ncli blossom download <hash> -o photo.jpg
ncli blossom servers list`,
Expand Down
10 changes: 5 additions & 5 deletions cli/blossom/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ func newDownloadCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "download <hash|blossom-uri|url>",
Short: "Download a blob by hash, blossom: URI, or server URL",
Long: `Accepts a bare sha256 hash, a "blossom:<hash>.<ext>" URI (BUD-10), or a
server URL ending in a hash -- tries the configured servers in order
(--server, or the default list), stopping at the first that answers.
Long: `Download a blob, given a bare sha256 hash, a "blossom:<hash>.<ext>" URI
(BUD-10), or a server URL ending in a hash. Servers are tried in order
until one answers.

Writes to --output, or "<hash>.<ext>" in the current directory if
omitted, or streams to stdout with "-o -" (suppressing the summary line).`,
Writes to --output, or to "<hash>.<ext>" in the current directory. Use
"-o -" to stream to stdout.`,
Example: ` ncli blossom download <hash>
ncli blossom download <hash> -o -`,
Args: common.ExactArgs(1),
Expand Down
6 changes: 3 additions & 3 deletions cli/blossom/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ func newListCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "list [identifier]",
Short: "List blobs stored under a pubkey",
Long: `List the blobs one pubkey has stored, on the first configured server or
on every one with --all, merged and deduped by hash.
Long: `List the blobs a pubkey has stored on the first configured server, or on
every server with --all, merged and deduplicated by hash.

identifier accepts a vault label, npub, hex pubkey, nprofile or nip-05
address, and defaults to --identity's pubkey when omitted.`,
address, and defaults to --identity's pubkey.`,
Example: ` ncli blossom list --identity satoshi
ncli blossom list --identity satoshi --all
ncli blossom list name@example.com`,
Expand Down
8 changes: 3 additions & 5 deletions cli/blossom/mirror.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ import (
func newMirrorCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "mirror <source-url>",
Short: "Mirror a blob from a URL onto your Blossom server(s)",
Long: `Sign a BUD-11 authorization and PUT /mirror to every target server
(--server, or the configured default list) -- each server fetches
source-url itself; no bytes pass through ncli. Reports a result per
server.`,
Short: "Mirror a blob from a URL to your Blossom servers",
Long: `Ask every target server to fetch and store a blob from source-url. Each
server downloads it directly; no bytes pass through ncli.`,
Example: ` ncli blossom mirror https://example.com/file.jpg --identity satoshi`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
Expand Down
7 changes: 3 additions & 4 deletions cli/blossom/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ import (
func newReportCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "report <hash>",
Short: "Report a blob to a Blossom server (BUD-09)",
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.`,
Short: "Report a blob to a Blossom server",
Long: `Submit a signed kind:1984 report event for a blob (BUD-09). Targets a
single server: --server, or the first configured one.`,
Example: ` ncli blossom report <hash> --identity satoshi`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
Expand Down
7 changes: 3 additions & 4 deletions cli/blossom/rm.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ func newRmCommand() *cobra.Command {

cmd := &cobra.Command{
Use: "rm <hash>",
Short: "Delete a blob from your Blossom server(s)",
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.`,
Short: "Delete a blob from your Blossom servers",
Long: `Delete a blob from every target server. Requires --yes in a
non-interactive session.`,
Example: ` ncli blossom rm <hash> --identity satoshi --yes`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
Expand Down
16 changes: 7 additions & 9 deletions cli/blossom/servers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ 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.`,
Long: `Manage the server list used when a command is given no --server flag.`,
Example: ` ncli blossom servers list`,
RunE: common.RequireSubcommand,
}
Expand Down Expand Up @@ -267,14 +267,12 @@ func newServersDiscoverCommand() *cobra.Command {

cmd := &cobra.Command{
Use: "discover <identifier>",
Short: "Discover another identity's published server list (BUD-03)",
Long: `Resolves <identifier> (vault label/nsec/npub/hex pubkey/nprofile/nip-05)
to a pubkey, then queries your configured Nostr relays for that pubkey's
most recent kind:10063 server-list event, and prints the servers it
declares.

Unlike "servers add/remove/list", which manage your own default list,
this looks up someone else's published servers.`,
Short: "Show another identity's published server list",
Long: `Print the Blossom servers another identity has published, from their
most recent kind:10063 event (BUD-03).

identifier accepts a vault label, npub, hex pubkey, nprofile or nip-05
address.`,
Example: ` ncli blossom servers discover npub1...`,
Args: common.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
Expand Down
11 changes: 5 additions & 6 deletions cli/blossom/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ func newUploadCommand() *cobra.Command {

cmd := &cobra.Command{
Use: "upload <file> [file...]",
Short: "Upload one or more files to your Blossom server(s)",
Long: `Sign a BUD-11 authorization and PUT each file to every target server
(--server, or the configured default list), reporting a result per
(file, server) pair. Exits non-zero if any pair failed.
Short: "Upload files to your Blossom servers",
Long: `Upload each file to every target server, reporting a result per (file,
server) pair. Exits non-zero if any pair fails.

Pass --optimize to request server-side transcoding/optimization (BUD-05's
PUT /media) instead of a byte-for-byte store.`,
--optimize requests server-side transcoding (BUD-05) instead of storing
the bytes as-is.`,
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`,
Expand Down
34 changes: 15 additions & 19 deletions cli/bunker/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,11 @@ func NewBunkerCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "bunker",
Short: "Run ncli as a NIP-46 remote signer",
Long: `Run ncli as a NIP-46 "bunker": listen on relays for other clients'
signing requests, approve or reject them from a live TUI, and remember
per-app decisions so you aren't re-prompted every time.
Long: `Listen on relays for other clients' NIP-46 signing requests and approve
or reject them from a TUI. Per-app decisions are remembered.

On Linux/macOS this leaves a background daemon running when the TUI
closes; reattach with "ncli bunker attach".`,
On Linux and macOS a background daemon keeps running when the TUI
closes. Reattach with "ncli bunker attach".`,
Example: ` ncli bunker
ncli bunker --identity satoshi
ncli bunker attach`,
Expand Down Expand Up @@ -92,9 +91,8 @@ func newAttachCommand() *cobra.Command {
return &cobra.Command{
Use: "attach",
Short: "Reattach the TUI to a running bunker daemon",
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).`,
Long: `Reconnect the TUI to a bunker daemon already running in the background.
Never starts one; fails if none is running.`,
Example: ` ncli bunker attach`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := requireInteractive(cmd); err != nil {
Expand Down Expand Up @@ -350,7 +348,7 @@ func newSessionsCommand() *cobra.Command {

cmd.AddCommand(&cobra.Command{
Use: "grants <pubkey>",
Short: "List one trusted app's remembered permissions individually",
Short: "List one app's remembered permissions",
Example: ` ncli bunker sessions grants <pubkey>`,
Args: common.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -445,7 +443,7 @@ func newSessionsCommand() *cobra.Command {
func newHistoryCommand() *cobra.Command {
return &cobra.Command{
Use: "history",
Short: "List recently resolved requests (approved/rejected/expired)",
Short: "List recently resolved signing requests",
Example: ` ncli bunker history`,
RunE: func(cmd *cobra.Command, args []string) error {
jsonMode, _ := cmd.Flags().GetBool("json")
Expand Down Expand Up @@ -502,15 +500,13 @@ func newHistoryCommand() *cobra.Command {
func newConnectCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "connect [nostrconnect-uri]",
Short: "Start a pairing with a running bunker daemon",
Long: `With no argument, generates and prints a fresh bunker:// URI for another
Nostr app to connect to. Given a nostrconnect:// URI, initiates that
pairing instead, blocking until the client confirms or it times out.

--grants <file> pre-authorizes the app that completes this pairing with a
declared set of permissions (see examples/bunker/ for the YAML shape),
instead of prompting interactively on first use. "ncli bunker sessions
grants <pubkey>" shows what actually landed once paired.`,
Short: "Pair an app with a running bunker daemon",
Long: `Print a fresh bunker:// URI for another Nostr app to connect to. Given a
nostrconnect:// URI, start that pairing instead and block until the
client confirms or it times out.

--grants pre-authorizes the paired app from a YAML permission file
instead of prompting on first use.`,
Example: ` ncli bunker connect
ncli bunker connect nostrconnect://...
ncli bunker connect --grants grants.yaml`,
Expand Down
13 changes: 5 additions & 8 deletions cli/delegate/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,11 @@ func NewDelegateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "delegate",
Short: "Generate a NIP-26 delegation token",
Long: `Launch an interactive wizard that creates and signs NIP-26 delegation
tokens. With --issuer set (via flag or NCLI_DELEGATE_ISSUER), skips the
wizard and generates the token non-interactively instead.

--issuer and --delegatee both accept a vault label, nsec, npub, hex
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.`,
Long: `Create and sign a NIP-26 delegation token. Runs an interactive wizard
unless --issuer is set, via the flag or NCLI_DELEGATE_ISSUER.

--issuer and --delegatee accept a vault label, nsec, npub, hex pubkey,
nprofile or nip-05 address, and must resolve to a private key.`,
Example: ` ncli id delegate
ncli id delegate --issuer satoshi --delegatee npub1... --kinds 1`,
RunE: func(cmd *cobra.Command, args []string) error {
Expand Down
2 changes: 1 addition & 1 deletion cli/ncli/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var (

applyCmd = &cobra.Command{
Use: "apply",
Short: "Run a client workflow from a config file",
Short: "Run a stream, sync, or inspect workflow",
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`,
Expand Down
8 changes: 4 additions & 4 deletions cli/ncli/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import (
var decodeCmd = &cobra.Command{
Use: "decode <entity>",
Short: "Decode a NIP-19 entity, cash token, or hub connection",
Long: `Decodes whichever bech32 shape you paste in -- a NIP-19 entity (npub,
nsec, note, nprofile, nevent, naddr), a NIP-CASH cash token, or a NIP-CW
circlehub1... connection -- into its hex keys, relay hints and metadata.
Long: `Decode a bech32 string -- a NIP-19 entity (npub, nsec, note, nprofile,
nevent, naddr), a NIP-CASH cash token, or a NIP-CW circlehub1...
connection -- into its hex keys, relay hints and metadata.

A pairing secret is never included in the output.`,
Pairing secrets are never printed.`,
Example: ` ncli decode npub1...
ncli decode nevent1...
ncli decode npub1... --json`,
Expand Down
7 changes: 3 additions & 4 deletions cli/ncli/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ var dumpCmd = &cobra.Command{
Use: "dump",
Short: "Export events to JSON",
Long: `Export events matching a filter to a JSON file, merged and deduplicated
by event ID across every target.
by event ID across all targets.

Targets and filters come from --targets, or --relays plus inline filter
flags -- pick one, not both. Omitting both falls back to "ncli prefs
relays".`,
--targets cannot be combined with --relays or the inline filter flags.
Omit both to use the relays from "ncli prefs relays".`,
Example: ` ncli dump -o events.json
ncli dump -t targets.yaml -o events.json
ncli dump -s wss://relay.example.com -k 1 --since 24h -o recent.json`,
Expand Down
11 changes: 5 additions & 6 deletions cli/ncli/find.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@ var (
findCmd = &cobra.Command{
Use: "find [identifier]",
Short: "Query events by ID and/or filter",
Long: `Look up events by ID and/or filter across relays or local stores,
stopping at the first target with a match. An npub or nip-05 identifier
defaults to that author's profile (kind 0); pass --kinds to widen it.
Long: `Query events across relays and local stores, stopping at the first
target with a match. An npub or nip-05 identifier returns that author's
profile unless --kinds widens it. Prints a single JSON array.

Targets and filters come from --targets, or --relays plus inline filter
flags -- pick one, not both. Omitting both falls back to "ncli prefs
relays". Always prints a single JSON array to stdout.`,
--targets cannot be combined with --relays or the inline filter flags.
Omit both to use the relays from "ncli prefs relays".`,
Example: ` ncli find note1...
ncli find npub1...
ncli find --authors npub1... --kinds 1 -s wss://relay.example.com`,
Expand Down
5 changes: 2 additions & 3 deletions cli/ncli/id.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@ import (
var idCmd = &cobra.Command{
Use: "id [identifier]",
Short: "Generate or inspect a Nostr identity",
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.
Long: `Generate a new keypair, or resolve and display an existing identity
given a vault label, npub, hex pubkey, nsec, nprofile or nip-05 address.

--json disables interactive prompts and reads the vault password from
NCLI_VAULT_PASSWORD.`,
Expand Down
12 changes: 4 additions & 8 deletions cli/ncli/id_sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,11 @@ 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 --identity's private key.
Short: "Sign unsigned events with a Nostr identity",
Long: `Sign an unsigned event, or an array of them, with --identity's private
key. --out is written in the shape it was read.

--events accepts a single event or an array; --out is written in the same
shape, so it chains directly into "ncli publish --events <out>" or
"ncli miner check --events <out>".

Fails if an event already declares a pubkey that conflicts with
--identity's resolved pubkey, rather than re-signing under a different key.`,
Fails if an event already declares a different pubkey.`,
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 {
Expand Down
15 changes: 7 additions & 8 deletions cli/ncli/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ var minerMineCmd = &cobra.Command{
Short: "Mine proof-of-work into an unsigned event",
Long: `Mine NIP-13 proof-of-work for an event across multiple CPU cores.

The event comes from --event, or inline from --content/--content-file --
pick one, not both. Exactly one of --out or --in-place says where the
result goes. If --identity resolves to a private key, the mined event is
signed before it's written.`,
The event comes from --event, or inline from --content/--content-file;
these cannot be combined. Exactly one of --out or --in-place is
required. A mined event is signed if --identity resolves to a private
key.`,
Example: ` ncli miner mine -e event.json -o mined.json
ncli miner mine -e event.json --in-place --workers 4
ncli miner mine --content "hello" --identity satoshi -d 20 -o mined.json`,
Expand Down Expand Up @@ -266,11 +266,10 @@ var minerCheckCmd = &cobra.Command{
Use: "check",
Short: "Verify proof-of-work",
Long: `Verify NIP-13 proof-of-work on already-mined events, read from --events
or fetched live across every target. Exits non-zero if any event fails,
so it drops straight into CI.
or fetched live from every target. Exits non-zero if any event fails.

Live mode takes --targets, or --relays plus inline filter flags -- pick
one, not both. Omitting both falls back to "ncli prefs relays".`,
--targets cannot be combined with --relays or the inline filter flags.
Omit both to use the relays from "ncli prefs relays".`,
Example: ` ncli miner check -e events.json
ncli miner check -t targets.yaml`,
Args: func(cmd *cobra.Command, args []string) error {
Expand Down
10 changes: 4 additions & 6 deletions cli/ncli/ping.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@ import (
var pingCmd = &cobra.Command{
Use: "ping [relay...]",
Short: "Test relay connectivity",
Long: `Probe each target relay with a Limit-1 subscription. Exits non-zero if
any relay was unreachable -- unlike find/dump, which tolerate a dead
target, reachability is the whole point here.
Long: `Probe each relay with a Limit-1 subscription. Local store paths are
skipped. Exits non-zero if any relay is unreachable.

Give relays as positional arguments, or --targets -- pick one, not both.
Omitting both falls back to "ncli prefs relays". --tui shows a live
board instead of log lines.`,
--targets cannot be combined with relay arguments. Omit both to use the
relays from "ncli prefs relays". --tui shows a live board.`,
Example: ` ncli ping wss://relay.example.com
ncli ping -t targets.yaml
ncli ping --tui wss://relay.example.com`,
Expand Down
Loading
Loading