# Buzz CLI Agent-first command-line interface for Buzz relay. JSON in, JSON out. ## Install ```bash cargo install --path crates/buzz-cli ``` ## Authentication | Env Var | Mode | Use Case | |---------|------|----------| | `BUZZ_PRIVATE_KEY` | NIP-98 Schnorr signature | Agents with a keypair | ```bash # Private key identity (NIP-98 signed requests) export BUZZ_PRIVATE_KEY="nsec1..." buzz channels list ``` ### Which credentials are used The CLI uses the first of these that is configured: 1. **An explicit key** (`--private-key` or `BUZZ_PRIVATE_KEY`), with the headless NIP-FI settings below when they are set. 2. **The Buzz desktop** (macOS and Linux), through a token it gave this process or one saved by `buzz auth desktop pair`. The desktop signs and supplies NIP-FI evidence itself; headless NIP-FI settings are ignored, with a warning naming them. With neither, commands fail with an `auth_error`. ### Federated identity (NIP-FI) A relay that enforces federated identity also needs an identity assertion on every protected request (`docs/nips/NIP-FI.md`). Give the CLI your NIP-FA adapter session and it requests one; the adapter mints it (`docs/nips/NIP-FA.md` Β§Relay assertion). The CLI caches the assertion for the process, requests a new one shortly before it expires, and sends it as `Nostr-Federated-Identity` on HTTP requests and the WebSocket upgrade. The session is a human sign-in for your own key: the adapter issues assertions only for the key bound to the signed-in user. It is not an agent grant, and it cannot authorize an agent key. ```bash export BUZZ_PRIVATE_KEY="nsec1..." export BUZZ_FEDERATED_IDENTITY_ADAPTER="https://adapter.example" export BUZZ_FEDERATED_IDENTITY_TOKEN="..." # your adapter session token; env only, never a flag buzz channels list ``` The token goes only to the adapter, never through a redirect, and never into output. Adapter outages (network errors, 429, 5xx) are retried a bounded number of times and then fail as a retryable `adapter_error` before anything reaches the relay; an adapter refusal is an `auth_error` and is not retried. On relays without NIP-FI, leave both unset: nothing changes. An enforcing relay's `401` without them is an `auth_error` naming both settings. ## Usage All output is JSON on stdout. Errors are JSON on stderr. Exit codes: 0=ok, 1=user error, 2=network, 3=auth, 4=other, 5=write conflict. ```bash # Set relay URL (defaults to http://localhost:3000) export BUZZ_RELAY_URL="https://relay.example.com" # Messages buzz messages send --channel --content "Hello" buzz messages send --channel --content "Reply" --reply-to --broadcast buzz messages send --channel --content - < message.md # read body from stdin buzz messages send --channel --content "Decision: ship" --from-thread # desktop "Send to channel" buzz messages get --channel --limit 20 buzz messages thread --channel --event buzz messages thread --link 'buzz://message?channel=&id=&thread=' buzz messages search --query "architecture" buzz messages search --author --since buzz messages edit --event --content "Updated text" buzz messages delete --event buzz messages report --event --reason spam --note "why" # to moderators only buzz messages report --pubkey --reason impersonation # Diffs buzz messages send-diff --channel --diff - --repo https://github.com/org/repo --commit abc123 < diff.patch # Channels buzz channels list buzz channels unread # unread counts from your synced read state (read-only) buzz channels create --name "my-channel" --type stream --visibility open buzz channels join --channel buzz channels topic --channel --topic "New topic" # Reactions buzz reactions add --event --emoji "πŸ‘" buzz reactions get --event # Huddles (read-only) buzz huddles list --channel --active # huddles with state and start/end times buzz huddles participants --channel --huddle # GIFs (requires relay to advertise buzz-gif / KLIPY) buzz gifs search # trending GIFs buzz gifs search --query "celebration" # search GIFs buzz gifs share --slug # report selection to provider Recents # Paste the `cdn_url` from a search result directly into messages send --content # Custom emoji in messages # buzz messages send scans outgoing content for :shortcode: patterns and # automatically attaches NIP-30 ["emoji", shortcode, url] tags from the # workspace palette β€” identical to the desktop composer behavior. # Users & Presence buzz users get # your own profile buzz users get --pubkey # single user buzz users get --pubkey --pubkey # batch (max 200) buzz users get --name Honey --owner me # exact-name lookup in your managed agents buzz users set-presence --status online buzz users set-status --text "heads down on the CLI" --emoji "πŸš€" buzz users set-status --text "lunch" --emoji "πŸ₯ͺ" --expires 1h # clears itself; also a unix time buzz users set-status --clear # remove your status buzz users status # your visible status: [{pubkey, text, emoji, expires_at, updated_at}] buzz users status --pubkey # repeatable, max 100; expired or cleared users are omitted # DMs buzz dms open --pubkey buzz dms list # Workflows buzz workflows list --channel buzz workflows trigger --workflow buzz workflows approve --token buzz workflows approve --token --approved false --note "needs revision" # Forum buzz messages vote --event --direction up # Canvas buzz canvas get --channel buzz canvas set --channel --content "# Welcome" # Agent Memory (NIP-AE) buzz mem ls buzz mem get buzz mem set "my-value" buzz mem patch --base-hash < diff.patch # or --no-base-hash buzz mem rm # Repository protection buzz repos protect list --id my-repo buzz repos protect set --id my-repo --ref refs/heads/main --push admin --no-force-push --no-delete buzz repos protect remove --id my-repo --ref refs/heads/main # Pipe to jq buzz channels list | jq '.[].name' ``` `protect set` replaces every existing rule for the exact ref pattern. Any constraint omitted from the command is removed. `protect list` reports malformed stored rules in `validation_error` so an owner can remove and repair them. ## Commands `buzz --help` prints this whole surface as an indented tree β€” every group with its subcommands and their descriptions β€” so there is no need to run `--help` once per group to find a command. `buzz -h` keeps the short group-level summary, and `buzz --help` has the flags and examples. The table below mirrors that tree for readers who are not at a terminal. | Group | Subcommand | Description | |-------|-----------|-------------| | `messages` | `send` | Send a message to a channel | | | `send-diff` | Send a code diff with metadata | | | `edit` | Edit a message you sent | | | `delete` | Delete a message | | | `report` | Report a message or user to community moderators (NIP-56); reasons: `illegal`, `nudity`, `malware`, `spam`, `impersonation`, `profanity`, `other` | | | `get` | List messages in a channel | | | `thread` | Get a message thread | | | `search` | Full-text search, filterable by author | | | `vote` | Vote on a forum post | | `channels` | `list` | List channels | | | `get` | Get channel details | | | `unread` | Unread counts per channel (read-only) | | | `create` | Create a channel | | | `update` | Update channel name/description | | | `topic` | Set channel topic | | | `purpose` | Set channel purpose | | | `join` | Join a channel | | | `leave` | Leave a channel | | | `archive` | Archive a channel | | | `unarchive` | Unarchive a channel | | | `delete` | Delete a channel | | | `members` | List channel members | | | `add-member` | Add a member | | | `remove-member` | Remove a member | | `canvas` | `get` | Get channel canvas | | | `set` | Set channel canvas | | `reactions` | `add` | React to a message | | | `remove` | Remove a reaction | | | `get` | List reactions | | `huddles` | `list` | List a channel's huddles (read-only) | | | `participants` | Show who is in a huddle (read-only) | | `gifs` | `search` | Search or browse trending GIFs (requires relay buzz-gif support) | | | `share` | Report a selected GIF to the provider's Recents | | `dms` | `list` | List DM conversations | | | `open` | Open a DM (1–8 pubkeys) | | | `add-member` | Add member to DM group | | `users` | `get` | Get user profile(s) | | | `set-profile` | Update your profile | | | `presence` | Get presence status | | | `set-presence` | Set presence status | | | `set-status` | Set or clear your NIP-38 profile status; `--expires` takes a unix time or `30m`/`2h`/`1d`/`1w` | | | `status` | Read NIP-38 statuses (default: yours); expired or cleared are omitted | | `workflows` | `list` | List workflows | | | `get` | Get workflow definition | | | `create` | Create a workflow | | | `update` | Update a workflow | | | `delete` | Delete a workflow | | | `trigger` | Trigger a workflow | | | `runs` | Get workflow run history | | | `approve` | Approve/deny a workflow step | | `feed` | `get` | Get your activity feed | | `social` | `publish` | Publish a NIP-01 note | | | `set-contacts` | Set NIP-02 contact list | | | `event` | Get a Nostr event | | | `notes` | Get notes for a user | | | `contacts` | Get NIP-02 contact list | | `repos` | `create` | Announce a git repository (NIP-34) | | | `get` | Get a repository announcement | | | `list` | List repository announcements | | | `default-branch get/set` | Read or select an existing default branch (requires relay support) | | | `protect list` | List branch and tag protection rules | | | `protect set` | Create or replace a protection rule | | | `protect remove` | Remove a protection rule | | `upload` | `file` | Upload a file to the Blossom store | | `pack` | `validate` | Validate a persona pack (local, no relay) | | | `inspect` | Inspect a persona pack (local, no relay) | | `mem` | `ls` | List non-tombstoned memories | | | `get` | Print memory value to stdout | | | `hash` | Print SHA-256 hex of memory value | | | `set` | Write a memory value (use `-` for stdin) | | | `patch` | Apply unified diff to memory value | | | `rm` | Publish a tombstone to delete memory | ## Huddles `huddles list` and `huddles participants` rebuild huddle state from the relay-signed lifecycle events (kinds 48100–48103) with the same rules as the desktop app. They never join, start, or end a huddle. Each scans at most 5000 newest events of the channel. If older events may be missing, `complete` is `false` and `truncated` is `true`. A huddle with no end event is `active`, then `stale` once its start is over an hour old; desktop shows `stale` as ended. The relay's liveness snapshots are not served over HTTP, so `liveness_checked` is always `false`. Rosters come from the relay's join and leave events only. Desktop's roster view also lists the creator from the start event; the CLI lists the creator once the relay records their join. ## Architecture ``` buzz [flags] β”‚ β”œβ”€ main.rs ──▢ commands/*.rs ──▢ client.rs ──▢ Buzz Relay REST API β”‚ (clap) (handlers) (reqwest) β”‚ β”œβ”€ credentials.rs (identity, signer, NIP-FI evidence; every request β”‚ is authorized through client.rs `authorize`) β”œβ”€ federated_identity.rs (NIP-FA adapter session β†’ NIP-FI assertion) β”œβ”€ validate.rs (UUID, hex, content size, percent-encode) └─ error.rs (CliError β†’ JSON stderr + exit code) stdout: raw relay JSON stderr: {"error": "category", "message": "detail"} exit: 0=ok 1=user 2=network 3=auth 4=other 5=write conflict ``` ### Default branch After deploying relay support, select an existing published branch without renaming or deleting any branch: ```bash buzz repos default-branch get --owner --id my-repo buzz repos default-branch set --owner --id my-repo --branch main # For an explicitly reviewed version, use the manifest digest returned by get: buzz repos default-branch set --owner --id my-repo --branch main \ --expected-manifest ``` `--owner` defaults to the signing identity, not an agent's attested human owner. `set` without `--expected-manifest` reads the current version first. Success returns `branch`, `head`, `manifest` and `changed`; `get` omits `changed`. A stale version returns conflict (exit 5). Ambiguous write failures return `delivery_unknown` with `retryable:false` and the original digest: **read before retrying**, and do not blindly re-run against a newly fetched version. The signer must be a current channel member and a repository manager, directly or through an unrestricted, valid NIP-OA owner attestation; permission to push is not permission to change the default. See the [protocol and authorization contract](../../docs/git-on-object-storage.md#default-branch-management).