mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
## Context
`buzz users get --name Honey` searches relay-wide profiles and can
return an identically named agent owned by someone else. This caused
agents from the wrong owner to be added to a channel.
## Summary
This bug fix scopes exact-name agent lookup to owner-authored
managed-agent records, then cryptographically verifies each returned
profile's NIP-OA `auth` tag before asserting ownership. The relay and
database contracts remain unchanged.
### Related issue
None found.
## Changes
- Adds `buzz users get --name Honey --owner me|<hex>|<npub>`.
- Resolves `me` to the NIP-OA owner when the CLI runs as an agent,
otherwise to the CLI identity.
- Matches kind `30177` managed-agent record names exactly and
case-insensitively under the requested owner.
- Requires exactly one valid NIP-OA `auth` tag whose verified owner
equals the requested owner and whose `kind` and `created_at` conditions
apply to the profile event before returning `owner_pubkey` or
`owned_by_me: true`.
- Keeps missing, malformed, stale, condition-mismatched, or unverifiable
owner-record candidates visible with `owned_by_me: false` and an
explicit `verification` value.
- Returns every same-name record for the owner so callers can require
explicit selection when duplicates remain.
- Preserves the existing output shape and client-side name filter for
unscoped searches.
- Documents the distinct owner-scoped managed-agent lookup and unscoped
NIP-50 lookup modes.
### Testing
The reviewer-reproducible red and green commands below exercise the
ownership bug against the target branch and this branch.
## Screenshots
Not applicable. This is a CLI-only change.
## Reviewer-reproducible examples
The lookups below were run against the live relay from `main` and this
branch.
### Red: unscoped lookup returns the 100-profile relay-wide cap and
excludes John's agents
On `main`:
```bash
cargo run -q -p buzz-cli -- users get --name Honey \
| jq '{count: length, first_three: .[:3] | map(.pubkey), johns_agents: map(select(.pubkey == "31b29bcbe69d6716fbb7ba33602b89200bfc9ddfdabcfd1ea6fbfa70b816dfc7" or .pubkey == "4597ac725bba33fc7dd0454c1e2316a5ed770426acf667837d46f6553b3fcf54"))}'
```
Observed output:
```json
{
"count": 100,
"first_three": [
"20d27fc6c0ab4f50b66d1a32a64c5ca1fb985254143ce911f61ab7733333c3d7",
"00644478cdd9032c563ddc712b3687d8345d948945aab3c18bab95afbf6f519a",
"93c16697d0e58007bc11fb953208bc6b1cff387b2dee094abc10bf82dfee5424"
],
"johns_agents": []
}
```
`main` also rejects the owner-scoped command:
```bash
cargo run -q -p buzz-cli -- users get --name Honey --owner me
```
```text
error: unexpected argument '--owner' found
Usage: buzz users get --name <NAME>
```
### Green: owner-scoped lookup distinguishes verified and unresolved
records
On this branch:
```bash
cargo run -q -p buzz-cli -- users get --name Honey --owner me \
| jq 'map({pubkey,display_name,owner_pubkey,owned_by_me,verification})'
```
Observed output:
```json
[
{
"pubkey": "0ca77314d7ac8b3fcf6c647cc8cb9c3afd840db3b2a8ff2079f09a168de1827e",
"display_name": null,
"owner_pubkey": null,
"owned_by_me": false,
"verification": "missing_profile"
},
{
"pubkey": "31b29bcbe69d6716fbb7ba33602b89200bfc9ddfdabcfd1ea6fbfa70b816dfc7",
"display_name": "Honey",
"owner_pubkey": "67252b09c31a995daa63aada26569fbc6a3d12f573113f001ce7432f870da820",
"owned_by_me": true,
"verification": "verified"
},
{
"pubkey": "4597ac725bba33fc7dd0454c1e2316a5ed770426acf667837d46f6553b3fcf54",
"display_name": "Honey",
"owner_pubkey": "67252b09c31a995daa63aada26569fbc6a3d12f573113f001ce7432f870da820",
"owned_by_me": true,
"verification": "verified"
}
]
```
Only the two profiles with valid NIP-OA proofs assert ownership. The
owner-authored record whose profile is absent remains visible but cannot
be selected as verified ownership.
---------
Signed-off-by: npub1qye6rec0htgg3np8yt6plpyyg8cyffaq66emt3kmk05eylckkzhq0hnf2k <0133a1e70fbad088cc2722f41f848441f044a7a0d6b3b5c6dbb3e9927f16b0ae@buzz.block.builderlab.xyz>
Co-authored-by: npub1qye6rec0htgg3np8yt6plpyyg8cyffaq66emt3kmk05eylckkzhq0hnf2k <0133a1e70fbad088cc2722f41f848441f044a7a0d6b3b5c6dbb3e9927f16b0ae@buzz.block.builderlab.xyz>
185 lines
6.4 KiB
Markdown
185 lines
6.4 KiB
Markdown
# 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
|
||
```
|
||
|
||
## 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 <uuid> --content "Hello"
|
||
buzz messages send --channel <uuid> --content "Reply" --reply-to <event-id> --broadcast
|
||
buzz messages send --channel <uuid> --content - < message.md # read body from stdin
|
||
buzz messages get --channel <uuid> --limit 20
|
||
buzz messages thread --channel <uuid> --event <event-id>
|
||
buzz messages search --query "architecture"
|
||
buzz messages search --author <pubkey|npub|name> --since <unix-ts>
|
||
buzz messages edit --event <event-id> --content "Updated text"
|
||
buzz messages delete --event <event-id>
|
||
|
||
# Diffs
|
||
buzz messages send-diff --channel <uuid> --diff - --repo https://github.com/org/repo --commit abc123 < diff.patch
|
||
|
||
# Channels
|
||
buzz channels list
|
||
buzz channels create --name "my-channel" --type stream --visibility open
|
||
buzz channels join --channel <uuid>
|
||
buzz channels topic --channel <uuid> --topic "New topic"
|
||
|
||
# Reactions
|
||
buzz reactions add --event <event-id> --emoji "👍"
|
||
buzz reactions get --event <event-id>
|
||
|
||
# Users & Presence
|
||
buzz users get # your own profile
|
||
buzz users get --pubkey <hex> # single user
|
||
buzz users get --pubkey <hex> --pubkey <hex> # 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 --clear # remove your status
|
||
|
||
# DMs
|
||
buzz dms open --pubkey <hex>
|
||
buzz dms list
|
||
|
||
# Workflows
|
||
buzz workflows list --channel <uuid>
|
||
buzz workflows trigger --workflow <uuid>
|
||
buzz workflows approve --token <uuid>
|
||
buzz workflows approve --token <uuid> --approved false --note "needs revision"
|
||
|
||
# Forum
|
||
buzz messages vote --event <event-id> --direction up
|
||
|
||
# Canvas
|
||
buzz canvas get --channel <uuid>
|
||
buzz canvas set --channel <uuid> --content "# Welcome"
|
||
|
||
# Agent Memory (NIP-AE)
|
||
buzz mem ls
|
||
buzz mem get <slug>
|
||
buzz mem set <slug> "my-value"
|
||
buzz mem patch <slug> --base-hash <hex> < diff.patch # or --no-base-hash
|
||
buzz mem rm <slug>
|
||
|
||
# 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
|
||
|
||
| 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 |
|
||
| | `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 |
|
||
| | `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 |
|
||
| `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 |
|
||
| `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 |
|
||
| | `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 |
|
||
|
||
## Architecture
|
||
|
||
```
|
||
buzz <group> <subcommand> [flags]
|
||
│
|
||
├─ main.rs ──▶ commands/*.rs ──▶ client.rs ──▶ Buzz Relay REST API
|
||
│ (clap) (handlers) (reqwest)
|
||
│
|
||
├─ 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
|
||
```
|