mirror of
https://github.com/block/buzz.git
synced 2026-10-31 22:41:12 +00:00
`buzz messages thread` and `buzz messages get` are both capped (500 and
200) with no way to page past the ceiling, so any thread or channel
larger than its cap is unreadable from the CLI. Both relay-side cursors
already exist and are fully implemented; neither flag was ever added.
## messages thread: --after / --after-id
`get_thread_replies` takes a composite `(created_at, event_id)` keyset
cursor (buzz-db/src/thread.rs:334-343) and the bridge parses it from
`thread_cursor`/`thread_cursor_id` (bridge.rs:1183) — the CLI never sent
the field, so a 265-reply thread ends at the 500-reply wall with no
escape. The wire names are unchanged; the flags are spelled `--after`
because this cursor pages FORWARD (`event_created_at > $n`), so the
house `--before` spelling from `social notes` would name the wrong
direction. Shape and both-or-neither grammar follow that precedent.
The relay reads the cursor only on the depth-limited code path — a
filter without `depth_limit` routes to the generic catch-all, which has
no cursor. Rather than touch that routing guard (removing it would
silently re-anchor every existing caller), a cursor supplied without
`--depth-limit` sends a depth sentinel meaning "no effective bound".
The sentinel is `i32::MAX`, not `u32::MAX`: buzz-db binds depth as i32
(thread.rs:459), so anything above `i32::MAX` wraps negative and matches
zero replies. Measured live: `--depth-limit 2147483648` returns the root
alone, `2147483647` returns the full thread.
## messages get: --before-id
`--before` maps to `until`, which is INCLUSIVE (`created_at <= $u`,
event.rs:505). `before_id` upgrades it to the exclusive keyset branch
(event.rs:493-499) and the bridge has parsed it, with a both-or-neither
grammar and a BAD_REQUEST on malformed input, since bridge.rs:263 — the
doc comment at :424 names the dense-second bug it exists to kill. The
flag did not exist.
## Why the tiebreak is not optional
The two paths have OPPOSITE cursor polarity, so they fail differently
and no timestamp-only rule covers both:
backward (get) `created_at <= $u` INCLUSIVE event.rs:505
forward (thread) `event_created_at > $n` STRICT thread.rs:443
Measured at this base. Backward, a cap binding inside a second shared by
several messages stalls or drops depending on loop shape. Forward, a page
boundary landing inside a shared second skips the rest of that second
outright: on a 44-reply thread with one 2-multiplicity second, the
timestamp-only walk drops a reply at caps {1, 2, 11, 22} and succeeds at
the other twenty — non-monotonic, because the hazard is where the
boundary lands, not the cap's size. Both failures are silent, rc=0.
With `(ts, id)` the tiebreak travels in the cursor: recovery no longer
depends on loop shape at all. That is what these flags buy — not just a
higher ceiling.
## Tests and help
15 new unit tests over two extracted pure filter builders, covering the
sentinel, its i32 representability, explicit-depth precedence, the
zero-cursor seed, and both-or-neither on each surface. Mutation-checked:
dropping the `before_id` write kills two of them and degrades the live
composite arm to the same drop as timestamp-only paging.
`--help` on both commands now states the default and cap, which end of
the range you get, that the thread cursor is strictly-after and needs
`--after 0` to seed a forward walk, and why to always pass the tiebreak.
Co-authored-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz>
Signed-off-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz>
Buzz CLI
Agent-first command-line interface for Buzz relay. JSON in, JSON out.
Install
cargo install --path crates/buzz-cli
Authentication
| Env Var | Mode | Use Case |
|---|---|---|
BUZZ_PRIVATE_KEY |
NIP-98 Schnorr signature | Agents with a keypair |
# 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.
# 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