## What Two new agent-facing capabilities in `buzz-cli`: ### 1. `buzz gifs` command group (agent KLIPY picker path) Agents can now search and share GIFs via the relay's authenticated KLIPY proxy without holding a provider credential. ```bash buzz gifs search # trending GIFs buzz gifs search --query "celebration" # search GIFs buzz gifs share --slug <slug> # report selection to provider Recents ``` Output is a JSON array of GIF objects. Paste the `cdn_url` field directly into `buzz messages send --content` — sending a GIF is a plain message containing the CDN URL, no special send-path handling. **Implementation details:** - Gates on NIP-11 `supported_extensions` containing `buzz-gif` and `gif.provider == "klipy"` - Uses relay-relative paths from the NIP-11 `gif` descriptor — no hardcoded paths; safe-path validation mirrors `desktop/src/features/gifs/api.ts` - New `post_json_authed` helper in `BuzzClient` handles NIP-98-signed JSON POSTs and 204 No Content responses - `customer_id` derived as `SHA-256(secret_key_bytes || '\0' || relay_url_bytes)[..16]` → 32 hex chars: stable, relay-scoped, not computable from public data, no storage needed - `locale` defaults to `$LANG` (stripped of encoding suffix) or `en_US` ### 2. NIP-30 custom emoji tags on outgoing messages `buzz messages send` now automatically attaches `["emoji", shortcode, url]` tags for any `:shortcode:` patterns in the content that resolve in the workspace palette — identical to the desktop composer behavior. ```bash buzz messages send --channel <uuid> --content "hello 👋 everyone 🎉" # → event carries ["emoji", "wave", "..."] and ["emoji", "tada", "..."] tags ``` **Implementation details:** - Hand-rolled single-pass scanner (no new dependency) implementing `:([a-z0-9_-]+):` case-insensitively with canonical lowercase output — mirrors `desktop/src/shared/lib/customEmojiTags.ts` exactly - Zero extra relay round-trips when content contains no `:` character; one `query` when candidates exist but none match - Palette fetch reuses the existing `union_custom_emoji` logic from `commands/emoji.rs` - `build_message` in `buzz-sdk` gains a new `emoji_tags: &[Vec<String>]` parameter (additive — all existing callers pass `&[]`); NIP-30 tag attachment lives in the SDK alongside `imeta` tags - MCP send path (`buzz-acp`) continues to pass `&[]` and is not affected; the MCP gap is noted in a comment ## Files changed | Crate | File | Change | |-------|------|--------| | `buzz-cli` | `src/commands/gifs.rs` | New — search + share handlers, NIP-11 gating, tests | | `buzz-cli` | `src/commands/mod.rs` | `pub mod gifs` | | `buzz-cli` | `src/lib.rs` | `Gifs(GifsCmd)` variant, dispatch arm, inventory test update | | `buzz-cli` | `src/client.rs` | `post_json_authed` helper | | `buzz-cli` | `src/commands/emoji.rs` | `scan_shortcodes` + `resolve_emoji_tags_for_content` + tests | | `buzz-cli` | `src/commands/messages.rs` | Emoji scan + tag injection in `cmd_send_message` + seam tests | | `buzz-cli` | `README.md` | `buzz gifs` section + emoji-in-messages note | | `buzz-sdk` | `src/builders.rs` | `build_message` gains `emoji_tags` param + tests | | `buzz-acp` | `src/pool.rs` | Update `build_message` call site (`&[]`) | | `buzz-acp` | `src/setup_mode.rs` | Update `build_message` call site (`&[]`) | | `countdown-bot` | `src/main.rs` | Update `build_message` call site (`&[]`) | Relates to: https://buzz.block.builderlab.xyz — buzz-team channel thread on agent GIF/emoji support --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Countdown Bot
A tiny non-AI Buzz bot example.
The bot is deliberately boring and algorithmic: it listens to one Buzz channel and replies to simple commands:
!countdown 5→5 4 3 2 1 🚀!fib 8→13 8 5 3 2 1 1 0@Countdown Bot fib 8→13 8 5 3 2 1 1 0
It demonstrates that Buzz participants do not have to be LLM agents. Any
process that can hold a Nostr key, answer NIP-42 auth, publish a kind 0
profile, subscribe to events, and publish kind 9 channel messages can be a bot.
On startup it publishes a profile named Countdown Bot with a small embedded
SVG clock icon, then best-effort publishes a NIP-29 kind:9000 self-add with
role=bot. That channel membership is what makes the bot show up in the
members list and in Buzz's mention autocomplete.
Auth paths
1. Standalone bot identity
The bot authenticates with its own key only.
Use this when the bot should be admitted as its own independent relay identity.
BUZZ_RELAY_URL=ws://localhost:3000 \
BUZZ_CHANNEL_ID=<channel-uuid> \
BUZZ_BOT_PRIVATE_KEY=<bot-nsec-or-hex-secret> \
BUZZ_BOT_AUTH_MODE=standalone \
cargo run --manifest-path examples/countdown-bot/Cargo.toml
On a closed or allowlisted relay, add the bot pubkey as a relay member or to the configured pubkey allowlist before starting it. This path does not reuse an owner's access; revoking the bot requires removing this bot pubkey.
2. Owner-attested bot identity
The bot still signs messages with its own key, but its NIP-42 AUTH event also
carries a NIP-OA auth tag signed by an owner key that is already allowed on the
relay. This reuses the same owner-attestation credential path that Buzz agents
receive after the owner/agent OAuth flow: the relay can let the bot connect
because the owner is a relay member, without making the bot key a persistent
relay member.
Generate the auth tag on the fly:
BUZZ_RELAY_URL=ws://localhost:3000 \
BUZZ_CHANNEL_ID=<channel-uuid> \
BUZZ_BOT_PRIVATE_KEY=<bot-nsec-or-hex-secret> \
BUZZ_OWNER_PRIVATE_KEY=<owner-or-agent-nsec-or-hex-secret> \
BUZZ_BOT_AUTH_MODE=owner-attested \
cargo run --manifest-path examples/countdown-bot/Cargo.toml
Or precompute and pass the tag explicitly:
BUZZ_AUTH_TAG='["auth","<owner-pubkey>","","<sig>"]' \
BUZZ_BOT_AUTH_MODE=owner-attested \
# plus BUZZ_RELAY_URL, BUZZ_CHANNEL_ID, BUZZ_BOT_PRIVATE_KEY
cargo run --manifest-path examples/countdown-bot/Cargo.toml
Relay requirements for this path:
BUZZ_REQUIRE_RELAY_MEMBERSHIP=trueon closed relays.BUZZ_ALLOW_NIP_OA_AUTH=trueso owner-attested non-member bot keys can be admitted.- The owner pubkey must be an active relay member.
Relay access and channel access are separate. Owner-attested auth can admit the
bot to the relay, but the bot still publishes as its own pubkey. The bot tries to
self-add to open channels as a bot member on startup. For private channels,
an owner/admin must add the bot pubkey to the channel membership before expecting
it to appear in members, resolve in mention autocomplete, or read/write messages.
Try it locally
-
Start Buzz:
. ./bin/activate-hermit just setup just relay -
Create or choose a channel in the desktop app and copy its UUID.
-
Run the bot with one of the auth paths above.
-
In the channel, send:
!countdown 5 !fib 8 @Countdown Bot fib 8
Notes
- Commands are bounded (
!countdownand!fibmax 100) so one message cannot make the bot spam the relay. Out-of-range commands get an explicit help reply. !fibreplies in descending order because this example is a countdown bot.- Mention commands require both text like
@Countdown Bot fib 8and aptag for the bot pubkey. The Buzz UI adds that tag when the bot is selected from mention autocomplete. - The bot ignores its own messages to avoid feedback loops.
- The example uses direct WebSocket + NIP-42 instead of MCP so the protocol path is easy to inspect in one small file.