mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Personas could only be created by clicking through Buzz Desktop, so nothing scriptable could stand up an agent roster — including the agents themselves. `buzz personas create|list|get|delete` writes the same kind:30175 coordinates Desktop reads, from a flag set or straight from a `.agent.json` export. These are owner-authored events, so the signing key IS the owner: no NIP-OA auth tag is involved, and running with a different key publishes to a coordinate space the owner's Desktop never reads. Publishing a definition does not start an agent; launching one mints key material and stays a Desktop operation. Two relay behaviors shape the implementation: - A write must be stamped past the coordinate's current head. NIP-33 breaks a `created_at` tie by lowest event id, so a same-second rewrite can otherwise lose to the event it was replacing. - `soft_delete_by_coordinate` matches `created_at <= tombstone`, and its result only feeds a debug log. A tombstone older than its target is accepted, deletes nothing, and reports OK — so delete stamps from the head it just read and then re-reads the coordinate to confirm. The re-read only raises a conflict for a head strictly newer than the tombstone, since a lagging replica can still return the deleted head. `--from` refuses rather than repairs a snapshot that would publish a persona Desktop can't mint from: `respondTo=allowlist` with no pubkeys, an unknown `respondTo`, or definition text carrying invisible characters. Avatars follow Desktop's reader rather than a CLI-only rule. `--avatar` takes a local image and carries it inline as a data URL when it fits the bounds Desktop renders — 8 KiB for SVG, 256 KiB for raster, both inside the relay's 256 KiB content cap. Anything larger is downscaled to 512px and re-encoded, then re-checked against the bound: flat art typically lands back inside it and never reaches media storage at all, and only what still doesn't fit is uploaded. Re-encoding is also what makes that upload work. Media storage refuses images carrying metadata — EXIF, colour profiles, comments — as an identity channel, so a photo straight off a camera failed outright. Decoding and re-encoding drops metadata by construction rather than by stripping known chunks, so it cannot drift from the relay's allowlist the way a structural stripper would. EXIF orientation is baked into the pixels first, because dropping the tag without applying it publishes the avatar sideways. GIF passes through untouched, since re-encoding would flatten animation, and WebP re-encodes to PNG because `image`'s WebP encoder is lossless-only and would inflate a lossy source. `--from` carries a snapshot's inlined avatar through that same path, so a Desktop export round-trips with its image. The upload runs after the `--replace` conflict check: an upload that a rejected write would strand leaves an orphan blob behind. Signed-off-by: Max Lampert <maxwell@squareup.com>
105 lines
3.3 KiB
TOML
105 lines
3.3 KiB
TOML
[package]
|
|
name = "buzz-cli"
|
|
version.workspace = true
|
|
edition.workspace = true
|
|
rust-version.workspace = true
|
|
license.workspace = true
|
|
repository.workspace = true
|
|
description = "Agent-first CLI for Buzz relay"
|
|
|
|
[lib]
|
|
name = "buzz_cli"
|
|
path = "src/lib.rs"
|
|
|
|
[[bin]]
|
|
name = "buzz"
|
|
path = "src/main.rs"
|
|
|
|
[dependencies]
|
|
# CLI argument parsing — derive macros + env var support (BUZZ_API_TOKEN auto-wired)
|
|
clap = { version = "4", features = ["derive", "env"] }
|
|
|
|
# HTTP client — async REST calls to the relay
|
|
reqwest = { workspace = true, features = ["json"] }
|
|
|
|
# Async runtime — tokio macros + multi-thread for reqwest
|
|
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
|
|
|
|
# Serialization — JSON body building and response passthrough
|
|
serde = { workspace = true }
|
|
serde_json = { workspace = true }
|
|
|
|
# Structured error types with exit code mapping
|
|
thiserror = { workspace = true }
|
|
|
|
# Nostr event signing — used in `buzz auth`, auto-mint, and signed event writes
|
|
nostr = { workspace = true }
|
|
|
|
# UUID parsing for validate_uuid + event building
|
|
uuid = { workspace = true }
|
|
|
|
# RFC3339 observer timestamps for owner-reviewed agent draft requests
|
|
chrono = { workspace = true }
|
|
|
|
# Typed event builders for all write operations
|
|
buzz-sdk = { workspace = true }
|
|
buzz-core = { workspace = true }
|
|
|
|
# Base64 encoding — NIP-98 event serialization for Authorization header
|
|
base64 = "0.22"
|
|
|
|
# SHA-256 — NIP-98 payload hash tag, Blossom file hash, mem patch base-hash
|
|
sha2 = "0.11"
|
|
|
|
# Unified-diff parser and strict applier — `mem patch`
|
|
diffy = "0.5"
|
|
|
|
# Hex encoding — SHA-256 hash output for Blossom uploads
|
|
hex = { workspace = true }
|
|
|
|
# Byte buffers returned by authenticated media downloads
|
|
bytes = "1"
|
|
|
|
# MIME type detection via magic bytes — file upload validation
|
|
infer = "0.19"
|
|
|
|
# Avatar normalization — decode/downscale/re-encode drops image metadata by
|
|
# construction, which media storage requires. Same version and feature set as
|
|
# buzz-media and desktop; depending on buzz-media instead would pull rust-s3,
|
|
# axum, and mp4 into this binary.
|
|
image = { version = "0.25", default-features = false, features = [
|
|
"jpeg",
|
|
"png",
|
|
"gif",
|
|
"webp",
|
|
] }
|
|
|
|
# URL parsing — extract server domain for Blossom auth tag
|
|
url = { workspace = true }
|
|
|
|
# Persona pack parsing, validation, and resolution
|
|
buzz-persona = { path = "../buzz-persona" }
|
|
|
|
# Platform app-data dir resolution — locates the desktop app's
|
|
# channel-templates.json store for `channels create --template`
|
|
dirs = "6"
|
|
|
|
# WebSocket client — ephemeral event publish (kind:20001 is WS-only on the relay)
|
|
buzz-ws-client = { path = "../buzz-ws-client" }
|
|
|
|
# Explicit rustls dep with ring provider — required to install the process-level
|
|
# CryptoProvider at startup. Without this the standalone `buzz` binary panics when
|
|
# a multi-package release build (buzz-acp + buzz-dev-mcp + buzz-cli in one cargo
|
|
# invocation) unifies both ring and aws-lc-rs features, leaving rustls unable to
|
|
# auto-select a provider. See crates/buzz-acp/Cargo.toml for the same dependency.
|
|
rustls = { version = "0.23", default-features = false, features = ["ring", "std"] }
|
|
|
|
# Random number generation — full jitter for exponential backoff in with_retry
|
|
rand = { workspace = true }
|
|
|
|
[dev-dependencies]
|
|
# Scratch files for channel-templates.json fixtures in tests
|
|
tempfile = "3"
|
|
# Minimal HTTP test server for retry/policy integration tests
|
|
axum = { workspace = true }
|