diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 19754fd23..b0b727e03 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1,2 @@ -* @block/sprout-oss-team \ No newline at end of file +# TODO: verify @block/buzz-oss-team exists in GitHub org +* @block/buzz-oss-team diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 597979f98..ed5edbac4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -262,37 +262,37 @@ jobs: docker logs "${container}" || true return 1 } - wait_healthy "Postgres" "sprout-postgres" - wait_healthy "Redis" "sprout-redis" - wait_healthy "Typesense" "sprout-typesense" - wait_healthy "MinIO" "sprout-minio" + wait_healthy "Postgres" "buzz-postgres" + wait_healthy "Redis" "buzz-redis" + wait_healthy "Typesense" "buzz-typesense" + wait_healthy "MinIO" "buzz-minio" - name: Apply database schema run: ./bin/pgschema apply --file schema/schema.sql --auto-approve env: PGHOST: localhost PGPORT: "5432" - PGUSER: sprout - PGPASSWORD: sprout_dev - PGDATABASE: sprout + PGUSER: buzz + PGPASSWORD: buzz_dev + PGDATABASE: buzz - name: Build relay - run: cargo build --profile ci -p sprout-relay + run: cargo build --profile ci -p buzz-relay - name: Start relay run: | nohup env \ - DATABASE_URL=postgres://sprout:sprout_dev@localhost:5432/sprout \ + DATABASE_URL=postgres://buzz:buzz_dev@localhost:5432/buzz \ REDIS_URL=redis://localhost:6379 \ TYPESENSE_URL=http://localhost:8108 \ - TYPESENSE_API_KEY=sprout_dev_key \ + TYPESENSE_API_KEY=buzz_dev_key \ RELAY_URL=ws://localhost:3000 \ - SPROUT_BIND_ADDR=0.0.0.0:3000 \ - SPROUT_REQUIRE_AUTH_TOKEN=false \ - SPROUT_RECONCILE_CHANNELS=true \ - SPROUT_GIT_PROBE_WRITERS=8 \ - ./target/ci/sprout-relay > /tmp/sprout-relay.log 2>&1 & - echo $! > /tmp/sprout-relay.pid + BUZZ_BIND_ADDR=0.0.0.0:3000 \ + BUZZ_REQUIRE_AUTH_TOKEN=false \ + BUZZ_RECONCILE_CHANNELS=true \ + BUZZ_GIT_PROBE_WRITERS=8 \ + ./target/ci/buzz-relay > /tmp/buzz-relay.log 2>&1 & + echo $! > /tmp/buzz-relay.pid for attempt in $(seq 1 60); do - if ! kill -0 "$(cat /tmp/sprout-relay.pid)" 2>/dev/null; then - cat /tmp/sprout-relay.log + if ! kill -0 "$(cat /tmp/buzz-relay.pid)" 2>/dev/null; then + cat /tmp/buzz-relay.log exit 1 fi status_code=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3000/_readiness || true) @@ -301,7 +301,7 @@ jobs: fi sleep 1 done - cat /tmp/sprout-relay.log + cat /tmp/buzz-relay.log exit 1 - name: Seed desktop e2e data run: bash scripts/setup-desktop-test-data.sh @@ -315,7 +315,7 @@ jobs: path: | desktop/playwright-report desktop/test-results - /tmp/sprout-relay.log + /tmp/buzz-relay.log if-no-files-found: ignore - name: Save pnpm store cache if: github.event_name == 'push' @@ -444,10 +444,10 @@ jobs: TARGET: ${{ matrix.target }} run: | cross build --release --target "$TARGET" \ - -p sprout-relay \ - -p sprout-acp \ - -p sprout-agent \ - -p sprout-dev-mcp \ + -p buzz-relay \ + -p buzz-acp \ + -p buzz-agent \ + -p buzz-dev-mcp \ -p git-credential-nostr \ -p git-sign-nostr @@ -472,11 +472,11 @@ jobs: run: | TARGET=$(rustc -vV | sed -n 's|host: ||p') mkdir -p desktop/src-tauri/binaries - touch "desktop/src-tauri/binaries/sprout-acp-$TARGET" - touch "desktop/src-tauri/binaries/sprout-agent-$TARGET" - touch "desktop/src-tauri/binaries/sprout-dev-mcp-$TARGET" + touch "desktop/src-tauri/binaries/buzz-acp-$TARGET" + touch "desktop/src-tauri/binaries/buzz-agent-$TARGET" + touch "desktop/src-tauri/binaries/buzz-dev-mcp-$TARGET" touch "desktop/src-tauri/binaries/git-credential-nostr-$TARGET" - touch "desktop/src-tauri/binaries/sprout-$TARGET" + touch "desktop/src-tauri/binaries/buzz-$TARGET" # Mesh rev is derived from Cargo.lock so a dependency bump needs no # lockstep edit here; the cache key tracks it automatically. - name: Resolve mesh-llm rev diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ca3b07249..17a418868 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,12 +67,12 @@ jobs: - name: Generate release config run: cd desktop && node scripts/build-release-config.mjs env: - SPROUT_UPDATER_PUBLIC_KEY: ${{ secrets.SPROUT_UPDATER_PUBLIC_KEY }} - SPROUT_UPDATER_ENDPOINT: https://github.com/block/sprout/releases/download/sprout-desktop-latest/latest.json + BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY }} + BUZZ_UPDATER_ENDPOINT: https://github.com/block/sprout/releases/download/buzz-desktop-latest/latest.json - name: Build sidecars run: | - cargo build --release -p sprout-acp -p sprout-agent -p sprout-dev-mcp -p git-credential-nostr -p sprout-cli + cargo build --release -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli ./scripts/bundle-sidecars.sh # Mesh rev derived from Cargo.lock (no lockstep edit on dep bump); cache key tracks it. @@ -118,8 +118,8 @@ jobs: - name: Build unsigned Tauri app run: cd desktop && pnpm tauri build --verbose --no-sign --config src-tauri/tauri.release.conf.json env: - SPROUT_UPDATER_PUBLIC_KEY: ${{ secrets.SPROUT_UPDATER_PUBLIC_KEY }} - SPROUT_UPDATER_ENDPOINT: https://github.com/block/sprout/releases/download/sprout-desktop-latest/latest.json + BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY }} + BUZZ_UPDATER_ENDPOINT: https://github.com/block/sprout/releases/download/buzz-desktop-latest/latest.json TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} CMAKE_POLICY_VERSION_MINIMUM: "3.5" @@ -148,7 +148,7 @@ jobs: codesign-s3-bucket: ${{ secrets.CODESIGN_S3_BUCKET }} unsigned-artifact-path: ${{ steps.unsigned.outputs.dmg }} entitlements-plist-path: desktop/src-tauri/Entitlements.plist - artifact-name: sprout-${{ github.sha }}-${{ github.run_id }}-arm64 + artifact-name: buzz-${{ github.sha }}-${{ github.run_id }}-arm64 - name: Replace DMG and rebuild updater archive env: @@ -214,7 +214,7 @@ jobs: bash desktop/scripts/generate-oss-latest-json.sh \ "$VERSION" \ "$SIG_PATH" \ - "https://github.com/block/sprout/releases/download/sprout-desktop-latest/$ARCHIVE_NAME" \ + "https://github.com/block/sprout/releases/download/buzz-desktop-latest/$ARCHIVE_NAME" \ > latest.json cat latest.json env: @@ -244,12 +244,12 @@ jobs: - name: Update rolling release for auto-updater run: | - gh release create sprout-desktop-latest \ + gh release create buzz-desktop-latest \ --prerelease \ --title "Sprout Desktop Auto-Update" \ --notes "Rolling release for the Tauri auto-updater. Do not download manually — use the versioned release instead." \ 2>/dev/null || true - gh release upload sprout-desktop-latest \ + gh release upload buzz-desktop-latest \ latest.json \ "$ARCHIVE_PATH" \ "$SIG_PATH" \ @@ -292,19 +292,19 @@ jobs: - name: Generate release config run: cd desktop && node scripts/build-release-config.mjs env: - SPROUT_UPDATER_PUBLIC_KEY: ${{ secrets.SPROUT_UPDATER_PUBLIC_KEY }} - SPROUT_UPDATER_ENDPOINT: https://github.com/block/sprout/releases/download/sprout-desktop-latest/latest.json + BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY }} + BUZZ_UPDATER_ENDPOINT: https://github.com/block/sprout/releases/download/buzz-desktop-latest/latest.json - name: Build sidecars run: | - cargo build --release --target "$TARGET" -p sprout-acp -p sprout-agent -p sprout-dev-mcp -p git-credential-nostr -p sprout-cli + cargo build --release --target "$TARGET" -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli ./scripts/bundle-sidecars.sh "$TARGET" - name: Build unsigned Tauri app run: cd desktop && pnpm tauri build --verbose --no-sign --target "$TARGET" --config src-tauri/tauri.release.conf.json env: - SPROUT_UPDATER_PUBLIC_KEY: ${{ secrets.SPROUT_UPDATER_PUBLIC_KEY }} - SPROUT_UPDATER_ENDPOINT: https://github.com/block/sprout/releases/download/sprout-desktop-latest/latest.json + BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY }} + BUZZ_UPDATER_ENDPOINT: https://github.com/block/sprout/releases/download/buzz-desktop-latest/latest.json TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} CMAKE_POLICY_VERSION_MINIMUM: "3.5" @@ -330,7 +330,7 @@ jobs: codesign-s3-bucket: ${{ secrets.CODESIGN_S3_BUCKET }} unsigned-artifact-path: ${{ steps.unsigned.outputs.dmg }} entitlements-plist-path: desktop/src-tauri/Entitlements.plist - artifact-name: sprout-${{ github.sha }}-${{ github.run_id }}-x64 + artifact-name: buzz-${{ github.sha }}-${{ github.run_id }}-x64 - name: Replace DMG and signed .app env: @@ -422,7 +422,7 @@ jobs: - name: Build sidecars run: | - cargo build --release -p sprout-acp -p sprout-agent -p sprout-dev-mcp -p git-credential-nostr -p sprout-cli + cargo build --release -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli ./scripts/bundle-sidecars.sh - name: Build Linux Tauri app diff --git a/.github/workflows/sprig.yml b/.github/workflows/sprig.yml index 575d6427e..cd05d8597 100644 --- a/.github/workflows/sprig.yml +++ b/.github/workflows/sprig.yml @@ -1,9 +1,9 @@ name: Sprig # Builds and publishes Sprig — one deploy-anywhere Linux multicall binary for: -# sprout-acp ACP harness that bridges Sprout events to the LLM agent -# sprout-agent ACP-compliant agent (spawns MCP, calls LLMs) -# sprout-dev-mcp Developer MCP server (multicall: rg, tree, sprout, +# buzz-acp ACP harness that bridges Sprout events to the LLM agent +# buzz-agent ACP-compliant agent (spawns MCP, calls LLMs) +# buzz-dev-mcp Developer MCP server (multicall: rg, tree, buzz, # git-credential-nostr, git-sign-nostr) # # Targets: x86_64-unknown-linux-musl and aarch64-unknown-linux-musl (static @@ -137,7 +137,7 @@ jobs: set -euo pipefail TAG="sprig-latest" TITLE="Sprig (rolling)" - NOTES="Rolling Linux build of Sprig (all-in-one sprout-acp + sprout-agent + sprout-dev-mcp), tracking \`main\` (\`${SHA}\`)." + NOTES="Rolling Linux build of Sprig (all-in-one buzz-acp + buzz-agent + buzz-dev-mcp), tracking \`main\` (\`${SHA}\`)." if gh api "repos/${REPO}/git/refs/tags/${TAG}" >/dev/null 2>&1; then gh api -X PATCH "repos/${REPO}/git/refs/tags/${TAG}" \ @@ -197,5 +197,5 @@ jobs: set -euo pipefail gh release create "$TAG" \ --title "Sprig v${VERSION}" \ - --notes "Sprig v${VERSION} — Linux all-in-one builds of sprout-acp + sprout-agent + sprout-dev-mcp." \ + --notes "Sprig v${VERSION} — Linux all-in-one builds of buzz-acp + buzz-agent + buzz-dev-mcp." \ dist/* diff --git a/Cargo.lock b/Cargo.lock index feca763ec..075998be8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -701,6 +701,466 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "buzz-acp" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64", + "buzz-core", + "buzz-persona", + "buzz-sdk", + "chrono", + "clap", + "evalexpr", + "futures-util", + "hex", + "nix 0.31.3", + "nostr", + "reqwest 0.13.3", + "rustls", + "serde", + "serde_json", + "sha2 0.11.0", + "thiserror 2.0.18", + "tokio", + "tokio-tungstenite 0.29.0", + "tokio-util", + "toml 1.1.2+spec-1.1.0", + "tracing", + "tracing-subscriber", + "url", + "uuid", +] + +[[package]] +name = "buzz-admin" +version = "0.1.0" +dependencies = [ + "anyhow", + "buzz-auth", + "buzz-core", + "buzz-db", + "clap", + "hex", + "nostr", + "serde_json", + "tokio", +] + +[[package]] +name = "buzz-agent" +version = "0.1.0" +dependencies = [ + "arc-swap", + "async-trait", + "axum", + "base64", + "getrandom 0.4.2", + "hex", + "nix 0.31.3", + "reqwest 0.13.3", + "rmcp", + "serde", + "serde_json", + "serde_yaml", + "sha2 0.11.0", + "tempfile", + "tokio", + "tracing", + "tracing-subscriber", + "urlencoding", + "webbrowser", +] + +[[package]] +name = "buzz-audit" +version = "0.1.0" +dependencies = [ + "buzz-core", + "chrono", + "futures-util", + "hex", + "serde", + "serde_json", + "sha2 0.11.0", + "sqlx", + "thiserror 2.0.18", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "buzz-auth" +version = "0.1.0" +dependencies = [ + "buzz-core", + "hex", + "nostr", + "rand 0.10.1", + "serde", + "serde_json", + "sha2 0.11.0", + "thiserror 2.0.18", + "tokio", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "buzz-cli" +version = "0.1.0" +dependencies = [ + "base64", + "buzz-core", + "buzz-persona", + "buzz-sdk", + "buzz-ws-client", + "clap", + "diffy", + "hex", + "infer", + "nostr", + "reqwest 0.13.3", + "serde", + "serde_json", + "sha2 0.11.0", + "thiserror 2.0.18", + "tokio", + "url", + "uuid", +] + +[[package]] +name = "buzz-core" +version = "0.1.0" +dependencies = [ + "chrono", + "hex", + "hmac 0.13.0", + "nostr", + "percent-encoding", + "rand 0.10.1", + "serde", + "serde_json", + "sha2 0.11.0", + "subtle", + "thiserror 2.0.18", + "url", + "uuid", + "zeroize", +] + +[[package]] +name = "buzz-db" +version = "0.1.0" +dependencies = [ + "buzz-core", + "chrono", + "hex", + "nostr", + "serde", + "serde_json", + "sha2 0.11.0", + "sqlx", + "thiserror 2.0.18", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "buzz-dev-mcp" +version = "0.1.0" +dependencies = [ + "base64", + "buzz-cli", + "git-credential-nostr", + "git-sign-nostr", + "ignore", + "image", + "nix 0.31.3", + "nostr", + "reqwest 0.13.3", + "rmcp", + "schemars", + "serde", + "serde_json", + "similar", + "tempfile", + "tokio", + "tokio-util", + "tracing", + "tracing-subscriber", + "zeroize", +] + +[[package]] +name = "buzz-media" +version = "0.1.0" +dependencies = [ + "axum", + "blurhash", + "buzz-core", + "bytes", + "chrono", + "futures-core", + "futures-util", + "hex", + "image", + "imagesize", + "infer", + "mp4", + "nostr", + "rust-s3", + "serde", + "serde_json", + "sha2 0.11.0", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "buzz-pair-relay" +version = "0.1.0" +dependencies = [ + "futures-util", + "http-body-util", + "hyper", + "hyper-util", + "parking_lot", + "secp256k1 0.31.1", + "serde_json", + "sha2 0.11.0", + "tokio", + "tokio-tungstenite 0.29.0", + "tokio-util", +] + +[[package]] +name = "buzz-pairing-cli" +version = "0.1.0" +dependencies = [ + "buzz-core", + "clap", + "futures-util", + "hex", + "nostr", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-tungstenite 0.29.0", + "url", + "zeroize", +] + +[[package]] +name = "buzz-persona" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "serde_yaml", + "tempfile", + "thiserror 2.0.18", +] + +[[package]] +name = "buzz-proxy" +version = "0.1.0" +dependencies = [ + "axum", + "buzz-core", + "chrono", + "dashmap", + "futures-util", + "hex", + "hmac 0.13.0", + "moka", + "nostr", + "rand 0.10.1", + "reqwest 0.13.3", + "serde", + "serde_json", + "sha2 0.11.0", + "thiserror 2.0.18", + "tokio", + "tokio-tungstenite 0.29.0", + "tower-http", + "tracing", + "tracing-subscriber", + "url", + "uuid", +] + +[[package]] +name = "buzz-pubsub" +version = "0.1.0" +dependencies = [ + "buzz-auth", + "buzz-core", + "chrono", + "deadpool-redis", + "futures-util", + "nostr", + "redis", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "buzz-relay" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "base64", + "buzz-audit", + "buzz-auth", + "buzz-core", + "buzz-db", + "buzz-media", + "buzz-pubsub", + "buzz-sdk", + "buzz-search", + "buzz-workflow", + "bytes", + "chrono", + "dashmap", + "deadpool-redis", + "futures-util", + "hex", + "hmac 0.13.0", + "infer", + "mesh-llm-host-runtime", + "mesh-llm-sdk", + "metrics", + "metrics-exporter-prometheus", + "moka", + "nostr", + "rand 0.10.1", + "redis", + "reqwest 0.13.3", + "rust-s3", + "serde", + "serde_json", + "serde_yaml", + "sha2 0.11.0", + "sqlx", + "subtle", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tracing", + "tracing-subscriber", + "url", + "uuid", +] + +[[package]] +name = "buzz-sdk" +version = "0.1.0" +dependencies = [ + "buzz-core", + "nostr", + "serde", + "serde_json", + "thiserror 2.0.18", + "uuid", +] + +[[package]] +name = "buzz-search" +version = "0.1.0" +dependencies = [ + "buzz-core", + "chrono", + "nostr", + "reqwest 0.13.3", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "buzz-test-client" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64", + "buzz-core", + "buzz-ws-client", + "chrono", + "futures-util", + "hex", + "nostr", + "rand 0.10.1", + "reqwest 0.13.3", + "rust-s3", + "rustls", + "serde", + "serde_json", + "sha2 0.11.0", + "thiserror 2.0.18", + "tokio", + "tokio-tungstenite 0.29.0", + "tracing", + "tracing-subscriber", + "url", + "uuid", +] + +[[package]] +name = "buzz-workflow" +version = "0.1.0" +dependencies = [ + "buzz-core", + "buzz-db", + "chrono", + "cron", + "dashmap", + "evalexpr", + "hex", + "nostr", + "reqwest 0.13.3", + "serde", + "serde_json", + "serde_yaml", + "thiserror 2.0.18", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "buzz-ws-client" +version = "0.1.0" +dependencies = [ + "futures-util", + "nostr", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tokio-tungstenite 0.29.0", + "tracing", + "url", +] + [[package]] name = "bytemuck" version = "1.25.0" @@ -1091,10 +1551,10 @@ name = "countdown-bot" version = "0.1.0" dependencies = [ "anyhow", + "buzz-sdk", "futures-util", "nostr", "serde_json", - "sprout-sdk", "tokio", "tokio-tungstenite 0.29.0", "url", @@ -6285,1194 +6745,734 @@ dependencies = [ name = "rustls-platform-verifier-android" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - -[[package]] -name = "rustls-webpki" -version = "0.103.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" -dependencies = [ - "aws-lc-rs", - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "rxml" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc94b580d0f5a6b7a2d604e597513d3c673154b52ddeccd1d5c32360d945ee" -dependencies = [ - "bytes", - "rxml_validation", -] - -[[package]] -name = "rxml_validation" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e80413b9a35e9d33217b3dcac04cf95f6559d15944b93887a08be5496c4a4" -dependencies = [ - "compact_str", -] - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "safe-transmute" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3944826ff8fa8093089aba3acb4ef44b9446a99a16f3bf4e74af3f77d340ab7d" - -[[package]] -name = "salsa20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" -dependencies = [ - "cipher", -] - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "schemars" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" -dependencies = [ - "chrono", - "dyn-clone", - "ref-cast", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "scrypt" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" -dependencies = [ - "password-hash", - "pbkdf2", - "salsa20", - "sha2 0.10.9", -] - -[[package]] -name = "secp256k1" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" -dependencies = [ - "rand 0.8.6", - "secp256k1-sys 0.10.1", - "serde", -] - -[[package]] -name = "secp256k1" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" -dependencies = [ - "bitcoin_hashes", - "rand 0.9.4", - "secp256k1-sys 0.11.0", -] - -[[package]] -name = "secp256k1-sys" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" -dependencies = [ - "cc", -] - -[[package]] -name = "secp256k1-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" -dependencies = [ - "cc", -] - -[[package]] -name = "secret-service" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4" -dependencies = [ - "aes", - "cbc", - "futures-util", - "generic-array", - "hkdf 0.12.4", - "num", - "once_cell", - "rand 0.8.6", - "serde", - "sha2 0.10.9", - "zbus", -] - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags", - "core-foundation 0.10.1", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "seize" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b55fb86dfd3a2f5f76ea78310a88f96c4ea21a3031f8d212443d56123fd0521" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - -[[package]] -name = "send_wrapper" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] -name = "serde" -version = "1.0.228" +name = "rustls-webpki" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ - "serde_core", - "serde_derive", + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] -name = "serde_bytes" -version = "0.11.19" +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" -dependencies = [ - "serde", - "serde_core", -] +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] -name = "serde_core" -version = "1.0.228" +name = "rxml" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "65bc94b580d0f5a6b7a2d604e597513d3c673154b52ddeccd1d5c32360d945ee" dependencies = [ - "serde_derive", + "bytes", + "rxml_validation", ] [[package]] -name = "serde_derive" -version = "1.0.228" +name = "rxml_validation" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "826e80413b9a35e9d33217b3dcac04cf95f6559d15944b93887a08be5496c4a4" dependencies = [ - "proc-macro2", - "quote", - "syn", + "compact_str", ] [[package]] -name = "serde_derive_internals" -version = "0.29.1" +name = "ryu" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] -name = "serde_json" -version = "1.0.150" +name = "safe-transmute" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] +checksum = "3944826ff8fa8093089aba3acb4ef44b9446a99a16f3bf4e74af3f77d340ab7d" [[package]] -name = "serde_path_to_error" -version = "0.1.20" +name = "salsa20" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" dependencies = [ - "itoa", - "serde", - "serde_core", + "cipher", ] [[package]] -name = "serde_repr" -version = "0.1.20" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ - "proc-macro2", - "quote", - "syn", + "winapi-util", ] [[package]] -name = "serde_spanned" -version = "1.1.1" +name = "schannel" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "serde_core", + "windows-sys 0.61.2", ] [[package]] -name = "serde_urlencoded" -version = "0.7.1" +name = "schemars" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ - "form_urlencoded", - "itoa", - "ryu", + "chrono", + "dyn-clone", + "ref-cast", + "schemars_derive", "serde", + "serde_json", ] [[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" +name = "schemars_derive" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", ] [[package]] -name = "serdect" -version = "0.4.3" +name = "scoped-tls" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" -dependencies = [ - "base16ct", - "serde", -] +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] -name = "sha1" -version = "0.10.6" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if 1.0.4", - "cpufeatures 0.2.17", - "digest 0.10.7", -] +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "sha1" +name = "scrypt" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" dependencies = [ - "cfg-if 1.0.4", - "cpufeatures 0.3.0", - "digest 0.11.3", + "password-hash", + "pbkdf2", + "salsa20", + "sha2 0.10.9", ] [[package]] -name = "sha1_smol" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" - -[[package]] -name = "sha2" -version = "0.10.9" +name = "secp256k1" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "cfg-if 1.0.4", - "cpufeatures 0.2.17", - "digest 0.10.7", - "sha2-asm", + "rand 0.8.6", + "secp256k1-sys 0.10.1", + "serde", ] [[package]] -name = "sha2" -version = "0.11.0" +name = "secp256k1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" dependencies = [ - "cfg-if 1.0.4", - "cpufeatures 0.3.0", - "digest 0.11.3", + "bitcoin_hashes", + "rand 0.9.4", + "secp256k1-sys 0.11.0", ] [[package]] -name = "sha2-asm" -version = "0.6.4" +name = "secp256k1-sys" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b845214d6175804686b2bd482bcffe96651bb2d1200742b712003504a2dac1ab" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" dependencies = [ "cc", ] [[package]] -name = "sharded-slab" -version = "0.1.7" +name = "secp256k1-sys" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" dependencies = [ - "lazy_static", + "cc", ] [[package]] -name = "shellexpand" -version = "3.1.2" +name = "secret-service" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" +checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4" dependencies = [ - "bstr", - "dirs", - "os_str_bytes", + "aes", + "cbc", + "futures-util", + "generic-array", + "hkdf 0.12.4", + "num", + "once_cell", + "rand 0.8.6", + "serde", + "sha2 0.10.9", + "zbus", ] [[package]] -name = "shlex" -version = "1.3.0" +name = "security-framework" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] [[package]] -name = "signal-hook" -version = "0.3.18" +name = "security-framework" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", "libc", - "signal-hook-registry", + "security-framework-sys", ] [[package]] -name = "signal-hook-mio" -version = "0.2.5" +name = "security-framework-sys" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ + "core-foundation-sys", "libc", - "mio", - "signal-hook", ] [[package]] -name = "signal-hook-registry" -version = "1.4.8" +name = "seize" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +checksum = "5b55fb86dfd3a2f5f76ea78310a88f96c4ea21a3031f8d212443d56123fd0521" dependencies = [ - "errno", "libc", + "windows-sys 0.61.2", ] [[package]] -name = "signature" -version = "3.0.0" +name = "semver" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] -name = "simd-adler32" -version = "0.3.9" +name = "send_wrapper" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] -name = "simd_cesu8" -version = "1.1.1" +name = "serde" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ - "rustc_version", - "simdutf8", + "serde_core", + "serde_derive", ] [[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - -[[package]] -name = "similar" -version = "3.1.0" +name = "serde_bytes" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04d93e861ede2e497b47833469b8ec9d5c07fa4c78ce7a00f6eb7dd8168b4b3f" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" dependencies = [ - "bstr", + "serde", + "serde_core", ] [[package]] -name = "simple-dns" -version = "0.11.3" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a75cbde1bf934313596a004973e462f9a82caa814dcf1a5f507bdf51597eeb4" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ - "bitflags", + "serde_derive", ] [[package]] -name = "siphasher" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" - -[[package]] -name = "sketches-ddsketch" -version = "0.3.1" +name = "serde_derive" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" - -[[package]] -name = "skippy-cache" -version = "0.68.0" -source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82#ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ - "anyhow", - "blake3", - "skippy-protocol", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "skippy-coordinator" -version = "0.68.0" -source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82#ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82" +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ - "thiserror 2.0.18", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "skippy-ffi" -version = "0.68.0" -source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82#ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82" +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ - "libloading", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", ] [[package]] -name = "skippy-metrics" -version = "0.68.0" -source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82#ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82" - -[[package]] -name = "skippy-protocol" -version = "0.68.0" -source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82#ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82" +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ - "prost", - "prost-build", - "protoc-bin-vendored", + "itoa", "serde", + "serde_core", ] [[package]] -name = "skippy-runtime" -version = "0.68.0" -source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82#ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82" +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ - "anyhow", - "libc", - "serde", - "serde_json", - "sha2 0.10.9", - "skippy-ffi", - "tokio", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "skippy-server" -version = "0.68.0" -source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82#ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82" +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ - "anyhow", - "async-trait", - "axum", - "base64", - "blake3", - "clap", - "futures-util", - "libc", - "openai-frontend", - "opentelemetry-proto", - "serde", - "serde_json", - "sha2 0.10.9", - "skippy-cache", - "skippy-metrics", - "skippy-protocol", - "skippy-runtime", - "socket2", - "tokio", - "tokio-stream", - "tonic", + "serde_core", ] [[package]] -name = "skippy-topology" -version = "0.68.0" -source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82#ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82" +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ + "form_urlencoded", + "itoa", + "ryu", "serde", - "serde_json", ] [[package]] -name = "slab" -version = "0.4.12" +name = "serde_yaml" +version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] [[package]] -name = "smallvec" -version = "1.15.1" +name = "serdect" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" dependencies = [ + "base16ct", "serde", ] [[package]] -name = "socket-pktinfo" -version = "0.3.2" +name = "sha1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927136cc2ae6a1b0e66ac6b1210902b75c3f726db004a73bc18686dcd0dcd22f" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "libc", - "socket2", - "windows-sys 0.60.2", + "cfg-if 1.0.4", + "cpufeatures 0.2.17", + "digest 0.10.7", ] [[package]] -name = "socket2" -version = "0.6.3" +name = "sha1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ - "libc", - "windows-sys 0.61.2", + "cfg-if 1.0.4", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] -name = "sorted-index-buffer" -version = "0.2.1" +name = "sha1_smol" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea06cc588e43c632923a55450401b8f25e628131571d4e1baea1bdfdb2b5ed06" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" [[package]] -name = "spez" -version = "0.1.2" +name = "sha2" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87e960f4dca2788eeb86bbdde8dd246be8948790b7618d656e68f9b720a86e8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ - "proc-macro2", - "quote", - "syn", + "cfg-if 1.0.4", + "cpufeatures 0.2.17", + "digest 0.10.7", + "sha2-asm", ] [[package]] -name = "spin" -version = "0.9.8" +name = "sha2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ - "lock_api", + "cfg-if 1.0.4", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] -name = "spin" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" - -[[package]] -name = "spki" -version = "0.8.0" +name = "sha2-asm" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +checksum = "b845214d6175804686b2bd482bcffe96651bb2d1200742b712003504a2dac1ab" dependencies = [ - "base64ct", - "der", + "cc", ] [[package]] -name = "sprig" -version = "0.1.0" +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ - "sprout-acp", - "sprout-agent", - "sprout-dev-mcp", + "lazy_static", ] [[package]] -name = "sprout-acp" -version = "0.1.0" +name = "shellexpand" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" dependencies = [ - "anyhow", - "base64", - "chrono", - "clap", - "evalexpr", - "futures-util", - "hex", - "nix 0.31.3", - "nostr", - "reqwest 0.13.3", - "rustls", - "serde", - "serde_json", - "sha2 0.11.0", - "sprout-core", - "sprout-persona", - "sprout-sdk", - "thiserror 2.0.18", - "tokio", - "tokio-tungstenite 0.29.0", - "tokio-util", - "toml 1.1.2+spec-1.1.0", - "tracing", - "tracing-subscriber", - "url", - "uuid", + "bstr", + "dirs", + "os_str_bytes", ] [[package]] -name = "sprout-admin" -version = "0.1.0" -dependencies = [ - "anyhow", - "clap", - "hex", - "nostr", - "serde_json", - "sprout-auth", - "sprout-core", - "sprout-db", - "tokio", -] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "sprout-agent" -version = "0.1.0" +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ - "arc-swap", - "async-trait", - "axum", - "base64", - "getrandom 0.4.2", - "hex", - "nix 0.31.3", - "reqwest 0.13.3", - "rmcp", - "serde", - "serde_json", - "serde_yaml", - "sha2 0.11.0", - "tempfile", - "tokio", - "tracing", - "tracing-subscriber", - "urlencoding", - "webbrowser", + "libc", + "signal-hook-registry", ] [[package]] -name = "sprout-audit" -version = "0.1.0" +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ - "chrono", - "futures-util", - "hex", - "serde", - "serde_json", - "sha2 0.11.0", - "sprout-core", - "sqlx", - "thiserror 2.0.18", - "tokio", - "tracing", - "uuid", + "libc", + "mio", + "signal-hook", ] [[package]] -name = "sprout-auth" -version = "0.1.0" +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ - "hex", - "nostr", - "rand 0.10.1", - "serde", - "serde_json", - "sha2 0.11.0", - "sprout-core", - "thiserror 2.0.18", - "tokio", - "tracing", - "url", - "uuid", + "errno", + "libc", ] [[package]] -name = "sprout-cli" -version = "0.1.0" -dependencies = [ - "base64", - "clap", - "diffy", - "hex", - "infer", - "nostr", - "reqwest 0.13.3", - "serde", - "serde_json", - "sha2 0.11.0", - "sprout-core", - "sprout-persona", - "sprout-sdk", - "sprout-ws-client", - "thiserror 2.0.18", - "tokio", - "url", - "uuid", -] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" [[package]] -name = "sprout-core" -version = "0.1.0" -dependencies = [ - "chrono", - "hex", - "hmac 0.13.0", - "nostr", - "percent-encoding", - "rand 0.10.1", - "serde", - "serde_json", - "sha2 0.11.0", - "subtle", - "thiserror 2.0.18", - "url", - "uuid", - "zeroize", -] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] -name = "sprout-db" -version = "0.1.0" +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" dependencies = [ - "chrono", - "hex", - "nostr", - "serde", - "serde_json", - "sha2 0.11.0", - "sprout-core", - "sqlx", - "thiserror 2.0.18", - "tokio", - "tracing", - "uuid", + "rustc_version", + "simdutf8", ] [[package]] -name = "sprout-dev-mcp" -version = "0.1.0" +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "similar" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04d93e861ede2e497b47833469b8ec9d5c07fa4c78ce7a00f6eb7dd8168b4b3f" dependencies = [ - "base64", - "git-credential-nostr", - "git-sign-nostr", - "ignore", - "image", - "nix 0.31.3", - "nostr", - "reqwest 0.13.3", - "rmcp", - "schemars", - "serde", - "serde_json", - "similar", - "sprout-cli", - "tempfile", - "tokio", - "tokio-util", - "tracing", - "tracing-subscriber", - "zeroize", + "bstr", ] [[package]] -name = "sprout-media" -version = "0.1.0" +name = "simple-dns" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a75cbde1bf934313596a004973e462f9a82caa814dcf1a5f507bdf51597eeb4" dependencies = [ - "axum", - "blurhash", - "bytes", - "chrono", - "futures-core", - "futures-util", - "hex", - "image", - "imagesize", - "infer", - "mp4", - "nostr", - "rust-s3", - "serde", - "serde_json", - "sha2 0.11.0", - "sprout-core", - "tempfile", - "thiserror 2.0.18", - "tokio", - "tokio-util", - "tracing", + "bitflags", ] [[package]] -name = "sprout-pair-relay" -version = "0.1.0" -dependencies = [ - "futures-util", - "http-body-util", - "hyper", - "hyper-util", - "parking_lot", - "secp256k1 0.31.1", - "serde_json", - "sha2 0.11.0", - "tokio", - "tokio-tungstenite 0.29.0", - "tokio-util", +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "sketches-ddsketch" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" + +[[package]] +name = "skippy-cache" +version = "0.68.0" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82#ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82" +dependencies = [ + "anyhow", + "blake3", + "skippy-protocol", ] [[package]] -name = "sprout-pairing-cli" -version = "0.1.0" +name = "skippy-coordinator" +version = "0.68.0" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82#ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82" dependencies = [ - "clap", - "futures-util", - "hex", - "nostr", - "serde_json", - "sprout-core", "thiserror 2.0.18", - "tokio", - "tokio-tungstenite 0.29.0", - "url", - "zeroize", ] [[package]] -name = "sprout-persona" -version = "0.1.0" +name = "skippy-ffi" +version = "0.68.0" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82#ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82" dependencies = [ - "serde", - "serde_json", - "serde_yaml", - "tempfile", - "thiserror 2.0.18", + "libloading", ] [[package]] -name = "sprout-proxy" -version = "0.1.0" +name = "skippy-metrics" +version = "0.68.0" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82#ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82" + +[[package]] +name = "skippy-protocol" +version = "0.68.0" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82#ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82" dependencies = [ - "axum", - "chrono", - "dashmap", - "futures-util", - "hex", - "hmac 0.13.0", - "moka", - "nostr", - "rand 0.10.1", - "reqwest 0.13.3", + "prost", + "prost-build", + "protoc-bin-vendored", "serde", - "serde_json", - "sha2 0.11.0", - "sprout-core", - "thiserror 2.0.18", - "tokio", - "tokio-tungstenite 0.29.0", - "tower-http", - "tracing", - "tracing-subscriber", - "url", - "uuid", ] [[package]] -name = "sprout-pubsub" -version = "0.1.0" +name = "skippy-runtime" +version = "0.68.0" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82#ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82" dependencies = [ - "chrono", - "deadpool-redis", - "futures-util", - "nostr", - "redis", + "anyhow", + "libc", "serde", "serde_json", - "sprout-auth", - "sprout-core", - "thiserror 2.0.18", + "sha2 0.10.9", + "skippy-ffi", "tokio", - "tracing", - "uuid", ] [[package]] -name = "sprout-relay" -version = "0.1.0" +name = "skippy-server" +version = "0.68.0" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82#ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82" dependencies = [ "anyhow", + "async-trait", "axum", "base64", - "bytes", - "chrono", - "dashmap", - "deadpool-redis", + "blake3", + "clap", "futures-util", - "hex", - "hmac 0.13.0", - "infer", - "mesh-llm-host-runtime", - "mesh-llm-sdk", - "metrics", - "metrics-exporter-prometheus", - "moka", - "nostr", - "rand 0.10.1", - "redis", - "reqwest 0.13.3", - "rust-s3", + "libc", + "openai-frontend", + "opentelemetry-proto", "serde", "serde_json", - "serde_yaml", - "sha2 0.11.0", - "sprout-audit", - "sprout-auth", - "sprout-core", - "sprout-db", - "sprout-media", - "sprout-pubsub", - "sprout-sdk", - "sprout-search", - "sprout-workflow", - "sqlx", - "subtle", - "tempfile", - "thiserror 2.0.18", + "sha2 0.10.9", + "skippy-cache", + "skippy-metrics", + "skippy-protocol", + "skippy-runtime", + "socket2", "tokio", - "tokio-util", - "tower", - "tower-http", - "tracing", - "tracing-subscriber", - "url", - "uuid", + "tokio-stream", + "tonic", ] [[package]] -name = "sprout-sdk" -version = "0.1.0" +name = "skippy-topology" +version = "0.68.0" +source = "git+https://github.com/Mesh-LLM/mesh-llm.git?rev=ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82#ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82" dependencies = [ - "nostr", "serde", "serde_json", - "sprout-core", - "thiserror 2.0.18", - "uuid", ] [[package]] -name = "sprout-search" -version = "0.1.0" +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ - "chrono", - "nostr", - "reqwest 0.13.3", "serde", - "serde_json", - "sprout-core", - "thiserror 2.0.18", - "tokio", - "tracing", - "uuid", ] [[package]] -name = "sprout-test-client" -version = "0.1.0" +name = "socket-pktinfo" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927136cc2ae6a1b0e66ac6b1210902b75c3f726db004a73bc18686dcd0dcd22f" dependencies = [ - "anyhow", - "base64", - "chrono", - "futures-util", - "hex", - "nostr", - "rand 0.10.1", - "reqwest 0.13.3", - "rust-s3", - "rustls", - "serde", - "serde_json", - "sha2 0.11.0", - "sprout-core", - "sprout-ws-client", - "thiserror 2.0.18", - "tokio", - "tokio-tungstenite 0.29.0", - "tracing", - "tracing-subscriber", - "url", - "uuid", + "libc", + "socket2", + "windows-sys 0.60.2", ] [[package]] -name = "sprout-workflow" -version = "0.1.0" +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ - "chrono", - "cron", - "dashmap", - "evalexpr", - "hex", - "nostr", - "reqwest 0.13.3", - "serde", - "serde_json", - "serde_yaml", - "sprout-core", - "sprout-db", - "thiserror 2.0.18", - "tokio", - "tracing", - "uuid", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "sorted-index-buffer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea06cc588e43c632923a55450401b8f25e628131571d4e1baea1bdfdb2b5ed06" + +[[package]] +name = "spez" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87e960f4dca2788eeb86bbdde8dd246be8948790b7618d656e68f9b720a86e8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" + +[[package]] +name = "spki" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +dependencies = [ + "base64ct", + "der", ] [[package]] -name = "sprout-ws-client" +name = "sprig" version = "0.1.0" dependencies = [ - "futures-util", - "nostr", - "serde_json", - "thiserror 2.0.18", - "tokio", - "tokio-tungstenite 0.29.0", - "tracing", - "url", + "buzz-acp", + "buzz-agent", + "buzz-dev-mcp", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c43abfb4b..d73562580 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,29 +1,29 @@ [workspace] members = [ - "crates/sprout-relay", - "crates/sprout-core", - "crates/sprout-db", - "crates/sprout-pubsub", - "crates/sprout-auth", - "crates/sprout-search", - "crates/sprout-audit", - "crates/sprout-acp", - "crates/sprout-agent", + "crates/buzz-relay", + "crates/buzz-core", + "crates/buzz-db", + "crates/buzz-pubsub", + "crates/buzz-auth", + "crates/buzz-search", + "crates/buzz-audit", + "crates/buzz-acp", + "crates/buzz-agent", "crates/sprig", - "crates/sprout-proxy", - "crates/sprout-test-client", - "crates/sprout-ws-client", - "crates/sprout-admin", - "crates/sprout-workflow", - "crates/sprout-media", - "crates/sprout-cli", - "crates/sprout-pairing-cli", - "crates/sprout-sdk", - "crates/sprout-persona", + "crates/buzz-proxy", + "crates/buzz-test-client", + "crates/buzz-ws-client", + "crates/buzz-admin", + "crates/buzz-workflow", + "crates/buzz-media", + "crates/buzz-cli", + "crates/buzz-pairing-cli", + "crates/buzz-sdk", + "crates/buzz-persona", "crates/git-credential-nostr", "crates/git-sign-nostr", - "crates/sprout-pair-relay", - "crates/sprout-dev-mcp", + "crates/buzz-pair-relay", + "crates/buzz-dev-mcp", "examples/countdown-bot", ] exclude = ["desktop/src-tauri"] @@ -102,22 +102,22 @@ futures-util = "0.3" tokio-tungstenite = { version = "0.29", features = ["rustls-tls-webpki-roots"] } url = "2" -# MCP SDK (used by sprout-dev-mcp and sprout-agent) +# MCP SDK (used by buzz-dev-mcp and buzz-agent) rmcp = { version = "1.1.0", features = ["server", "transport-io", "macros"] } schemars = { version = "1", default-features = false } # Internal crates -sprout-core = { path = "crates/sprout-core" } -sprout-db = { path = "crates/sprout-db" } -sprout-auth = { path = "crates/sprout-auth" } -sprout-pubsub = { path = "crates/sprout-pubsub" } -sprout-search = { path = "crates/sprout-search" } -sprout-audit = { path = "crates/sprout-audit" } -sprout-proxy = { path = "crates/sprout-proxy" } -sprout-workflow = { path = "crates/sprout-workflow" } -sprout-media = { path = "crates/sprout-media" } -sprout-sdk = { path = "crates/sprout-sdk" } -sprout-ws-client = { path = "crates/sprout-ws-client" } +buzz-core = { path = "crates/buzz-core" } +buzz-db = { path = "crates/buzz-db" } +buzz-auth = { path = "crates/buzz-auth" } +buzz-pubsub = { path = "crates/buzz-pubsub" } +buzz-search = { path = "crates/buzz-search" } +buzz-audit = { path = "crates/buzz-audit" } +buzz-proxy = { path = "crates/buzz-proxy" } +buzz-workflow = { path = "crates/buzz-workflow" } +buzz-media = { path = "crates/buzz-media" } +buzz-sdk = { path = "crates/buzz-sdk" } +buzz-ws-client = { path = "crates/buzz-ws-client" } # CI profile — release-grade codegen for the relay so e2e tests hit a # realistic binary, not an unoptimised debug build. Inherits `release` diff --git a/Dockerfile b/Dockerfile index cf255d5a9..29b8fb737 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,8 +3,8 @@ FROM --platform=linux/amd64 rust:1.95-bookworm AS builder WORKDIR /build COPY . . -RUN cargo build --release -p sprout-relay \ - && strip target/release/sprout-relay +RUN cargo build --release -p buzz-relay \ + && strip target/release/buzz-relay # ── Web build stage (Node/pnpm) ──────────────────────────── FROM --platform=linux/amd64 node:24-bookworm-slim AS web-builder @@ -18,22 +18,22 @@ RUN pnpm -C web build FROM --platform=linux/amd64 debian:bookworm-slim # CAKE: non-root UID 1000 (numeric, not username) -RUN groupadd -g 1000 sprout && useradd -u 1000 -g sprout -m sprout +RUN groupadd -g 1000 buzz && useradd -u 1000 -g buzz -m buzz # CAKE: writable dirs -RUN mkdir -p /cache /tmp && chown sprout:sprout /cache /tmp +RUN mkdir -p /cache /tmp && chown buzz:buzz /cache /tmp # git: relay shells out to `git` for hydrate/receive-pack/upload-pack (S3-backed repos) # socat: Istio abstract→file socket bridge RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates git socat && rm -rf /var/lib/apt/lists/* -COPY --from=builder /build/target/release/sprout-relay /code/sprout-relay +COPY --from=builder /build/target/release/buzz-relay /code/buzz-relay COPY --from=web-builder /build/web/dist /code/web COPY script/start /code/start RUN chmod +x /code/start -ENV SPROUT_WEB_DIR="/code/web" +ENV BUZZ_WEB_DIR="/code/web" # CAKE: required Envoy env vars (overridden at runtime by CAKE). ENV ENVOY_ADMIN_SOCKET_PATH="@envoy-admin.sock" \ diff --git a/crates/sprout-acp/Cargo.toml b/crates/buzz-acp/Cargo.toml similarity index 85% rename from crates/sprout-acp/Cargo.toml rename to crates/buzz-acp/Cargo.toml index a4f24860c..b0f020c9a 100644 --- a/crates/sprout-acp/Cargo.toml +++ b/crates/buzz-acp/Cargo.toml @@ -1,25 +1,25 @@ [package] -name = "sprout-acp" +name = "buzz-acp" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true repository.workspace = true -description = "ACP harness that bridges Sprout events to AI agents" +description = "ACP harness that bridges Buzz events to AI agents" [lib] -name = "sprout_acp" +name = "buzz_acp" path = "src/lib.rs" [[bin]] -name = "sprout-acp" +name = "buzz-acp" path = "src/main.rs" [dependencies] # Internal -sprout-core = { workspace = true } -sprout-sdk = { workspace = true } -sprout-persona = { path = "../sprout-persona" } +buzz-core = { workspace = true } +buzz-sdk = { workspace = true } +buzz-persona = { path = "../buzz-persona" } # Nostr nostr = { workspace = true } diff --git a/crates/sprout-acp/README.md b/crates/buzz-acp/README.md similarity index 69% rename from crates/sprout-acp/README.md rename to crates/buzz-acp/README.md index ea978c49a..a78c6cfdf 100644 --- a/crates/sprout-acp/README.md +++ b/crates/buzz-acp/README.md @@ -1,11 +1,11 @@ -# sprout-acp +# buzz-acp -ACP harness that connects AI agents to Sprout. The harness listens for @mentions on the relay, prompts your agent, and the agent replies using the Sprout CLI. +ACP harness that connects AI agents to Buzz. The harness listens for @mentions on the relay, prompts your agent, and the agent replies using the Buzz CLI. ``` -Sprout Relay ──WS──→ sprout-acp ──stdio──→ Your Agent +Buzz Relay ──WS──→ buzz-acp ──stdio──→ Your Agent │ - Sprout CLI + Buzz CLI (send_message, etc.) ``` @@ -13,22 +13,22 @@ Supports any agent that speaks [ACP](https://agentclientprotocol.com/) over stdi ## Prerequisites -- A running Sprout relay (`just relay` starts Docker services automatically, or use a hosted instance) +- A running Buzz relay (`just relay` starts Docker services automatically, or use a hosted instance) - A Nostr keypair for the agent (see [Generating Keys](#generating-keys)) Build: ```bash -cargo build --release -p sprout-acp +cargo build --release -p buzz-acp export PATH="$PWD/target/release:$PATH" ``` ## Generating Keys -Each agent needs a Nostr keypair — this is the agent's identity in Sprout. Use `sprout-admin` to mint one: +Each agent needs a Nostr keypair — this is the agent's identity in Buzz. Use `buzz-admin` to mint one: ```bash -cargo run -p sprout-admin -- mint-token --name "my-agent" --scopes "messages:read,messages:write,channels:read" +cargo run -p buzz-admin -- mint-token --name "my-agent" --scopes "messages:read,messages:write,channels:read" ``` This prints an `nsec1...` private key and an API token. **Save both immediately — they're shown only once.** @@ -41,19 +41,19 @@ The harness discovers channels by querying the relay with the agent's authentica By default, the harness discovers only channels the agent is a **member** of (`GET /api/channels?member=true`). When the agent is added to a new channel, the membership notification subscription auto-subscribes to it. -**Private channels** require explicit membership. The relay doesn't yet have a REST/event API for managing channel members — this is a known gap. For now, use `create_channel` via the Sprout CLI to create new channels (the creator is automatically a member). +**Private channels** require explicit membership. The relay doesn't yet have a REST/event API for managing channel members — this is a known gap. For now, use `create_channel` via the Buzz CLI to create new channels (the creator is automatically a member). ## Quick Start (goose) ```bash -export SPROUT_PRIVATE_KEY="nsec1..." # your agent's key (see "Generating Keys") -export SPROUT_RELAY_URL="ws://localhost:3000" +export BUZZ_PRIVATE_KEY="nsec1..." # your agent's key (see "Generating Keys") +export BUZZ_RELAY_URL="ws://localhost:3000" export GOOSE_MODE=auto -sprout-acp +buzz-acp ``` -That's it. The harness spawns `goose acp`, connects to the relay, discovers channels, and starts listening. When someone @mentions the agent, goose receives the message and can reply using the Sprout CLI that the harness configures automatically. +That's it. The harness spawns `goose acp`, connects to the relay, discovers channels, and starts listening. When someone @mentions the agent, goose receives the message and can reply using the Buzz CLI that the harness configures automatically. ## Running with Codex @@ -65,10 +65,10 @@ cd /path/to/codex-acp && cargo build --release # Run export OPENAI_API_KEY="sk-..." # required — use an OpenAI API key, not a ChatGPT subscription -export SPROUT_ACP_AGENT_COMMAND="/path/to/codex-acp/target/release/codex-acp" -export SPROUT_ACP_AGENT_ARGS='-c,permissions.approval_policy="never"' +export BUZZ_ACP_AGENT_COMMAND="/path/to/codex-acp/target/release/codex-acp" +export BUZZ_ACP_AGENT_ARGS='-c,permissions.approval_policy="never"' -sprout-acp +buzz-acp ``` > **API key note:** `codex-acp` always attempts a ChatGPT WebSocket login first, which logs a `426 Upgrade Required` error. This is expected and non-fatal — it falls back to `OPENAI_API_KEY` automatically. Set `OPENAI_API_KEY` to ensure it has a working fallback. @@ -83,12 +83,12 @@ npm install -g @agentclientprotocol/claude-agent-acp # Run export ANTHROPIC_API_KEY="sk-ant-..." -export SPROUT_ACP_AGENT_COMMAND="claude-agent-acp" +export BUZZ_ACP_AGENT_COMMAND="claude-agent-acp" -sprout-acp +buzz-acp ``` -Older installs that still expose `claude-code-acp` are also supported. `sprout-acp` +Older installs that still expose `claude-code-acp` are also supported. `buzz-acp` treats both Claude ACP command names as the same zero-arg runtime. ## Configuration @@ -99,27 +99,27 @@ All configuration is via environment variables (or CLI flags — every env var h | Variable | Required | Default | Description | |----------|----------|---------|-------------| -| `SPROUT_PRIVATE_KEY` | **yes** | — | Agent's Nostr private key (`nsec1...`). Used for relay auth and agent identity. | -| `SPROUT_RELAY_URL` | no | `ws://localhost:3000` | Relay WebSocket URL. | -| `SPROUT_ACP_AGENT_COMMAND` | no | `goose` | Agent binary to spawn. | -| `SPROUT_ACP_AGENT_ARGS` | no | `acp` | Agent arguments (comma-separated). | -| `SPROUT_ACP_MCP_COMMAND` | no | `""` (empty) | Path to an optional MCP server binary to provide to the agent subprocess. | -| `SPROUT_ACP_IDLE_TIMEOUT` | no | `620` | Idle timeout: max seconds of silence before cancelling a turn. Resets on any agent stdout activity. | -| `SPROUT_ACP_MAX_TURN_DURATION` | no | `3600` | Absolute wall-clock cap per turn (safety valve). | -| `SPROUT_API_TOKEN` | no | — | API token (required if relay enforces token auth). | +| `BUZZ_PRIVATE_KEY` | **yes** | — | Agent's Nostr private key (`nsec1...`). Used for relay auth and agent identity. | +| `BUZZ_RELAY_URL` | no | `ws://localhost:3000` | Relay WebSocket URL. | +| `BUZZ_ACP_AGENT_COMMAND` | no | `goose` | Agent binary to spawn. | +| `BUZZ_ACP_AGENT_ARGS` | no | `acp` | Agent arguments (comma-separated). | +| `BUZZ_ACP_MCP_COMMAND` | no | `""` (empty) | Path to an optional MCP server binary to provide to the agent subprocess. | +| `BUZZ_ACP_IDLE_TIMEOUT` | no | `620` | Idle timeout: max seconds of silence before cancelling a turn. Resets on any agent stdout activity. | +| `BUZZ_ACP_MAX_TURN_DURATION` | no | `3600` | Absolute wall-clock cap per turn (safety valve). | +| `BUZZ_API_TOKEN` | no | — | API token (required if relay enforces token auth). | -**Note:** `SPROUT_ACP_AGENT_ARGS` splits on commas. For args with values, use: `-c,key="value"`. +**Note:** `BUZZ_ACP_AGENT_ARGS` splits on commas. For args with values, use: `-c,key="value"`. -**Legacy env vars:** `SPROUT_ACP_PRIVATE_KEY`, `SPROUT_ACP_API_TOKEN`, and `SPROUT_ACP_TURN_TIMEOUT` (replaced by `SPROUT_ACP_IDLE_TIMEOUT`) are still accepted as fallbacks. +**Legacy env vars:** `BUZZ_ACP_PRIVATE_KEY`, `BUZZ_ACP_API_TOKEN`, and `BUZZ_ACP_TURN_TIMEOUT` (replaced by `BUZZ_ACP_IDLE_TIMEOUT`) are still accepted as fallbacks. ### Parallel Agents & Heartbeat | Flag | Env Var | Default | Description | |------|---------|---------|-------------| -| `--agents` | `SPROUT_ACP_AGENTS` | `1` | Number of agent subprocesses (1–32). | -| `--heartbeat-interval` | `SPROUT_ACP_HEARTBEAT_INTERVAL` | `0` | Seconds between heartbeat prompts. `0` = disabled. Must be `0` or ≥10 when enabled. | -| `--heartbeat-prompt` | `SPROUT_ACP_HEARTBEAT_PROMPT` | (built-in) | Custom heartbeat prompt text. Conflicts with `--heartbeat-prompt-file`. | -| `--heartbeat-prompt-file` | `SPROUT_ACP_HEARTBEAT_PROMPT_FILE` | — | Read heartbeat prompt from a file. Conflicts with `--heartbeat-prompt`. | +| `--agents` | `BUZZ_ACP_AGENTS` | `1` | Number of agent subprocesses (1–32). | +| `--heartbeat-interval` | `BUZZ_ACP_HEARTBEAT_INTERVAL` | `0` | Seconds between heartbeat prompts. `0` = disabled. Must be `0` or ≥10 when enabled. | +| `--heartbeat-prompt` | `BUZZ_ACP_HEARTBEAT_PROMPT` | (built-in) | Custom heartbeat prompt text. Conflicts with `--heartbeat-prompt-file`. | +| `--heartbeat-prompt-file` | `BUZZ_ACP_HEARTBEAT_PROMPT_FILE` | — | Read heartbeat prompt from a file. Conflicts with `--heartbeat-prompt`. | ### Inbound Author Gate @@ -127,8 +127,8 @@ Controls which authors' events the harness forwards to the agent. Events from di | Flag | Env Var | Default | Description | |------|---------|---------|-------------| -| `--respond-to` | `SPROUT_ACP_RESPOND_TO` | `owner-only` | Author gate mode: `owner-only`, `allowlist`, `anyone`, `nobody`. | -| `--respond-to-allowlist` | `SPROUT_ACP_RESPOND_TO_ALLOWLIST` | — | Comma-separated 64-char hex pubkeys (required when mode is `allowlist`). Owner is always implicitly included. | +| `--respond-to` | `BUZZ_ACP_RESPOND_TO` | `owner-only` | Author gate mode: `owner-only`, `allowlist`, `anyone`, `nobody`. | +| `--respond-to-allowlist` | `BUZZ_ACP_RESPOND_TO_ALLOWLIST` | — | Comma-separated 64-char hex pubkeys (required when mode is `allowlist`). Owner is always implicitly included. | **Modes:** @@ -147,39 +147,39 @@ The gate applies to **all** inbound events — @mentions, DMs, thread replies, a ```bash # Default: only respond to owner -sprout-acp +buzz-acp # Respond to a team of three users (owner always included automatically) -sprout-acp --respond-to allowlist \ +buzz-acp --respond-to allowlist \ --respond-to-allowlist "abc123...64hex,def456...64hex,789abc...64hex" # Respond to anyone (open agent) -sprout-acp --respond-to anyone +buzz-acp --respond-to anyone # Broadcast-only: post on heartbeat, ignore all inbound events -sprout-acp --respond-to nobody --heartbeat-interval 300 +buzz-acp --respond-to nobody --heartbeat-interval 300 ``` ### Configuration Examples **Single agent, no heartbeat (default):** ```bash -sprout-acp +buzz-acp ``` **Four agents, no heartbeat (high-throughput event processing):** ```bash -sprout-acp --agents 4 +buzz-acp --agents 4 ``` **Two agents with 5-minute heartbeat:** ```bash -sprout-acp --agents 2 --heartbeat-interval 300 +buzz-acp --agents 2 --heartbeat-interval 300 ``` **Custom heartbeat prompt:** ```bash -sprout-acp --agents 2 --heartbeat-interval 300 \ +buzz-acp --agents 2 --heartbeat-interval 300 \ --heartbeat-prompt "Check get_feed_actions() for pending approvals, then get_feed_mentions() for unanswered mentions. If nothing actionable, end your turn immediately." ``` @@ -208,12 +208,12 @@ By default, the ACP harness subscribes to stream message kinds (9, 46010, 40007) **CLI flags:** ```bash -sprout-acp --kinds 9,46010,40007,45001,45002,45003 --no-mention-filter +buzz-acp --kinds 9,46010,40007,45001,45002,45003 --no-mention-filter ``` **Or with `--subscribe all`:** ```bash -sprout-acp --subscribe all --kinds 9,46010,40007,45001,45002,45003 +buzz-acp --subscribe all --kinds 9,46010,40007,45001,45002,45003 ``` **Per-channel config:** @@ -236,7 +236,7 @@ Forum event kinds: 2. **Channel discovery** — Queries the relay REST API for accessible channels, subscribes to each. 3. **Event loop** — Listens for @mention events (kind 9 with the agent's pubkey in a `#p` tag). Events queue per channel. 4. **Prompting** — When events are pending and no prompt is in flight for that channel, drains all queued events for the oldest channel into a single batched prompt via ACP `session/prompt`. -5. **Agent response** — The agent processes the prompt and uses the Sprout CLI (`send_message`, `get_messages`, etc.) to interact with Sprout. +5. **Agent response** — The agent processes the prompt and uses the Buzz CLI (`send_message`, `get_messages`, etc.) to interact with Buzz. 6. **Recovery** — If the agent crashes, the harness respawns it. If the relay disconnects, the harness reconnects with a `since` filter to avoid missing events. Each channel has at most one prompt in flight. Multiple channels can be processed concurrently when agents > 1. @@ -252,7 +252,7 @@ The harness works with any agent that implements the [ACP spec](https://agentcli - Accept `session/prompt` with a text message and stream `session/update` notifications - Return a `stopReason` (`end_turn`, `cancelled`, `max_tokens`, etc.) -Set `SPROUT_ACP_AGENT_COMMAND` and `SPROUT_ACP_AGENT_ARGS` to point at your agent binary. +Set `BUZZ_ACP_AGENT_COMMAND` and `BUZZ_ACP_AGENT_ARGS` to point at your agent binary. ## Testing diff --git a/crates/sprout-acp/src/acp.rs b/crates/buzz-acp/src/acp.rs similarity index 99% rename from crates/sprout-acp/src/acp.rs rename to crates/buzz-acp/src/acp.rs index c551b7899..7d8db4d40 100644 --- a/crates/sprout-acp/src/acp.rs +++ b/crates/buzz-acp/src/acp.rs @@ -202,7 +202,7 @@ impl AcpClient { // Callers MUST still call shutdown().await for guaranteed cleanup. .kill_on_drop(true); - // Per-persona env vars (e.g., GOOSE_PROVIDER, SPROUT_AGENT_PROVIDER). + // Per-persona env vars (e.g., GOOSE_PROVIDER, BUZZ_AGENT_PROVIDER). // Only injected if not already set in parent env (operator precedence). for (key, value) in extra_env { if std::env::var(key).is_err() { @@ -274,7 +274,7 @@ impl AcpClient { "protocolVersion": 1, "clientCapabilities": {}, "clientInfo": { - "name": "sprout-acp", + "name": "buzz-acp", "version": env!("CARGO_PKG_VERSION") } }); @@ -374,7 +374,7 @@ impl AcpClient { /// /// Used for slash-command pass-through: ACP connectors detect commands via /// the **first** block's text starting with `/`, so the harness sends - /// `["/cmd args", ""]` instead of one wrapped block. + /// `["/cmd args", ""]` instead of one wrapped block. pub async fn session_prompt_blocks_with_idle_timeout( &mut self, session_id: &str, @@ -1394,7 +1394,7 @@ mod tests { "protocolVersion": 1, "clientCapabilities": {}, "clientInfo": { - "name": "sprout-acp", + "name": "buzz-acp", "version": "0.1.0" } } @@ -1402,7 +1402,7 @@ mod tests { assert_eq!(msg["params"]["protocolVersion"].as_u64(), Some(1)); assert_eq!( msg["params"]["clientInfo"]["name"].as_str(), - Some("sprout-acp") + Some("buzz-acp") ); assert!(msg["params"]["clientCapabilities"].is_object()); } @@ -1416,11 +1416,11 @@ mod tests { args: vec![], env: vec![ EnvVar { - name: "SPROUT_RELAY_URL".into(), + name: "BUZZ_RELAY_URL".into(), value: "ws://localhost:3000".into(), }, EnvVar { - name: "SPROUT_PRIVATE_KEY".into(), + name: "BUZZ_PRIVATE_KEY".into(), value: "nsec1abc".into(), }, ], @@ -1437,13 +1437,13 @@ mod tests { assert_eq!(serialized["env"].as_array().unwrap().len(), 2); assert_eq!( serialized["env"][0]["name"].as_str(), - Some("SPROUT_RELAY_URL") + Some("BUZZ_RELAY_URL") ); } #[test] fn session_prompt_request_format() { - let prompt_text = "[Sprout @mention]\nChannel: test\nFrom: npub1...\nMessage: hello"; + let prompt_text = "[Buzz @mention]\nChannel: test\nFrom: npub1...\nMessage: hello"; let msg = serde_json::json!({ "jsonrpc": "2.0", "id": 2u64, @@ -1469,7 +1469,7 @@ mod tests { "sess_abc123", &[ "/goal ship it", - "[Sprout event: @mention]\nContent: @Eva /goal ship it", + "[Buzz event: @mention]\nContent: @Eva /goal ship it", ], ); let prompt = params["prompt"].as_array().unwrap(); diff --git a/crates/sprout-acp/src/base_prompt.md b/crates/buzz-acp/src/base_prompt.md similarity index 58% rename from crates/sprout-acp/src/base_prompt.md rename to crates/buzz-acp/src/base_prompt.md index 9e5693536..64add5c4f 100644 --- a/crates/sprout-acp/src/base_prompt.md +++ b/crates/buzz-acp/src/base_prompt.md @@ -1,24 +1,24 @@ -You are operating inside the Sprout platform — a Nostr-based messaging platform for human-agent collaboration. The sprout-acp harness routes channel events to your session. +You are operating inside the Buzz platform — a Nostr-based messaging platform for human-agent collaboration. The buzz-acp harness routes channel events to your session. -## Sprout CLI +## Buzz CLI -The `sprout` CLI is your primary interface. Auth env vars: `SPROUT_RELAY_URL`, `SPROUT_PRIVATE_KEY`, `SPROUT_AUTH_TAG`. Exit codes: 0 ok, 1 user error, 2 network, 3 auth, 4 other. Output is structured JSON — pipe through `jq` as needed. +The `buzz` CLI is your primary interface. Auth env vars: `BUZZ_RELAY_URL`, `BUZZ_PRIVATE_KEY`, `BUZZ_AUTH_TAG`. Exit codes: 0 ok, 1 user error, 2 network, 3 auth, 4 other. Output is structured JSON — pipe through `jq` as needed. | Group | Key commands | |-------|-------------| -| `sprout messages` | `send`, `get`, `thread`, `search` | -| `sprout channels` | `list`, `get`, `create`, `join`, `members` | -| `sprout canvas` | `get`, `set` | -| `sprout reactions` | `add`, `remove` | -| `sprout dms` | `list`, `open` | -| `sprout users` | `get`, `set-profile`, `presence` | -| `sprout workflows` | `list`, `trigger`, `runs` | -| `sprout feed` | `get` | -| `sprout social` | `publish`, `notes` | -| `sprout repos` | `create`, `get`, `list` | -| `sprout upload` | `file` | - -Run `sprout --help` or `sprout --help` for full usage. +| `buzz messages` | `send`, `get`, `thread`, `search` | +| `buzz channels` | `list`, `get`, `create`, `join`, `members` | +| `buzz canvas` | `get`, `set` | +| `buzz reactions` | `add`, `remove` | +| `buzz dms` | `list`, `open` | +| `buzz users` | `get`, `set-profile`, `presence` | +| `buzz workflows` | `list`, `trigger`, `runs` | +| `buzz feed` | `get` | +| `buzz social` | `publish`, `notes` | +| `buzz repos` | `create`, `get`, `list` | +| `buzz upload` | `file` | + +Run `buzz --help` or `buzz --help` for full usage. ## Communication Patterns @@ -27,18 +27,18 @@ Run `sprout --help` or `sprout --help` for full usage. - Respond promptly to @mentions. - Be direct. State what you did, what you found, or what you need. No preamble. - Message content supports GitHub-flavored Markdown. Use fenced code blocks with a language tag (` ```python `, ` ```typescript `, etc.) for syntax-highlighted rendering on desktop and mobile. Omitting the language tag renders monochrome. -- Reply to the thread root (`sprout messages send --reply-to `), not the latest message — flat threads stay readable; reply chains bury context 3+ levels deep. One thread = one unit of work: ask sub-questions inline. A real tangent starts a new top-level message. +- Reply to the thread root (`buzz messages send --reply-to `), not the latest message — flat threads stay readable; reply chains bury context 3+ levels deep. One thread = one unit of work: ask sub-questions inline. A real tangent starts a new top-level message. - Work in the thread, report milestones at the root. The thread is the messy middle — progress, dead ends, clarifying questions, and routine updates. Use a top-level post for channel-visible milestones: picked up, blocked + need input, change ready / PR up, done, or anything teammates skimming only root-level messages must act on. Thread notifications are easy to miss; a top-level post ensures the requester sees the outcome. - New topic → new top-level message. Don't graft an unrelated task onto an existing thread. - When you are mentioned in multiple threads, prioritize the most recent one chronologically. If someone steers or redirects you in a newer thread while you are working from an older dispatch, reply in the newer thread to acknowledge — do not bury your response in the original thread where it may go unseen. -- No push notifications — poll with `sprout messages get --channel --since `. When `since` is set without `before`, results are oldest-first (chronological). +- No push notifications — poll with `buzz messages get --channel --since `. When `since` is set without `before`, results are oldest-first (chronological). ## Startup Recovery -1. `sprout feed get` — surface pending mentions and action items. Filter by type: `mentions`, `needs_action`, `activity`, `agent_activity`. -2. `sprout messages get --channel ` on assigned channels — catch up on recent history. +1. `buzz feed get` — surface pending mentions and action items. Filter by type: `mentions`, `needs_action`, `activity`, `agent_activity`. +2. `buzz messages get --channel ` on assigned channels — catch up on recent history. 3. Check `AGENTS.md` in your working directory for team context. -4. Check `RESEARCH/`, `GUIDES/`, `PLANS/` before searching externally. Use `sprout messages search --query "..."` for cross-channel keyword lookups. +4. Check `RESEARCH/`, `GUIDES/`, `PLANS/` before searching externally. Use `buzz messages search --query "..."` for cross-channel keyword lookups. ## Workspace Layout diff --git a/crates/sprout-acp/src/config.rs b/crates/buzz-acp/src/config.rs similarity index 93% rename from crates/sprout-acp/src/config.rs rename to crates/buzz-acp/src/config.rs index fdad2b4f6..bbbf7062c 100644 --- a/crates/sprout-acp/src/config.rs +++ b/crates/buzz-acp/src/config.rs @@ -1,4 +1,4 @@ -//! Configuration for the sprout-acp harness. +//! Configuration for the buzz-acp harness. //! //! CLI-first: every option is a CLI flag with env var fallback. //! Config file (TOML) for complex subscription rules. @@ -19,11 +19,11 @@ use crate::filter::SubscriptionRule; /// deprecated `--turn-timeout` is set. /// /// Sized for slow turns where the agent may go silent on its outer ACP channel -/// while running long sub-tools (e.g. a sprout-agent running another agent, or +/// while running long sub-tools (e.g. a buzz-agent running another agent, or /// codex/claude doing multi-minute single tool calls). 900s gives 300s of /// breathing room above the 600s max shell timeout, so legitimate long-running /// tool calls don't race the idle deadline. -/// Override via `--idle-timeout` / `SPROUT_ACP_IDLE_TIMEOUT`. +/// Override via `--idle-timeout` / `BUZZ_ACP_IDLE_TIMEOUT`. pub(crate) const DEFAULT_IDLE_TIMEOUT_SECS: u64 = 900; // ── Errors ──────────────────────────────────────────────────────────────────── @@ -153,25 +153,25 @@ impl std::fmt::Display for PermissionMode { // ── Models subcommand ───────────────────────────────────────────────────────── -/// CLI args for `sprout-acp models` — query available models from an agent. +/// CLI args for `buzz-acp models` — query available models from an agent. /// /// This is a standalone `Parser` (not a subcommand variant) because the /// `models` path must bypass `Config::from_cli()` entirely — no relay, /// no private key, no harness setup. #[derive(Debug, Parser)] #[command( - name = "sprout-acp models", + name = "buzz-acp models", about = "Query available models from the configured agent" )] pub struct ModelsArgs { /// Agent binary to spawn (e.g. "goose", "claude-agent-acp", "codex-acp"). - #[arg(long, env = "SPROUT_ACP_AGENT_COMMAND", default_value = "goose")] + #[arg(long, env = "BUZZ_ACP_AGENT_COMMAND", default_value = "goose")] pub agent_command: String, /// Arguments passed to the agent binary. #[arg( long, - env = "SPROUT_ACP_AGENT_ARGS", + env = "BUZZ_ACP_AGENT_ARGS", default_value = "acp", value_delimiter = ',' )] @@ -186,74 +186,74 @@ pub struct ModelsArgs { #[derive(Debug, Parser)] #[command( - name = "sprout-acp", - about = "ACP harness that bridges Sprout events to AI agents" + name = "buzz-acp", + about = "ACP harness that bridges Buzz events to AI agents" )] pub struct CliArgs { - #[arg(long, env = "SPROUT_RELAY_URL", default_value = "ws://localhost:3000")] + #[arg(long, env = "BUZZ_RELAY_URL", default_value = "ws://localhost:3000")] pub relay_url: String, - #[arg(long, env = "SPROUT_PRIVATE_KEY")] + #[arg(long, env = "BUZZ_PRIVATE_KEY")] pub private_key: String, /// Agent owner pubkey (64-char hex). Used for --respond-to=owner-only gate. - #[arg(long, env = "SPROUT_ACP_AGENT_OWNER")] + #[arg(long, env = "BUZZ_ACP_AGENT_OWNER")] pub agent_owner: Option, - #[arg(long, env = "SPROUT_ACP_AGENT_COMMAND", default_value = "goose")] + #[arg(long, env = "BUZZ_ACP_AGENT_COMMAND", default_value = "goose")] pub agent_command: String, #[arg( long, - env = "SPROUT_ACP_AGENT_ARGS", + env = "BUZZ_ACP_AGENT_ARGS", default_value = "acp", value_delimiter = ',' )] pub agent_args: Vec, - #[arg(long, env = "SPROUT_ACP_MCP_COMMAND", default_value = "")] + #[arg(long, env = "BUZZ_ACP_MCP_COMMAND", default_value = "")] pub mcp_command: String, /// Idle timeout: max seconds of silence before killing a turn. /// Resets on any agent stdout activity. - #[arg(long, env = "SPROUT_ACP_IDLE_TIMEOUT")] + #[arg(long, env = "BUZZ_ACP_IDLE_TIMEOUT")] pub idle_timeout: Option, /// Absolute wall-clock cap per turn (safety valve). - #[arg(long, env = "SPROUT_ACP_MAX_TURN_DURATION", default_value = "3600")] + #[arg(long, env = "BUZZ_ACP_MAX_TURN_DURATION", default_value = "3600")] pub max_turn_duration: u64, /// Deprecated: alias for --idle-timeout. If both set, --idle-timeout wins. - #[arg(long, env = "SPROUT_ACP_TURN_TIMEOUT", hide = true)] + #[arg(long, env = "BUZZ_ACP_TURN_TIMEOUT", hide = true)] pub turn_timeout: Option, #[arg( long, - env = "SPROUT_ACP_SYSTEM_PROMPT", + env = "BUZZ_ACP_SYSTEM_PROMPT", conflicts_with = "system_prompt_file" )] pub system_prompt: Option, #[arg( long, - env = "SPROUT_ACP_SYSTEM_PROMPT_FILE", + env = "BUZZ_ACP_SYSTEM_PROMPT_FILE", conflicts_with = "system_prompt" )] pub system_prompt_file: Option, /// Number of parallel agent subprocesses. - #[arg(long, env = "SPROUT_ACP_AGENTS", default_value_t = 1, + #[arg(long, env = "BUZZ_ACP_AGENTS", default_value_t = 1, value_parser = clap::value_parser!(u32).range(1..=32))] pub agents: u32, /// Seconds between heartbeat prompts. 0 = disabled. - #[arg(long, env = "SPROUT_ACP_HEARTBEAT_INTERVAL", default_value_t = 0)] + #[arg(long, env = "BUZZ_ACP_HEARTBEAT_INTERVAL", default_value_t = 0)] pub heartbeat_interval: u64, /// Heartbeat prompt text. Conflicts with --heartbeat-prompt-file. #[arg( long, - env = "SPROUT_ACP_HEARTBEAT_PROMPT", + env = "BUZZ_ACP_HEARTBEAT_PROMPT", conflicts_with = "heartbeat_prompt_file" )] pub heartbeat_prompt: Option, @@ -261,35 +261,35 @@ pub struct CliArgs { /// Read heartbeat prompt from file. #[arg( long, - env = "SPROUT_ACP_HEARTBEAT_PROMPT_FILE", + env = "BUZZ_ACP_HEARTBEAT_PROMPT_FILE", conflicts_with = "heartbeat_prompt" )] pub heartbeat_prompt_file: Option, - #[arg(long, env = "SPROUT_ACP_INITIAL_MESSAGE")] + #[arg(long, env = "BUZZ_ACP_INITIAL_MESSAGE")] pub initial_message: Option, #[arg( long, - env = "SPROUT_ACP_SUBSCRIBE", + env = "BUZZ_ACP_SUBSCRIBE", default_value = "mentions", value_enum )] pub subscribe: SubscribeMode, - #[arg(long, env = "SPROUT_ACP_KINDS", value_delimiter = ',')] + #[arg(long, env = "BUZZ_ACP_KINDS", value_delimiter = ',')] pub kinds: Option>, - #[arg(long, env = "SPROUT_ACP_CHANNELS", value_delimiter = ',')] + #[arg(long, env = "BUZZ_ACP_CHANNELS", value_delimiter = ',')] pub channels: Option>, - #[arg(long, env = "SPROUT_ACP_NO_MENTION_FILTER")] + #[arg(long, env = "BUZZ_ACP_NO_MENTION_FILTER")] pub no_mention_filter: bool, - #[arg(long, env = "SPROUT_ACP_CONFIG", default_value = "./sprout-acp.toml")] + #[arg(long, env = "BUZZ_ACP_CONFIG", default_value = "./buzz-acp.toml")] pub config: PathBuf, - #[arg(long, env = "SPROUT_ACP_DEDUP", default_value = "queue", value_enum)] + #[arg(long, env = "BUZZ_ACP_DEDUP", default_value = "queue", value_enum)] pub dedup: DedupMode, /// How to handle new @mentions while a turn is already in-flight. @@ -297,33 +297,33 @@ pub struct CliArgs { /// owner-interrupt: cancel only for agent owner's mentions. #[arg( long, - env = "SPROUT_ACP_MULTIPLE_EVENT_HANDLING", + env = "BUZZ_ACP_MULTIPLE_EVENT_HANDLING", default_value = "queue", value_enum )] pub multiple_event_handling: MultipleEventHandling, - #[arg(long, env = "SPROUT_ACP_NO_IGNORE_SELF")] + #[arg(long, env = "BUZZ_ACP_NO_IGNORE_SELF")] pub no_ignore_self: bool, /// Maximum number of context messages to include for thread replies and DMs. /// Set to 0 to disable automatic context fetching. Max 100. - #[arg(long, env = "SPROUT_ACP_CONTEXT_MESSAGE_LIMIT", default_value_t = 12, + #[arg(long, env = "BUZZ_ACP_CONTEXT_MESSAGE_LIMIT", default_value_t = 12, value_parser = clap::value_parser!(u32).range(0..=100))] pub context_message_limit: u32, /// Maximum turns per session before proactive rotation. 0 = disabled /// (rotate only on MaxTokens / MaxTurnRequests). - #[arg(long, env = "SPROUT_ACP_MAX_TURNS_PER_SESSION", default_value_t = 0, + #[arg(long, env = "BUZZ_ACP_MAX_TURNS_PER_SESSION", default_value_t = 0, value_parser = clap::value_parser!(u32))] pub max_turns_per_session: u32, /// Disable automatic presence (online/offline) status. - #[arg(long, env = "SPROUT_ACP_NO_PRESENCE")] + #[arg(long, env = "BUZZ_ACP_NO_PRESENCE")] pub no_presence: bool, /// Disable typing indicators while agent is processing. - #[arg(long, env = "SPROUT_ACP_NO_TYPING")] + #[arg(long, env = "BUZZ_ACP_NO_TYPING")] pub no_typing: bool, /// Enable NIP-AE agent core memory injection. @@ -331,13 +331,13 @@ pub struct CliArgs { /// Memory injection is on by default. When enabled, the harness /// fetches the agent's per-session core engram and renders it as an /// `[Agent Memory — core]` prompt section (or renders the onboarding nudge - /// when the relay confirms no core engram exists). The `sprout mem` CLI + /// when the relay confirms no core engram exists). The `buzz mem` CLI /// and the relay's acceptance of kind:30174 engrams are unaffected — this /// flag controls prompt-time injection in the ACP harness only. - /// Pass `--no-memory` / `SPROUT_ACP_NO_MEMORY=true` to disable. + /// Pass `--no-memory` / `BUZZ_ACP_NO_MEMORY=true` to disable. #[arg( long, - env = "SPROUT_ACP_MEMORY", + env = "BUZZ_ACP_MEMORY", conflicts_with = "no_memory", default_value_t = true )] @@ -346,26 +346,26 @@ pub struct CliArgs { /// Disable NIP-AE agent core memory injection. /// /// Memory injection is on by default; set this flag/env var to opt out. - #[arg(long, env = "SPROUT_ACP_NO_MEMORY", conflicts_with = "memory")] + #[arg(long, env = "BUZZ_ACP_NO_MEMORY", conflicts_with = "memory")] pub no_memory: bool, /// Disable the [Base] platform-context section prepended to every prompt. - /// When set, agents receive only the persona [System] prompt with no Sprout orientation. - #[arg(long, env = "SPROUT_ACP_NO_BASE_PROMPT")] + /// When set, agents receive only the persona [System] prompt with no Buzz orientation. + #[arg(long, env = "BUZZ_ACP_NO_BASE_PROMPT")] pub no_base_prompt: bool, /// Path to a custom base prompt file. Overrides the compiled-in default. /// Mutually exclusive with --no-base-prompt. #[arg( long, - env = "SPROUT_ACP_BASE_PROMPT_FILE", + env = "BUZZ_ACP_BASE_PROMPT_FILE", conflicts_with = "no_base_prompt" )] pub base_prompt_file: Option, /// Desired LLM model ID. Applied to every new ACP session after creation. - /// Use `sprout-acp models` to discover available model IDs. - #[arg(long, env = "SPROUT_ACP_MODEL")] + /// Use `buzz-acp models` to discover available model IDs. + #[arg(long, env = "BUZZ_ACP_MODEL")] pub model: Option, /// Permission mode for agents that support `session/set_config_option` @@ -376,7 +376,7 @@ pub struct CliArgs { /// behaviour. #[arg( long, - env = "SPROUT_ACP_PERMISSION_MODE", + env = "BUZZ_ACP_PERMISSION_MODE", default_value = "bypass-permissions", value_enum )] @@ -386,7 +386,7 @@ pub struct CliArgs { /// Modes: owner-only (default), allowlist, anyone, nobody. #[arg( long, - env = "SPROUT_ACP_RESPOND_TO", + env = "BUZZ_ACP_RESPOND_TO", default_value = "owner-only", value_enum )] @@ -394,20 +394,20 @@ pub struct CliArgs { /// Comma-separated 64-char hex pubkeys for allowlist mode. /// Owner pubkey is always implicitly included. - #[arg(long, env = "SPROUT_ACP_RESPOND_TO_ALLOWLIST", value_delimiter = ',')] + #[arg(long, env = "BUZZ_ACP_RESPOND_TO_ALLOWLIST", value_delimiter = ',')] pub respond_to_allowlist: Option>, /// Path to a persona pack directory. Used with --persona-name to configure /// the agent from a .persona.md pack instead of CLI flags. - #[arg(long, env = "SPROUT_ACP_PERSONA_PACK")] + #[arg(long, env = "BUZZ_ACP_PERSONA_PACK")] pub persona_pack: Option, /// Name of the persona within the pack to use. Required when --persona-pack is set. - #[arg(long, env = "SPROUT_ACP_PERSONA_NAME")] + #[arg(long, env = "BUZZ_ACP_PERSONA_NAME")] pub persona_name: Option, /// Publish encrypted ACP observer frames over the relay. - #[arg(long, env = "SPROUT_ACP_RELAY_OBSERVER", default_value_t = false)] + #[arg(long, env = "BUZZ_ACP_RELAY_OBSERVER", default_value_t = false)] pub relay_observer: bool, } @@ -454,7 +454,7 @@ pub struct Config { /// Whether NIP-AE agent core memory injection is enabled. When false, /// the harness skips the per-session core engram fetch and renders no /// `[Agent Memory — core]` section. On by default; disabled via the - /// `--no-memory` / `SPROUT_ACP_NO_MEMORY` opt-out. + /// `--no-memory` / `BUZZ_ACP_NO_MEMORY` opt-out. pub memory_enabled: bool, /// Desired LLM model ID. Applied after every `session_new_full()`. pub model: Option, @@ -464,7 +464,7 @@ pub struct Config { pub respond_to: RespondTo, /// Validated allowlist of pubkey hex strings (used when respond_to == Allowlist). pub respond_to_allowlist: HashSet, - /// Per-persona env vars to inject at agent spawn time (e.g., GOOSE_PROVIDER, GOOSE_MODEL, SPROUT_AGENT_MODEL). + /// Per-persona env vars to inject at agent spawn time (e.g., GOOSE_PROVIDER, GOOSE_MODEL, BUZZ_AGENT_MODEL). /// Populated from persona pack resolution. Empty when no pack is configured. pub persona_env_vars: Vec<(String, String)>, /// Whether to publish encrypted observer frames through the relay. @@ -517,7 +517,7 @@ fn default_agent_args(command: &str) -> Option> { match normalize_agent_command_identity(command).as_str() { "goose" => Some(vec!["acp".to_string()]), "codex" | "codex-acp" | "claude-agent-acp" | "claude-code-acp" | "claude-code" - | "claudecode" | "sprout-agent" => Some(Vec::new()), + | "claudecode" | "buzz-agent" => Some(Vec::new()), _ => None, } } @@ -560,8 +560,8 @@ pub fn normalize_agent_args(command: &str, agent_args: Vec) -> Vec { tracing::warn!( - "--turn-timeout / SPROUT_ACP_TURN_TIMEOUT is deprecated and ignored \ - when --idle-timeout / SPROUT_ACP_IDLE_TIMEOUT is also set" + "--turn-timeout / BUZZ_ACP_TURN_TIMEOUT is deprecated and ignored \ + when --idle-timeout / BUZZ_ACP_IDLE_TIMEOUT is also set" ); idle } (Some(idle), None) => idle, (None, Some(turn)) => { tracing::warn!( - "--turn-timeout / SPROUT_ACP_TURN_TIMEOUT is deprecated; \ - use --idle-timeout / SPROUT_ACP_IDLE_TIMEOUT instead" + "--turn-timeout / BUZZ_ACP_TURN_TIMEOUT is deprecated; \ + use --idle-timeout / BUZZ_ACP_IDLE_TIMEOUT instead" ); turn } @@ -743,7 +743,7 @@ impl Config { let (persona_system_prompt, persona_model, persona_env_vars) = match (&args.persona_pack, &args.persona_name) { (Some(pack_dir), Some(name)) => { - let pack = sprout_persona::resolve::resolve_pack(pack_dir).map_err(|e| { + let pack = buzz_persona::resolve::resolve_pack(pack_dir).map_err(|e| { ConfigError::ConfigFile(format!( "failed to resolve pack {}: {e}", pack_dir.display() @@ -966,7 +966,7 @@ pub fn resolve_channel_filters( discovered_channels: &[Uuid], rules: &[SubscriptionRule], ) -> HashMap { - use sprout_core::kind::{ + use buzz_core::kind::{ KIND_STREAM_MESSAGE, KIND_STREAM_REMINDER, KIND_WORKFLOW_APPROVAL_REQUESTED, }; @@ -1068,7 +1068,7 @@ pub fn resolve_dynamic_channel_filter( channel_id: Uuid, rules: &[crate::filter::SubscriptionRule], ) -> Option { - use sprout_core::kind::{ + use buzz_core::kind::{ KIND_STREAM_MESSAGE, KIND_STREAM_REMINDER, KIND_WORKFLOW_APPROVAL_REQUESTED, }; @@ -1183,7 +1183,7 @@ mod tests { kinds_override: None, channels_override: None, no_mention_filter: false, - config_path: PathBuf::from("./sprout-acp.toml"), + config_path: PathBuf::from("./buzz-acp.toml"), context_message_limit: 12, max_turns_per_session: 0, presence_enabled: true, @@ -1234,9 +1234,9 @@ mod tests { let f = result.get(ch).expect("channel should be present"); assert!(f.require_mention, "mentions mode requires mention"); let kinds = f.kinds.as_ref().expect("should have kinds"); - assert!(kinds.contains(&sprout_core::kind::KIND_STREAM_MESSAGE)); - assert!(kinds.contains(&sprout_core::kind::KIND_WORKFLOW_APPROVAL_REQUESTED)); - assert!(kinds.contains(&sprout_core::kind::KIND_STREAM_REMINDER)); + assert!(kinds.contains(&buzz_core::kind::KIND_STREAM_MESSAGE)); + assert!(kinds.contains(&buzz_core::kind::KIND_WORKFLOW_APPROVAL_REQUESTED)); + assert!(kinds.contains(&buzz_core::kind::KIND_STREAM_REMINDER)); } } @@ -1309,13 +1309,13 @@ mod tests { } #[test] - fn normalizes_sprout_agent_args_to_empty() { + fn normalizes_buzz_agent_args_to_empty() { assert_eq!( - normalize_agent_args("sprout-agent", Vec::new()), + normalize_agent_args("buzz-agent", Vec::new()), Vec::::new() ); assert_eq!( - normalize_agent_args("sprout-agent", vec!["acp".into()]), + normalize_agent_args("buzz-agent", vec!["acp".into()]), Vec::::new() ); } @@ -1556,7 +1556,7 @@ mod tests { #[test] fn test_load_rules_valid_toml() { - let dir = std::env::temp_dir().join("sprout-acp-test-valid"); + let dir = std::env::temp_dir().join("buzz-acp-test-valid"); let path = dir.join("rules.toml"); std::fs::create_dir_all(&dir).unwrap(); std::fs::write( @@ -1579,7 +1579,7 @@ require_mention = false #[test] fn test_load_rules_empty_name_rejected() { - let dir = std::env::temp_dir().join("sprout-acp-test-empty-name"); + let dir = std::env::temp_dir().join("buzz-acp-test-empty-name"); let path = dir.join("rules.toml"); std::fs::create_dir_all(&dir).unwrap(); std::fs::write( @@ -1599,7 +1599,7 @@ channels = "all" #[test] fn test_load_rules_duplicate_name_rejected() { - let dir = std::env::temp_dir().join("sprout-acp-test-dup-name"); + let dir = std::env::temp_dir().join("buzz-acp-test-dup-name"); let path = dir.join("rules.toml"); std::fs::create_dir_all(&dir).unwrap(); std::fs::write( @@ -1623,7 +1623,7 @@ channels = "all" #[test] fn test_load_rules_invalid_filter_rejected() { - let dir = std::env::temp_dir().join("sprout-acp-test-bad-filter"); + let dir = std::env::temp_dir().join("buzz-acp-test-bad-filter"); let path = dir.join("rules.toml"); std::fs::create_dir_all(&dir).unwrap(); // evalexpr rejects unbalanced parens at parse time. @@ -1645,7 +1645,7 @@ filter = "(((" #[test] fn test_load_rules_channel_scope_typo_rejected() { - let dir = std::env::temp_dir().join("sprout-acp-test-scope-typo"); + let dir = std::env::temp_dir().join("buzz-acp-test-scope-typo"); let path = dir.join("rules.toml"); std::fs::create_dir_all(&dir).unwrap(); std::fs::write( @@ -1665,7 +1665,7 @@ channels = "ALL" #[test] fn test_load_rules_too_many_rules_rejected() { - let dir = std::env::temp_dir().join("sprout-acp-test-too-many"); + let dir = std::env::temp_dir().join("buzz-acp-test-too-many"); let path = dir.join("rules.toml"); std::fs::create_dir_all(&dir).unwrap(); let mut toml = String::new(); @@ -1683,7 +1683,7 @@ channels = "ALL" #[test] fn test_load_rules_filter_too_long_rejected() { - let dir = std::env::temp_dir().join("sprout-acp-test-long-filter"); + let dir = std::env::temp_dir().join("buzz-acp-test-long-filter"); let path = dir.join("rules.toml"); std::fs::create_dir_all(&dir).unwrap(); let long_expr = format!("\"{}\"", "a".repeat(4097)); @@ -1890,7 +1890,7 @@ channels = "ALL" #[test] fn test_permission_mode_value_enum_camel_case_aliases() { // Operators may set env vars using the camelCase wire-format strings - // (e.g. SPROUT_ACP_PERMISSION_MODE=bypassPermissions). The #[value(alias)] + // (e.g. BUZZ_ACP_PERMISSION_MODE=bypassPermissions). The #[value(alias)] // attributes ensure these parse correctly. use clap::ValueEnum; let cases = [ diff --git a/crates/sprout-acp/src/engram_fetch.rs b/crates/buzz-acp/src/engram_fetch.rs similarity index 96% rename from crates/sprout-acp/src/engram_fetch.rs rename to crates/buzz-acp/src/engram_fetch.rs index 982f2a78a..534d05837 100644 --- a/crates/sprout-acp/src/engram_fetch.rs +++ b/crates/buzz-acp/src/engram_fetch.rs @@ -11,9 +11,9 @@ //! overwrite real, just-unreachable memory with a fresh profile. //! - Either way, session creation is never blocked. +use buzz_core::engram::{conversation_key, d_tag, select_head, validate_and_decrypt, Body}; +use buzz_core::kind::KIND_AGENT_ENGRAM; use nostr::{Event, Keys, PublicKey}; -use sprout_core::engram::{conversation_key, d_tag, select_head, validate_and_decrypt, Body}; -use sprout_core::kind::KIND_AGENT_ENGRAM; use crate::relay::RestClient; @@ -22,10 +22,10 @@ const SECTION_LABEL: &str = "Agent Memory — core"; /// Onboarding nudge for new agents with no core yet. /// -/// Wording is from Tyler's brief: "No core memory found. Use `sprout mem` +/// Wording is from Tyler's brief: "No core memory found. Use `buzz mem` /// to create a core memory. Ask your user about yourself." pub const ONBOARDING_NUDGE: &str = "No core memory found. \ -Use `sprout mem set core \"…\"` to create one (it will hold your identity, \ +Use `buzz mem set core \"…\"` to create one (it will hold your identity, \ rules, and goals across sessions). Ask your user about yourself."; /// Build the rendered prompt section for the agent's core. @@ -68,7 +68,7 @@ async fn fetch_core_body( owner: &PublicKey, ) -> Result, String> { let k_c = conversation_key(agent_keys.secret_key(), owner); - let d = d_tag(&k_c, sprout_core::engram::CORE_SLUG); + let d = d_tag(&k_c, buzz_core::engram::CORE_SLUG); let filter = nostr::Filter::new() .kind(nostr::Kind::Custom(KIND_AGENT_ENGRAM as u16)) @@ -166,8 +166,8 @@ fn decode_core_body( #[cfg(test)] mod tests { use super::*; + use buzz_core::engram::{build_event, Body}; use serde_json::json; - use sprout_core::engram::{build_event, Body}; /// Empty array → confirmed absence → Ok(None), so the caller emits the /// onboarding nudge. This is the only path that maps to "no core." diff --git a/crates/sprout-acp/src/filter.rs b/crates/buzz-acp/src/filter.rs similarity index 99% rename from crates/sprout-acp/src/filter.rs rename to crates/buzz-acp/src/filter.rs index 1b901524a..a222b0e84 100644 --- a/crates/sprout-acp/src/filter.rs +++ b/crates/buzz-acp/src/filter.rs @@ -203,7 +203,7 @@ static FILTER_EVAL_SEMAPHORE: std::sync::LazyLock> = /// `node.eval_boolean_with_context()` instead of re-parsing the expression /// string on every call (finding #34). /// - Registers custom string helpers: `str_contains`, `str_starts_with`, -/// `str_ends_with`, `str_len` (duplicated intentionally from sprout-workflow). +/// `str_ends_with`, `str_len` (duplicated intentionally from buzz-workflow). pub async fn evaluate_filter( expr: &str, ctx: &FilterContext, @@ -269,8 +269,8 @@ pub async fn evaluate_filter( /// | `timestamp` | int | `event.created_at` | /// /// Also registers `str_contains`, `str_starts_with`, `str_ends_with`, -/// `str_len` — duplicated from sprout-workflow intentionally so this crate -/// has no runtime dependency on sprout-workflow. +/// `str_len` — duplicated from buzz-workflow intentionally so this crate +/// has no runtime dependency on buzz-workflow. fn build_eval_context(ctx: &FilterContext) -> Result { use evalexpr::*; diff --git a/crates/sprout-acp/src/lib.rs b/crates/buzz-acp/src/lib.rs similarity index 95% rename from crates/sprout-acp/src/lib.rs rename to crates/buzz-acp/src/lib.rs index da8007113..a53192654 100644 --- a/crates/sprout-acp/src/lib.rs +++ b/crates/buzz-acp/src/lib.rs @@ -15,6 +15,13 @@ use std::time::Duration; use acp::{AcpClient, EnvVar, McpServer}; use anyhow::Result; +use buzz_core::kind::{ + KIND_MEMBER_ADDED_NOTIFICATION, KIND_MEMBER_REMOVED_NOTIFICATION, KIND_STREAM_MESSAGE, + KIND_STREAM_REMINDER, KIND_WORKFLOW_APPROVAL_REQUESTED, +}; +use buzz_core::observer::{ + decrypt_observer_payload, encrypt_observer_payload, OBSERVER_FRAME_TELEMETRY, +}; use clap::Parser; use config::{Config, DedupMode, ModelsArgs, MultipleEventHandling, RespondTo, SubscribeMode}; use filter::SubscriptionRule; @@ -26,13 +33,6 @@ use pool::{ }; use queue::{prepend_base_prompt, EventQueue, QueuedEvent, ThreadTags}; use relay::{HarnessRelay, RelayEventPublisher}; -use sprout_core::kind::{ - KIND_MEMBER_ADDED_NOTIFICATION, KIND_MEMBER_REMOVED_NOTIFICATION, KIND_STREAM_MESSAGE, - KIND_STREAM_REMINDER, KIND_WORKFLOW_APPROVAL_REQUESTED, -}; -use sprout_core::observer::{ - decrypt_observer_payload, encrypt_observer_payload, OBSERVER_FRAME_TELEMETRY, -}; use tokio::sync::{mpsc, watch}; use tracing_subscriber::EnvFilter; use uuid::Uuid; @@ -46,12 +46,12 @@ use uuid::Uuid; /// `ModelsArgs` parser; the default path uses the existing `CliArgs`. /// /// **Constraint**: subcommand must be argv[1] — flags before the subcommand -/// name (e.g., `sprout-acp --verbose models`) are not supported. +/// name (e.g., `buzz-acp --verbose models`) are not supported. fn is_subcommand(name: &str) -> bool { std::env::args().nth(1).map(|a| a == name).unwrap_or(false) } -/// Timeout for the `sprout-acp models` subcommand (spawn + init + session/new). +/// Timeout for the `buzz-acp models` subcommand (spawn + init + session/new). const MODELS_TIMEOUT: Duration = Duration::from_secs(10); // ── Presence helper ─────────────────────────────────────────────────────────── @@ -69,8 +69,8 @@ async fn publish_presence( keys: &nostr::Keys, status: &str, ) -> Result<(), relay::RelayError> { + use buzz_core::kind::KIND_PRESENCE_UPDATE; use nostr::{EventBuilder, Kind}; - use sprout_core::kind::KIND_PRESENCE_UPDATE; let event = EventBuilder::new(Kind::Custom(KIND_PRESENCE_UPDATE as u16), status) .tags([]) @@ -85,22 +85,22 @@ async fn publish_presence( /// Resolve the agent's owner pubkey at startup. /// /// Priority: -/// 1. `SPROUT_AUTH_TAG` env var — NIP-OA attestation signed by the owner. +/// 1. `BUZZ_AUTH_TAG` env var — NIP-OA attestation signed by the owner. /// Verified against the agent's own pubkey to extract the owner pubkey. -/// 2. `--agent-owner` CLI flag / `SPROUT_ACP_AGENT_OWNER` env var. +/// 2. `--agent-owner` CLI flag / `BUZZ_ACP_AGENT_OWNER` env var. fn resolve_agent_owner(config: &Config) -> Option { - // Try SPROUT_AUTH_TAG first (NIP-OA attestation). - if let Ok(auth_tag) = std::env::var("SPROUT_AUTH_TAG") { + // Try BUZZ_AUTH_TAG first (NIP-OA attestation). + if let Ok(auth_tag) = std::env::var("BUZZ_AUTH_TAG") { if !auth_tag.is_empty() { let agent_pk = config.keys.public_key(); - match sprout_sdk::nip_oa::verify_auth_tag(&auth_tag, &agent_pk) { + match buzz_sdk::nip_oa::verify_auth_tag(&auth_tag, &agent_pk) { Ok(owner_pk) => { let owner_hex = owner_pk.to_hex().to_ascii_lowercase(); - tracing::info!("owner resolved from SPROUT_AUTH_TAG: {owner_hex}"); + tracing::info!("owner resolved from BUZZ_AUTH_TAG: {owner_hex}"); return Some(owner_hex); } Err(e) => { - tracing::warn!("SPROUT_AUTH_TAG verification failed: {e} — falling back"); + tracing::warn!("BUZZ_AUTH_TAG verification failed: {e} — falling back"); } } } @@ -246,7 +246,7 @@ async fn check_sibling_via_profile( } // Cryptographically verify the NIP-OA attestation signature. let tag_json = serde_json::to_string(tag).unwrap_or_default(); - match sprout_sdk::nip_oa::verify_auth_tag(&tag_json, &agent_pk) { + match buzz_sdk::nip_oa::verify_auth_tag(&tag_json, &agent_pk) { Ok(_) => { tracing::debug!(author, expected_owner, "sibling verified via NIP-OA"); return true; @@ -455,7 +455,7 @@ async fn publish_relay_observer_event( return; } }; - let builder = match sprout_sdk::build_agent_observer_frame( + let builder = match buzz_sdk::build_agent_observer_frame( owner_pubkey_hex, agent_pubkey_hex, OBSERVER_FRAME_TELEMETRY, @@ -490,7 +490,7 @@ fn handle_relay_observer_control_event( owner_pubkey_hex: &str, ) { // Defense-in-depth: verify signature even though the relay already checked. - if let Err(e) = sprout_core::verify_event(&event) { + if let Err(e) = buzz_core::verify_event(&event) { tracing::warn!(error = %e, "observer control frame failed signature verification"); return; } @@ -789,13 +789,13 @@ async fn tokio_main() -> Result<()> { tracing_subscriber::fmt() .with_env_filter( - EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("sprout_acp=info")), + EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("buzz_acp=info")), ) .compact() .init(); let mut config = Config::from_cli().map_err(|e| anyhow::anyhow!("configuration error: {e}"))?; - tracing::info!("sprout-acp starting: {}", config.summary()); + tracing::info!("buzz-acp starting: {}", config.summary()); let observer = config .relay_observer @@ -889,7 +889,7 @@ async fn tokio_main() -> Result<()> { tracing::info!("agent_pool_ready agents={}", live_count); let mut pool = AgentPool::from_slots(agent_slots); - // ── Step 2: Connect to Sprout relay ────────────────────────────────────── + // ── Step 2: Connect to Buzz relay ────────────────────────────────────── // // Finding #22: capture a startup watermark BEFORE connecting to the relay. // This timestamp is used for membership notification replay (via @@ -904,11 +904,11 @@ async fn tokio_main() -> Result<()> { let pubkey_hex = config.keys.public_key().to_hex(); - // Parse SPROUT_AUTH_TAG into a nostr::Tag for NIP-OA relay membership delegation. - let relay_auth_tag: Option = std::env::var("SPROUT_AUTH_TAG") + // Parse BUZZ_AUTH_TAG into a nostr::Tag for NIP-OA relay membership delegation. + let relay_auth_tag: Option = std::env::var("BUZZ_AUTH_TAG") .ok() .filter(|s| !s.is_empty()) - .and_then(|s| sprout_sdk::nip_oa::parse_auth_tag(&s).ok()); + .and_then(|s| buzz_sdk::nip_oa::parse_auth_tag(&s).ok()); let mut relay = HarnessRelay::connect(&config.relay_url, &config.keys, &pubkey_hex, relay_auth_tag) @@ -943,7 +943,7 @@ async fn tokio_main() -> Result<()> { } // ── Step 2d: Resolve agent owner ──────────────────────────────────────── - // Priority: SPROUT_AUTH_TAG (NIP-OA attestation) → --agent-owner flag. + // Priority: BUZZ_AUTH_TAG (NIP-OA attestation) → --agent-owner flag. let startup_owner: Option = resolve_agent_owner(&config); if let Some(ref owner) = startup_owner { tracing::info!("agent owner: {owner}"); @@ -956,7 +956,7 @@ async fn tokio_main() -> Result<()> { RespondTo::OwnerOnly => { tracing::warn!( "respond-to=owner-only but no owner is set — all events will be \ - dropped. Set SPROUT_AUTH_TAG or --agent-owner, or use --respond-to=anyone." + dropped. Set BUZZ_AUTH_TAG or --agent-owner, or use --respond-to=anyone." ); } RespondTo::Allowlist => { @@ -1104,7 +1104,7 @@ async fn tokio_main() -> Result<()> { if !config.memory_enabled { tracing::info!( target: "engram::core", - "NIP-AE core memory injection disabled (re-enable by removing --no-memory / SPROUT_ACP_NO_MEMORY)" + "NIP-AE core memory injection disabled (re-enable by removing --no-memory / BUZZ_ACP_NO_MEMORY)" ); } @@ -1349,19 +1349,19 @@ async fn tokio_main() -> Result<()> { None } // Remaining branches don't touch pool — evaluated when pool is idle. - sprout_event = relay.next_event() => { + buzz_event = relay.next_event() => { let _ = result_rx; // end split borrow before relay handling - match sprout_event { - Some(sprout_event) => { - let kind_u32 = sprout_event.event.kind.as_u16() as u32; + match buzz_event { + Some(buzz_event) => { + let kind_u32 = buzz_event.event.kind.as_u16() as u32; // ── Membership notification handling ────────────── if kind_u32 == KIND_MEMBER_ADDED_NOTIFICATION || kind_u32 == KIND_MEMBER_REMOVED_NOTIFICATION { - let ch = sprout_event.channel_id; - let ts = sprout_event.event.created_at.as_secs(); - let eid = sprout_event.event.id.to_hex(); + let ch = buzz_event.channel_id; + let ts = buzz_event.event.created_at.as_secs(); + let eid = buzz_event.event.id.to_hex(); // Two-layer membership dedup: // @@ -1464,26 +1464,26 @@ async fn tokio_main() -> Result<()> { } // ── End membership notification handling ────────── - if config.ignore_self && sprout_event.event.pubkey.to_hex() == pubkey_hex { - tracing::debug!(channel_id = %sprout_event.channel_id, "dropping self-authored event"); + if config.ignore_self && buzz_event.event.pubkey.to_hex() == pubkey_hex { + tracing::debug!(channel_id = %buzz_event.channel_id, "dropping self-authored event"); continue; } // ── Shutdown command handling ───────────────────── // Check: kind:9, content "!shutdown", from owner, mentions THIS agent. let is_shutdown = kind_u32 == KIND_STREAM_MESSAGE - && sprout_event.event.content.trim() == "!shutdown" - && sprout_event.event.tags.iter().any(|t| { + && buzz_event.event.content.trim() == "!shutdown" + && buzz_event.event.tags.iter().any(|t| { t.as_slice().first().map(|s| s.as_str()) == Some("p") && t.as_slice().get(1).map(|s| s.as_str()) == Some(pubkey_hex.as_str()) }); if is_shutdown { let owner = owner_cache.get(); if let Some(owner) = owner { - if sprout_event.event.pubkey.to_hex() == *owner { + if buzz_event.event.pubkey.to_hex() == *owner { tracing::info!( - channel_id = %sprout_event.channel_id, - sender = %sprout_event.event.pubkey.to_hex(), + channel_id = %buzz_event.channel_id, + sender = %buzz_event.event.pubkey.to_hex(), "shutdown command from owner — exiting gracefully" ); let _ = shutdown_tx.send(()); @@ -1505,18 +1505,18 @@ async fn tokio_main() -> Result<()> { // --multiple-event-handling. It is explicit user // intent, not an automatic policy decision. let is_cancel = kind_u32 == KIND_STREAM_MESSAGE - && sprout_event.event.content.trim() == "!cancel" - && sprout_event.event.tags.iter().any(|t| { + && buzz_event.event.content.trim() == "!cancel" + && buzz_event.event.tags.iter().any(|t| { t.as_slice().first().map(|s| s.as_str()) == Some("p") && t.as_slice().get(1).map(|s| s.as_str()) == Some(pubkey_hex.as_str()) }); if is_cancel { if let Some(owner) = owner_cache.get() { - if sprout_event.event.pubkey.to_hex() == *owner { - let fired = cancel_in_flight_task(&mut pool, sprout_event.channel_id, CancelMode::Stop); + if buzz_event.event.pubkey.to_hex() == *owner { + let fired = cancel_in_flight_task(&mut pool, buzz_event.channel_id, CancelMode::Stop); if !fired { tracing::warn!( - channel_id = %sprout_event.channel_id, + channel_id = %buzz_event.channel_id, "!cancel received but no in-flight task — no-op" ); } @@ -1539,7 +1539,7 @@ async fn tokio_main() -> Result<()> { // same human). Allowlist is unchanged: owner + // explicit pubkey list only. { - let author = sprout_event.event.pubkey.to_hex(); + let author = buzz_event.event.pubkey.to_hex(); let allowed = match &config.respond_to { RespondTo::Anyone => true, RespondTo::Nobody => false, @@ -1554,8 +1554,8 @@ async fn tokio_main() -> Result<()> { }; if !allowed { tracing::debug!( - channel_id = %sprout_event.channel_id, - author = %sprout_event.event.pubkey.to_hex(), + channel_id = %buzz_event.channel_id, + author = %buzz_event.event.pubkey.to_hex(), mode = %config.respond_to, "inbound author gate — dropping event" ); @@ -1564,21 +1564,21 @@ async fn tokio_main() -> Result<()> { } // ── End inbound author gate ────────────────────── - let matched = filter::match_event(&sprout_event.event, sprout_event.channel_id, &rules, &pubkey_hex).await; + let matched = filter::match_event(&buzz_event.event, buzz_event.channel_id, &rules, &pubkey_hex).await; let prompt_tag = match matched { Some(m) => m.prompt_tag, None => { - tracing::debug!(channel_id = %sprout_event.channel_id, kind = sprout_event.event.kind.as_u16(), "event matched no rule — dropping"); + tracing::debug!(channel_id = %buzz_event.channel_id, kind = buzz_event.event.kind.as_u16(), "event matched no rule — dropping"); continue; } }; // Capture author pubkey before queue.push() moves - // sprout_event.event (needed for mode gate below). - let author_hex = sprout_event.event.pubkey.to_hex(); - let event_id_hex = sprout_event.event.id.to_hex(); + // buzz_event.event (needed for mode gate below). + let author_hex = buzz_event.event.pubkey.to_hex(); + let event_id_hex = buzz_event.event.id.to_hex(); let accepted = queue.push(QueuedEvent { - channel_id: sprout_event.channel_id, - event: sprout_event.event, + channel_id: buzz_event.channel_id, + event: buzz_event.event, received_at: std::time::Instant::now(), prompt_tag, }); @@ -1596,7 +1596,7 @@ async fn tokio_main() -> Result<()> { // ── Multiple-event-handling mode gate ───────────── // Event is already queued. If mode requires it AND // the channel has an in-flight task, fire cancel. - if accepted && queue.is_channel_in_flight(sprout_event.channel_id) { + if accepted && queue.is_channel_in_flight(buzz_event.channel_id) { let should_cancel = match config.multiple_event_handling { MultipleEventHandling::Queue => false, MultipleEventHandling::Interrupt => true, @@ -1608,7 +1608,7 @@ async fn tokio_main() -> Result<()> { } }; if should_cancel { - cancel_in_flight_task(&mut pool, sprout_event.channel_id, CancelMode::Interrupt); + cancel_in_flight_task(&mut pool, buzz_event.channel_id, CancelMode::Interrupt); } } // ── End mode gate ──────────────────────────────── @@ -1865,7 +1865,7 @@ async fn tokio_main() -> Result<()> { // for the background task to finish, rather than aborting immediately (#40). relay.shutdown().await; - tracing::info!("sprout-acp stopped"); + tracing::info!("buzz-acp stopped"); Ok(()) } @@ -2384,14 +2384,14 @@ fn default_heartbeat_prompt() -> String { You have been awakened for a routine heartbeat. You have NO incoming messages or\n\ active channel context for this turn.\n\n\ Your tasks:\n\ - 1. Run `sprout feed get --types needs_action` to check for pending workflow approvals or\n\ + 1. Run `buzz feed get --types needs_action` to check for pending workflow approvals or\n\ high-priority requests addressed to you.\n\ - 2. Run `sprout feed get --types mentions` to check for unanswered @mentions.\n\ + 2. Run `buzz feed get --types mentions` to check for unanswered @mentions.\n\ 3. If you find actionable items, address them using the appropriate CLI commands\n\ - (e.g., `sprout workflows approve --token `, `sprout messages send`,\n\ - `sprout messages send --reply-to `).\n\ + (e.g., `buzz workflows approve --token `, `buzz messages send`,\n\ + `buzz messages send --reply-to `).\n\ 4. If there are no pending actions or mentions, end your turn immediately.\n\n\ - Do not run `sprout channels list` or `sprout messages search` unless you have a specific reason.\n\ + Do not run `buzz channels list` or `buzz messages search` unless you have a specific reason.\n\ Do not invent work — only act on items surfaced by the feed commands." ) } @@ -2499,7 +2499,7 @@ async fn spawn_and_init( // ── run_models ───────────────────────────────────────────────────────────────── -/// `sprout-acp models` — spawn an agent, query its available models, exit. +/// `buzz-acp models` — spawn an agent, query its available models, exit. /// /// Flow: spawn → initialize → session/new → print models → shutdown. /// No relay connection, no MCP servers, no subscriptions. ~2-5s total. @@ -2653,11 +2653,11 @@ fn build_mcp_servers(config: &Config) -> Vec { env: { let mut env = vec![ EnvVar { - name: "SPROUT_RELAY_URL".into(), + name: "BUZZ_RELAY_URL".into(), value: config.relay_url.clone(), }, EnvVar { - name: "SPROUT_PRIVATE_KEY".into(), + name: "BUZZ_PRIVATE_KEY".into(), // bech32 encoding of a valid secret key is infallible. // Panic here is correct: injecting a bogus secret would cause // delayed, hard-to-diagnose agent failures downstream. @@ -2668,12 +2668,12 @@ fn build_mcp_servers(config: &Config) -> Vec { .expect("secret key bech32 encoding should never fail"), }, ]; - // Forward SPROUT_AUTH_TAG (NIP-OA owner attestation credential) + // Forward BUZZ_AUTH_TAG (NIP-OA owner attestation credential) // so the MCP server can attach it to every signed event. - if let Ok(auth_tag) = std::env::var("SPROUT_AUTH_TAG") { + if let Ok(auth_tag) = std::env::var("BUZZ_AUTH_TAG") { if !auth_tag.is_empty() { env.push(EnvVar { - name: "SPROUT_AUTH_TAG".into(), + name: "BUZZ_AUTH_TAG".into(), value: auth_tag, }); } @@ -2833,7 +2833,7 @@ mod build_mcp_servers_tests { kinds_override: None, channels_override: None, no_mention_filter: false, - config_path: std::path::PathBuf::from("./sprout-acp.toml"), + config_path: std::path::PathBuf::from("./buzz-acp.toml"), context_message_limit: 12, max_turns_per_session: 0, presence_enabled: true, @@ -2861,46 +2861,43 @@ mod build_mcp_servers_tests { let names: Vec<&str> = server.env.iter().map(|e| e.name.as_str()).collect(); assert!( - names.contains(&"SPROUT_RELAY_URL"), - "missing SPROUT_RELAY_URL; got {names:?}" + names.contains(&"BUZZ_RELAY_URL"), + "missing BUZZ_RELAY_URL; got {names:?}" ); assert!( - names.contains(&"SPROUT_PRIVATE_KEY"), - "missing SPROUT_PRIVATE_KEY; got {names:?}" + names.contains(&"BUZZ_PRIVATE_KEY"), + "missing BUZZ_PRIVATE_KEY; got {names:?}" ); } #[test] - fn session_new_mcp_server_forwards_sprout_auth_tag() { + fn session_new_mcp_server_forwards_buzz_auth_tag() { let _guard = ENV_LOCK.lock().unwrap(); - std::env::set_var("SPROUT_AUTH_TAG", "test-attestation-tag"); + std::env::set_var("BUZZ_AUTH_TAG", "test-attestation-tag"); let config = test_config(); let servers = build_mcp_servers(&config); - std::env::remove_var("SPROUT_AUTH_TAG"); + std::env::remove_var("BUZZ_AUTH_TAG"); let server = &servers[0]; - let auth_tag_env = server.env.iter().find(|e| e.name == "SPROUT_AUTH_TAG"); + let auth_tag_env = server.env.iter().find(|e| e.name == "BUZZ_AUTH_TAG"); assert!( auth_tag_env.is_some(), - "SPROUT_AUTH_TAG should be forwarded when set" + "BUZZ_AUTH_TAG should be forwarded when set" ); assert_eq!(auth_tag_env.unwrap().value, "test-attestation-tag"); } #[test] - fn session_new_mcp_server_skips_empty_sprout_auth_tag() { + fn session_new_mcp_server_skips_empty_buzz_auth_tag() { let _guard = ENV_LOCK.lock().unwrap(); - std::env::set_var("SPROUT_AUTH_TAG", ""); + std::env::set_var("BUZZ_AUTH_TAG", ""); let config = test_config(); let servers = build_mcp_servers(&config); - std::env::remove_var("SPROUT_AUTH_TAG"); + std::env::remove_var("BUZZ_AUTH_TAG"); let server = &servers[0]; - let has_auth_tag = server.env.iter().any(|e| e.name == "SPROUT_AUTH_TAG"); - assert!( - !has_auth_tag, - "empty SPROUT_AUTH_TAG should not be forwarded" - ); + let has_auth_tag = server.env.iter().any(|e| e.name == "BUZZ_AUTH_TAG"); + assert!(!has_auth_tag, "empty BUZZ_AUTH_TAG should not be forwarded"); } #[test] diff --git a/crates/sprout-acp/src/main.rs b/crates/buzz-acp/src/main.rs similarity index 62% rename from crates/sprout-acp/src/main.rs rename to crates/buzz-acp/src/main.rs index 1532f3731..1f332abda 100644 --- a/crates/sprout-acp/src/main.rs +++ b/crates/buzz-acp/src/main.rs @@ -1,3 +1,3 @@ fn main() -> anyhow::Result<()> { - sprout_acp::run() + buzz_acp::run() } diff --git a/crates/sprout-acp/src/observer.rs b/crates/buzz-acp/src/observer.rs similarity index 97% rename from crates/sprout-acp/src/observer.rs rename to crates/buzz-acp/src/observer.rs index 536d23eea..ac5795812 100644 --- a/crates/sprout-acp/src/observer.rs +++ b/crates/buzz-acp/src/observer.rs @@ -20,7 +20,7 @@ const OBSERVER_BUFFER_CAP: usize = 1_000; /// Best-effort metadata attached to observer events. #[derive(Clone, Debug, Default)] pub struct ObserverContext { - /// Sprout channel UUID for the current turn, when channel-scoped. + /// Buzz channel UUID for the current turn, when channel-scoped. pub channel_id: Option, /// ACP session ID associated with the current turn, once known. pub session_id: Option, @@ -63,7 +63,7 @@ pub struct ObserverEvent { pub kind: String, /// Pool slot index for the agent process that emitted the event. pub agent_index: Option, - /// Sprout channel UUID for channel-scoped events. + /// Buzz channel UUID for channel-scoped events. pub channel_id: Option, /// ACP session ID when known. pub session_id: Option, diff --git a/crates/sprout-acp/src/pool.rs b/crates/buzz-acp/src/pool.rs similarity index 99% rename from crates/sprout-acp/src/pool.rs rename to crates/buzz-acp/src/pool.rs index 9f881a2fd..86b7292b4 100644 --- a/crates/sprout-acp/src/pool.rs +++ b/crates/buzz-acp/src/pool.rs @@ -220,7 +220,7 @@ pub struct PromptContext { /// the per-session core engram fetch is skipped and `core_sections` /// remains empty for every channel, so `format_prompt` renders no /// `[Agent Memory — core]` section. On by default; disabled via - /// `--no-memory` / `SPROUT_ACP_NO_MEMORY`. + /// `--no-memory` / `BUZZ_ACP_NO_MEMORY`. pub memory_enabled: bool, } @@ -760,11 +760,11 @@ pub async fn run_prompt_task( // happens when a session is invalidated and recreated (see // `SessionState::invalidate_channel`). // - // Operator opt-out: `--no-memory` / `SPROUT_ACP_NO_MEMORY` disables the + // Operator opt-out: `--no-memory` / `BUZZ_ACP_NO_MEMORY` disables the // NIP-AE injection path. By default we run the fetch and populate // `state.core_sections`, so `format_prompt` renders the core section. // When disabled we skip the fetch outright and leave `core_sections` - // empty. The `sprout mem` CLI and the relay's acceptance of + // empty. The `buzz mem` CLI and the relay's acceptance of // kind:30174 engrams are unaffected. if is_new_session && ctx.memory_enabled { if let (PromptSource::Channel(cid), Some(owner_pk)) = @@ -921,7 +921,7 @@ pub async fn run_prompt_task( // When the batch is a single slash-command message (e.g. "@Eva /goal …"), // `slash_command` holds the bare command. It is sent as the FIRST prompt // content block so ACP connectors' slash-command detection - // (`prompt[0].text.startsWith("/")`) fires; the wrapped Sprout context + // (`prompt[0].text.startsWith("/")`) fires; the wrapped Buzz context // follows as a second block. let mut slash_command: Option = None; let prompt_text = if let Some(text) = prompt_text { @@ -1002,7 +1002,7 @@ pub async fn run_prompt_task( // ── Send the actual prompt ──────────────────────────────────────────── // Slash-command pass-through sends two text blocks: the bare command - // first (so connector detection fires), then the wrapped Sprout context. + // first (so connector detection fires), then the wrapped Buzz context. let prompt_blocks: Vec<&str> = match slash_command { Some(ref cmd) => vec![cmd.as_str(), prompt_text.as_str()], None => vec![prompt_text.as_str()], @@ -1309,7 +1309,7 @@ async fn fetch_channel_info(channel_id: Uuid, rest: &RestClient) -> Option String { /// Best-effort: add a reaction via a signed Nostr kind-7 event (NIP-25). /// -/// Builds a reaction event with `sprout_sdk::build_reaction`, signs it with +/// Builds a reaction event with `buzz_sdk::build_reaction`, signs it with /// the keys already stored in `RestClient`, and submits via `POST /events`. /// Returns immediately on timeout or any error — reactions are cosmetic. pub(crate) async fn reaction_add(rest: &crate::relay::RestClient, event_id: &str, emoji: &str) { @@ -1966,7 +1966,7 @@ pub(crate) async fn reaction_add(rest: &crate::relay::RestClient, event_id: &str return; } }; - let builder = match sprout_sdk::build_reaction(target_id, emoji) { + let builder = match buzz_sdk::build_reaction(target_id, emoji) { Ok(b) => b, Err(e) => { tracing::warn!(event_id, emoji, "reaction add: build failed: {e}"); @@ -2047,7 +2047,7 @@ pub(crate) async fn reaction_remove(rest: &crate::relay::RestClient, event_id: & return; } }; - let builder = match sprout_sdk::build_remove_reaction(target_id) { + let builder = match buzz_sdk::build_remove_reaction(target_id) { Ok(b) => b, Err(e) => { tracing::warn!(event_id, emoji, "reaction remove: build failed: {e}"); diff --git a/crates/sprout-acp/src/queue.rs b/crates/buzz-acp/src/queue.rs similarity index 98% rename from crates/sprout-acp/src/queue.rs rename to crates/buzz-acp/src/queue.rs index de97c6c8a..ee776f830 100644 --- a/crates/sprout-acp/src/queue.rs +++ b/crates/buzz-acp/src/queue.rs @@ -1,4 +1,4 @@ -//! Event queue state machine for sprout-acp. +//! Event queue state machine for buzz-acp. //! //! Manages per-channel event queues with per-channel in-flight tracking. //! When the harness is ready to prompt the agent, it flushes the channel with @@ -587,7 +587,7 @@ pub struct ThreadTags { /// /// NOTE: Only handles NIP-10 marker-based format (preferred). The deprecated /// positional format (no markers, `["e", id, relay_url]`) is not supported — -/// Sprout always generates marker-based tags (see relay messages.rs:762-783). +/// Buzz always generates marker-based tags (see relay messages.rs:762-783). pub fn parse_thread_tags(event: &Event) -> ThreadTags { let mut root = None; let mut reply = None; @@ -633,7 +633,7 @@ pub fn parse_thread_tags(event: &Event) -> ThreadTags { /// Extract a leading slash command from message content. /// /// ACP connectors (claude-agent-acp, codex-acp) detect slash commands by -/// checking whether the **first** prompt content block starts with `/`. Sprout +/// checking whether the **first** prompt content block starts with `/`. Buzz /// users must @mention an agent to reach it, so the wire content is typically /// `"@Eva /goal ship it"`. This strips leading mention tokens — `@word`, /// multi-word display names from `known_names`, and NIP-27 `nostr:npub1…` / @@ -661,7 +661,7 @@ pub fn extract_slash_command(content: &str, known_names: &[&str]) -> Option` on every `sprout messages +/// Tells the agent to pass `--reply-to ` on every `buzz messages /// send` call in this turn, and not to broadcast to the channel so replies /// stay inside the thread. fn append_reply_instruction(s: &mut String, event_id: &str) { s.push_str(&format!( "\nIMPORTANT: When responding, use `--reply-to {event_id}` \ - on EVERY `sprout messages send` call in this turn. \ + on EVERY `buzz messages send` call in this turn. \ Do not broadcast to the channel." )); } @@ -912,13 +912,13 @@ fn format_context_hints( // DM replies use thread command because /messages excludes thread replies. // DM non-replies use get for recent conversation. let ctx_hint = if has_conversation_context && is_reply { - "Thread context included below. Use `sprout messages thread --channel --event ` for full history if truncated." + "Thread context included below. Use `buzz messages thread --channel --event ` for full history if truncated." } else if has_conversation_context { - "Conversation context included below. Use `sprout messages get --channel ` for full history if truncated." + "Conversation context included below. Use `buzz messages get --channel ` for full history if truncated." } else if is_reply { - "Use `sprout messages thread --channel --event ` to fetch the reply chain." + "Use `buzz messages thread --channel --event ` to fetch the reply chain." } else { - "Use `sprout messages get --channel ` for conversation context." + "Use `buzz messages get --channel ` for conversation context." }; let mut s = format!( "[Context]\n\ @@ -941,9 +941,9 @@ fn format_context_hints( s } else if let Some(ref root) = thread_tags.root_event_id { let ctx_hint = if has_conversation_context { - "Thread context included below. Use `sprout messages thread --channel --event ` for full history if truncated." + "Thread context included below. Use `buzz messages thread --channel --event ` for full history if truncated." } else { - "Use `sprout messages thread --channel --event ` to fetch thread context." + "Use `buzz messages thread --channel --event ` to fetch thread context." }; let mut s = format!( "[Context]\n\ @@ -966,7 +966,7 @@ fn format_context_hints( "[Context]\n\ Scope: channel\n\ Channel: {channel_display}\n\ - Hint: Use `sprout messages get --channel ` for recent messages if needed." + Hint: Use `buzz messages get --channel ` for recent messages if needed." ) } } @@ -1033,7 +1033,7 @@ pub fn prepend_base_prompt(base: &str, body: &str) -> String { /// 1. `[System]\n{system_prompt}` — if system prompt is set /// 2. `[Context]` — scope, channel name, and contextual hints for the agent /// 3. `[Thread Context]` or `[Conversation Context]` — if fetched -/// 4. `[Event]` / `[Sprout events]` — the triggering event(s) +/// 4. `[Event]` / `[Buzz events]` — the triggering event(s) pub fn format_prompt(batch: &FlushBatch, args: &FormatPromptArgs<'_>) -> String { // Scope is always derived from the LAST event in the batch — that's the // one the agent is responding to. Thread/DM context is supplementary info @@ -1115,7 +1115,7 @@ pub fn format_prompt(batch: &FlushBatch, args: &FormatPromptArgs<'_>) -> String ) } else { format!( - "[Sprout event: {}]\n{}", + "[Buzz event: {}]\n{}", be.prompt_tag, format_event_block(batch.channel_id, args.channel_info, be, args.profile_lookup) ) @@ -1127,7 +1127,7 @@ pub fn format_prompt(batch: &FlushBatch, args: &FormatPromptArgs<'_>) -> String batch.events.len() ) } else { - format!("[Sprout events — {} events]", batch.events.len()) + format!("[Buzz events — {} events]", batch.events.len()) }; let mut s = header; for (i, be) in batch.events.iter().enumerate() { @@ -1371,7 +1371,7 @@ mod tests { // Should contain [Context] section before the event. assert!(prompt.contains("[Context]")); assert!(prompt.contains("Scope: channel")); - assert!(prompt.contains("[Sprout event: @mention]\n")); + assert!(prompt.contains("[Buzz event: @mention]\n")); assert!(prompt.contains(&format!("Channel: {}", ch))); assert!(prompt.contains(&format!("From: {}", npub))); assert!(prompt.contains("Content: Hello @agent")); @@ -1465,7 +1465,7 @@ mod tests { let prompt = format_prompt(&batch, &FormatPromptArgs::default()); assert!(prompt.contains("[Context]")); - assert!(prompt.contains("[Sprout events — 3 events]")); + assert!(prompt.contains("[Buzz events — 3 events]")); assert!(prompt.contains("--- Event 1 (tag-a) ---")); assert!(prompt.contains("--- Event 2 (tag-b) ---")); assert!(prompt.contains("--- Event 3 (tag-c) ---")); @@ -2447,8 +2447,8 @@ mod tests { ); // Hint should point to the thread command, not get. assert!( - prompt.contains("sprout messages thread"), - "DM reply hint should mention `sprout messages thread`, got:\n{prompt}" + prompt.contains("buzz messages thread"), + "DM reply hint should mention `buzz messages thread`, got:\n{prompt}" ); // Thread structural info should be present. assert!( @@ -2487,12 +2487,12 @@ mod tests { ); assert!(prompt.contains("Scope: dm")); assert!( - prompt.contains("sprout messages get"), - "DM non-reply hint should mention `sprout messages get`" + prompt.contains("buzz messages get"), + "DM non-reply hint should mention `buzz messages get`" ); assert!( - !prompt.contains("sprout messages thread"), - "DM non-reply should NOT mention `sprout messages thread`" + !prompt.contains("buzz messages thread"), + "DM non-reply should NOT mention `buzz messages thread`" ); } diff --git a/crates/sprout-acp/src/relay.rs b/crates/buzz-acp/src/relay.rs similarity index 99% rename from crates/sprout-acp/src/relay.rs rename to crates/buzz-acp/src/relay.rs index d2cdf6c7e..79c8fe216 100644 --- a/crates/sprout-acp/src/relay.rs +++ b/crates/buzz-acp/src/relay.rs @@ -1,6 +1,6 @@ -//! Harness-side Sprout relay client. +//! Harness-side Buzz relay client. //! -//! Connects to the Sprout relay via NIP-01 WebSocket, authenticates via NIP-42, +//! Connects to the Buzz relay via NIP-01 WebSocket, authenticates via NIP-42, //! discovers channels via REST API, and streams events back to the harness main //! loop. Also publishes ephemeral events (typing indicators) via the same //! WebSocket connection. @@ -9,7 +9,7 @@ //! //! A background tokio task owns the WebSocket stream. It: //! - Responds to Ping frames with Pong (preventing relay disconnect on long turns) -//! - Forwards `SproutEvent`s through an `mpsc` channel +//! - Forwards `BuzzEvent`s through an `mpsc` channel //! - Handles reconnection with `since` filters to avoid event loss //! - Responds to mid-session AUTH challenges //! - Publishes ephemeral events (typing indicators) via `PublishEvent` commands @@ -23,7 +23,7 @@ use std::time::Duration; // ─── Named constants ────────────────────────────────────────────────────────── /// Default capacity of the event channel from background task to harness. -/// Override with `SPROUT_ACP_EVENT_BUFFER` env var at startup. +/// Override with `BUZZ_ACP_EVENT_BUFFER` env var at startup. const EVENT_CHANNEL_CAPACITY_DEFAULT: usize = 256; /// Capacity of the command channel from harness to background task. const CMD_CHANNEL_CAPACITY: usize = 64; @@ -31,7 +31,7 @@ const CMD_CHANNEL_CAPACITY: usize = 64; /// Read the event channel capacity from the environment, falling back to the /// compiled-in default. Parsed once at call-site (connect time). fn event_channel_capacity() -> usize { - std::env::var("SPROUT_ACP_EVENT_BUFFER") + std::env::var("BUZZ_ACP_EVENT_BUFFER") .ok() .and_then(|v| v.parse::().ok()) .map(|v| v.max(1)) // mpsc::channel panics on capacity 0 @@ -61,13 +61,13 @@ const CONNECT_TIMEOUT: Duration = Duration::from_secs(10); use std::time::Instant; -use futures_util::{SinkExt, StreamExt}; -use nostr::{Event, EventBuilder, Keys, Kind, RelayUrl, Tag}; -use serde_json::{json, Value}; -use sprout_core::kind::{ +use buzz_core::kind::{ KIND_AGENT_OBSERVER_FRAME, KIND_MEMBER_ADDED_NOTIFICATION, KIND_MEMBER_REMOVED_NOTIFICATION, KIND_TYPING_INDICATOR, }; +use futures_util::{SinkExt, StreamExt}; +use nostr::{Event, EventBuilder, Keys, Kind, RelayUrl, Tag}; +use serde_json::{json, Value}; use tokio::sync::mpsc; use tokio::time::timeout; use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream}; @@ -294,7 +294,7 @@ impl RestClient { /// Events the harness cares about. #[derive(Debug, Clone)] -pub struct SproutEvent { +pub struct BuzzEvent { /// Which channel this event belongs to. pub channel_id: Uuid, /// The underlying Nostr event. @@ -408,14 +408,14 @@ type WsStream = WebSocketStream>; /// Harness-side relay client. /// -/// Connects to the Sprout relay, authenticates via NIP-42, and streams +/// Connects to the Buzz relay, authenticates via NIP-42, and streams /// matching events for subscribed channels. /// /// A background tokio task owns the WebSocket connection and responds to /// Ping frames, preventing disconnection during long agent turns. pub struct HarnessRelay { /// Receiver for events forwarded by the background task. - event_rx: mpsc::Receiver>, + event_rx: mpsc::Receiver>, /// Receiver for encrypted observer control events addressed to this agent. observer_control_rx: Option>, /// Sender for commands to the background task. @@ -470,7 +470,7 @@ impl HarnessRelay { // task so buffered messages aren't silently discarded. let (ws, handshake_buffer) = do_connect(relay_url, keys, auth_tag.as_ref()).await?; - let (event_tx, event_rx) = mpsc::channel::>(event_channel_capacity()); + let (event_tx, event_rx) = mpsc::channel::>(event_channel_capacity()); let (observer_control_tx, observer_control_rx) = mpsc::channel::(event_channel_capacity()); let (cmd_tx, cmd_rx) = mpsc::channel::(CMD_CHANNEL_CAPACITY); @@ -526,7 +526,7 @@ impl HarnessRelay { let p_tag = SingleLetterTag::lowercase(Alphabet::P); let member_filter = nostr::Filter::new() .kind(Kind::Custom( - sprout_core::kind::KIND_NIP29_GROUP_MEMBERS as u16, + buzz_core::kind::KIND_NIP29_GROUP_MEMBERS as u16, )) .custom_tags(p_tag, [pk_hex.as_str()]); let member_events = rest.query(&[member_filter]).await?; @@ -563,7 +563,7 @@ impl HarnessRelay { let d_values: Vec = channel_uuids.iter().map(|u| u.to_string()).collect(); let meta_filter = nostr::Filter::new() .kind(Kind::Custom( - sprout_core::kind::KIND_NIP29_GROUP_METADATA as u16, + buzz_core::kind::KIND_NIP29_GROUP_METADATA as u16, )) .custom_tags(d_tag, d_values); let meta_events = rest.query(&[meta_filter]).await?; @@ -717,7 +717,7 @@ impl HarnessRelay { /// /// Reads from the background task's event channel. Returns `None` on /// connection loss — the caller should call [`reconnect`](Self::reconnect). - pub async fn next_event(&mut self) -> Option { + pub async fn next_event(&mut self) -> Option { // The background task sends `None` to signal connection loss. self.event_rx.recv().await.flatten() } @@ -787,7 +787,7 @@ impl HarnessRelay { message: &str, thread_root: Option<&str>, ) -> Result { - use sprout_core::kind::KIND_STREAM_MESSAGE; + use buzz_core::kind::KIND_STREAM_MESSAGE; let h_tag = Tag::parse(["h", &channel_id.to_string()]) .map_err(|e| RelayError::EventBuild(e.to_string()))?; @@ -1232,7 +1232,7 @@ async fn execute_connected_command( async fn run_background_task( mut ws: WsStream, initial_handshake_buffer: std::collections::VecDeque, - event_tx: mpsc::Sender>, + event_tx: mpsc::Sender>, observer_control_tx: mpsc::Sender, mut cmd_rx: mpsc::Receiver, keys: Keys, @@ -1632,7 +1632,7 @@ async fn run_background_task( async fn handle_ws_message( msg: Message, ws: &mut WsStream, - event_tx: &mpsc::Sender>, + event_tx: &mpsc::Sender>, observer_control_tx: &mpsc::Sender, state: &mut BgState, keys: &Keys, @@ -1688,7 +1688,7 @@ async fn handle_ws_message( return true; } let ts = event.created_at.as_secs(); - let sprout_event = SproutEvent { + let buzz_event = BuzzEvent { channel_id: channel_uuid, event: *event, }; @@ -1702,7 +1702,7 @@ async fn handle_ws_message( "event channel at ≥80% capacity — backpressure imminent" ); } - match event_tx.try_send(Some(sprout_event)) { + match event_tx.try_send(Some(buzz_event)) { Ok(()) => { state.membership_last_seen = Some(state.membership_last_seen.unwrap_or(0).max(ts)); @@ -1731,7 +1731,7 @@ async fn handle_ws_message( let ts = event.created_at.as_secs(); let event_id_hex = event.id.to_hex(); if state.record_event(channel_id, &event) { - let sprout_event = SproutEvent { + let buzz_event = BuzzEvent { channel_id, event: *event, }; @@ -1745,7 +1745,7 @@ async fn handle_ws_message( "event channel at ≥80% capacity — backpressure imminent" ); } - match event_tx.try_send(Some(sprout_event)) { + match event_tx.try_send(Some(buzz_event)) { Ok(()) => {} Err(mpsc::error::TrySendError::Full(_)) => { // Remove from dedup set so the replayed event @@ -1936,7 +1936,7 @@ async fn handle_ws_message( async fn process_handshake_buffer( ws: &mut WsStream, buffer: std::collections::VecDeque, - event_tx: &mpsc::Sender>, + event_tx: &mpsc::Sender>, observer_control_tx: &mpsc::Sender, state: &mut BgState, keys: &Keys, @@ -2147,7 +2147,7 @@ async fn try_autonomous_reconnect( keys: &Keys, relay_url: &str, agent_pubkey_hex: &str, - event_tx: &mpsc::Sender>, + event_tx: &mpsc::Sender>, observer_control_tx: &mpsc::Sender, auth_tag: Option<&nostr::Tag>, ) -> ReconnectOutcome { @@ -2250,7 +2250,7 @@ async fn wait_for_reconnect( keys: &Keys, relay_url: &str, agent_pubkey_hex: &str, - event_tx: &mpsc::Sender>, + event_tx: &mpsc::Sender>, observer_control_tx: &mpsc::Sender, skip_drain: bool, auth_tag: Option<&nostr::Tag>, diff --git a/crates/sprout-admin/Cargo.toml b/crates/buzz-admin/Cargo.toml similarity index 64% rename from crates/sprout-admin/Cargo.toml rename to crates/buzz-admin/Cargo.toml index d536abca5..e08861696 100644 --- a/crates/sprout-admin/Cargo.toml +++ b/crates/buzz-admin/Cargo.toml @@ -1,20 +1,20 @@ [package] -name = "sprout-admin" +name = "buzz-admin" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true repository.workspace = true -description = "Operator CLI for Sprout relay administration" +description = "Operator CLI for Buzz relay administration" [[bin]] -name = "sprout-admin" +name = "buzz-admin" path = "src/main.rs" [dependencies] -sprout-db = { workspace = true } -sprout-core = { workspace = true } -sprout-auth = { workspace = true } +buzz-db = { workspace = true } +buzz-core = { workspace = true } +buzz-auth = { workspace = true } nostr = { workspace = true } tokio = { workspace = true } serde_json = { workspace = true } diff --git a/crates/sprout-admin/src/main.rs b/crates/buzz-admin/src/main.rs similarity index 91% rename from crates/sprout-admin/src/main.rs rename to crates/buzz-admin/src/main.rs index 8ad4027df..4a259b110 100644 --- a/crates/sprout-admin/src/main.rs +++ b/crates/buzz-admin/src/main.rs @@ -1,18 +1,18 @@ #![deny(unsafe_code)] -//! Sprout instance administration CLI. +//! Buzz instance administration CLI. //! //! In the pure Nostr architecture, API tokens no longer exist. //! Admin operations are performed via signed Nostr events (NIP-43 relay admin commands). //! This binary is retained as a placeholder for future admin tooling. use anyhow::Result; +use buzz_db::{Db, DbConfig}; use clap::{Parser, Subcommand}; use nostr::Keys; -use sprout_db::{Db, DbConfig}; #[derive(Parser)] -#[command(name = "sprout-admin", about = "Sprout instance administration")] +#[command(name = "buzz-admin", about = "Buzz instance administration")] struct Cli { #[command(subcommand)] command: Command, @@ -41,7 +41,7 @@ enum Command { /// clients can see those channels. Idempotent — safe to run multiple times. ReconcileChannels { /// Relay private key (hex) for signing events. Falls back to - /// SPROUT_RELAY_PRIVATE_KEY env var. If neither is set, generates + /// BUZZ_RELAY_PRIVATE_KEY env var. If neither is set, generates /// an ephemeral key (events will be unverifiable after restart). #[arg(long)] relay_key: Option, @@ -57,7 +57,7 @@ async fn main() -> Result<()> { let keys = Keys::generate(); println!("Public key: {}", keys.public_key().to_hex()); println!("Secret key: {}", keys.secret_key().display_secret()); - println!("\nSet SPROUT_PRIVATE_KEY to the secret key to use this identity."); + println!("\nSet BUZZ_PRIVATE_KEY to the secret key to use this identity."); } Command::AddMember { pubkey, role } => { let db = connect_db().await?; @@ -93,7 +93,7 @@ async fn main() -> Result<()> { async fn connect_db() -> Result { let db_url = std::env::var("DATABASE_URL") - .unwrap_or_else(|_| "postgres://sprout:sprout_dev@localhost:5432/sprout".to_string()); + .unwrap_or_else(|_| "postgres://buzz:buzz_dev@localhost:5432/buzz".to_string()); let db = Db::new(&DbConfig { database_url: db_url, ..DbConfig::default() @@ -103,15 +103,14 @@ async fn connect_db() -> Result { } async fn reconcile_channels(relay_key_arg: Option) -> Result<()> { + use buzz_core::kind::KIND_NIP29_GROUP_ADMINS; + use buzz_db::event::EventQuery; use nostr::{EventBuilder, Kind, Tag}; - use sprout_core::kind::KIND_NIP29_GROUP_ADMINS; - use sprout_db::event::EventQuery; let db = connect_db().await?; // Resolve relay signing key: arg > env > ephemeral - let relay_keys = match relay_key_arg.or_else(|| std::env::var("SPROUT_RELAY_PRIVATE_KEY").ok()) - { + let relay_keys = match relay_key_arg.or_else(|| std::env::var("BUZZ_RELAY_PRIVATE_KEY").ok()) { Some(key_hex) => { Keys::parse(&key_hex).map_err(|e| anyhow::anyhow!("invalid relay key: {e}"))? } @@ -122,7 +121,7 @@ async fn reconcile_channels(relay_key_arg: Option) -> Result<()> { k.public_key().to_hex() ); eprintln!("Events signed with this key won't be verifiable after this run."); - eprintln!("Pass --relay-key or set SPROUT_RELAY_PRIVATE_KEY for production use."); + eprintln!("Pass --relay-key or set BUZZ_RELAY_PRIVATE_KEY for production use."); k } }; diff --git a/crates/sprout-agent/Cargo.toml b/crates/buzz-agent/Cargo.toml similarity index 96% rename from crates/sprout-agent/Cargo.toml rename to crates/buzz-agent/Cargo.toml index d2c9b9b71..7889ad34a 100644 --- a/crates/sprout-agent/Cargo.toml +++ b/crates/buzz-agent/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "sprout-agent" +name = "buzz-agent" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -11,11 +11,11 @@ keywords = ["acp", "agent", "llm", "mcp", "minimal"] categories = ["command-line-utilities", "web-programming"] [lib] -name = "sprout_agent" +name = "buzz_agent" path = "src/lib.rs" [[bin]] -name = "sprout-agent" +name = "buzz-agent" path = "src/main.rs" # Test-only fake MCP server. Built unconditionally because cargo can't gate diff --git a/crates/sprout-agent/README.md b/crates/buzz-agent/README.md similarity index 82% rename from crates/sprout-agent/README.md rename to crates/buzz-agent/README.md index 95a00db60..80781c9ea 100644 --- a/crates/sprout-agent/README.md +++ b/crates/buzz-agent/README.md @@ -1,16 +1,16 @@ -# sprout-agent +# buzz-agent > Minimal, unbreakable ACP-compliant LLM agent. Stdio in, tool calls out. Non-streaming. No persistence. No cleverness. -[ACP](https://agentclientprotocol.com) is the Agent Client Protocol — JSON-RPC 2.0 over stdio between a client (Zed, JetBrains, sprout-acp, …) and an agent. [MCP](https://modelcontextprotocol.io) is how the agent talks to its tools. +[ACP](https://agentclientprotocol.com) is the Agent Client Protocol — JSON-RPC 2.0 over stdio between a client (Zed, JetBrains, buzz-acp, …) and an agent. [MCP](https://modelcontextprotocol.io) is how the agent talks to its tools. -`sprout-agent` is the agent. +`buzz-agent` is the agent. ## What It Is ``` +--------+ stdio (JSON-RPC 2.0) +---------------+ - | client | <----------------------> | sprout-agent | + | client | <----------------------> | buzz-agent | +--------+ ACP frames +---------------+ │ │ │ │ rmcp (stdio) @@ -35,26 +35,26 @@ The agent's **output is its tool calls**. Generated text is forwarded to the cli ```bash # Build -cargo build --release -p sprout-agent +cargo build --release -p buzz-agent # Run against Anthropic -SPROUT_AGENT_PROVIDER=anthropic \ +BUZZ_AGENT_PROVIDER=anthropic \ ANTHROPIC_API_KEY=sk-ant-... \ ANTHROPIC_MODEL=claude-sonnet-4-5 \ - ./target/release/sprout-agent + ./target/release/buzz-agent # Or any OpenAI-compatible endpoint -SPROUT_AGENT_PROVIDER=openai \ +BUZZ_AGENT_PROVIDER=openai \ OPENAI_COMPAT_API_KEY=sk-... \ OPENAI_COMPAT_MODEL=gpt-5 \ OPENAI_COMPAT_BASE_URL=https://api.openai.com/v1 \ - ./target/release/sprout-agent + ./target/release/buzz-agent # Or Databricks model serving via OAuth 2.0 PKCE -SPROUT_AGENT_PROVIDER=databricks \ +BUZZ_AGENT_PROVIDER=databricks \ DATABRICKS_HOST=https://dbc-...cloud.databricks.com \ DATABRICKS_MODEL=goose-claude-4-6-sonnet \ - ./target/release/sprout-agent + ./target/release/buzz-agent ``` That's the whole setup. The agent reads JSON-RPC frames from stdin, writes them to stdout, and logs to stderr. @@ -73,7 +73,7 @@ A complete round-trip. Lines starting with `→` are client→agent (stdin); ` "promptCapabilities":{"image":false,"audio":false,"embeddedContext":false}, "mcpCapabilities":{"http":false,"sse":false} }, - "agentInfo":{"name":"sprout-agent","version":"0.1.0"} + "agentInfo":{"name":"buzz-agent","version":"0.1.0"} }} // 2. Open a session. The client passes the MCP servers to spawn. @@ -129,7 +129,7 @@ Everything is environment variables. No flags, no config files. (We are a subpro | Variable | Default | Notes | |---|---|---| -| `SPROUT_AGENT_PROVIDER` | — | `anthropic`, `openai`, or `databricks`. If unset, or if `anthropic`/`openai` is selected but its API key is missing, Databricks is auto-selected when `DATABRICKS_HOST` + `DATABRICKS_MODEL` are set. | +| `BUZZ_AGENT_PROVIDER` | — | `anthropic`, `openai`, or `databricks`. If unset, or if `anthropic`/`openai` is selected but its API key is missing, Databricks is auto-selected when `DATABRICKS_HOST` + `DATABRICKS_MODEL` are set. | | `ANTHROPIC_API_KEY` | — | Required when provider=anthropic unless Databricks fallback is configured. | | `ANTHROPIC_MODEL` | — | Required when provider=anthropic. | | `ANTHROPIC_BASE_URL` | `https://api.anthropic.com` | | @@ -141,25 +141,25 @@ Everything is environment variables. No flags, no config files. (We are a subpro | `DATABRICKS_HOST` | — | Required when provider=databricks or when using Databricks fallback. | | `DATABRICKS_MODEL` | — | Required when provider=databricks or when using Databricks fallback. | | `DATABRICKS_TOKEN` | — | Optional static bearer escape hatch. If unset, Databricks uses browser OAuth + refresh cache. | -| `SPROUT_AGENT_SYSTEM_PROMPT` | built-in | Inline system prompt. | -| `SPROUT_AGENT_SYSTEM_PROMPT_FILE` | — | File path. Mutually exclusive with the above. | -| `SPROUT_AGENT_MAX_ROUNDS` | `0` | Tool-loop iteration cap. 0 = unlimited. | -| `SPROUT_AGENT_MAX_OUTPUT_TOKENS` | `32768` | Per LLM call. Headroom for large tool-call inputs (e.g. file writes via heredoc); Sonnet 4 / Opus 4 cap at 64K. | -| `SPROUT_AGENT_MAX_CONTEXT_TOKENS` | `200000` | Provider context window used by the handoff gate. | -| `SPROUT_AGENT_MAX_HANDOFFS` | `10` | Max context handoffs per session before falling back to truncation. | -| `SPROUT_AGENT_LLM_TIMEOUT_SECS` | `120` | | -| `SPROUT_AGENT_TOOL_TIMEOUT_SECS` | `660` | Per-tool call timeout in seconds | -| `SPROUT_AGENT_MAX_PARALLEL_TOOLS` | `8` | Max concurrent tool calls per turn (1 = sequential) | -| `SPROUT_AGENT_MAX_SESSIONS` | unlimited | Max concurrent ACP sessions. Sessions are cheap; default has no cap. | -| `SPROUT_AGENT_MAX_LINE_BYTES` | `4194304` | 4 MiB. Hard cap on inbound JSON-RPC frames. | -| `SPROUT_AGENT_MAX_HISTORY_BYTES` | `1048576` | 1 MiB. Old turns are evicted past this. | +| `BUZZ_AGENT_SYSTEM_PROMPT` | built-in | Inline system prompt. | +| `BUZZ_AGENT_SYSTEM_PROMPT_FILE` | — | File path. Mutually exclusive with the above. | +| `BUZZ_AGENT_MAX_ROUNDS` | `0` | Tool-loop iteration cap. 0 = unlimited. | +| `BUZZ_AGENT_MAX_OUTPUT_TOKENS` | `32768` | Per LLM call. Headroom for large tool-call inputs (e.g. file writes via heredoc); Sonnet 4 / Opus 4 cap at 64K. | +| `BUZZ_AGENT_MAX_CONTEXT_TOKENS` | `200000` | Provider context window used by the handoff gate. | +| `BUZZ_AGENT_MAX_HANDOFFS` | `10` | Max context handoffs per session before falling back to truncation. | +| `BUZZ_AGENT_LLM_TIMEOUT_SECS` | `120` | | +| `BUZZ_AGENT_TOOL_TIMEOUT_SECS` | `660` | Per-tool call timeout in seconds | +| `BUZZ_AGENT_MAX_PARALLEL_TOOLS` | `8` | Max concurrent tool calls per turn (1 = sequential) | +| `BUZZ_AGENT_MAX_SESSIONS` | unlimited | Max concurrent ACP sessions. Sessions are cheap; default has no cap. | +| `BUZZ_AGENT_MAX_LINE_BYTES` | `4194304` | 4 MiB. Hard cap on inbound JSON-RPC frames. | +| `BUZZ_AGENT_MAX_HISTORY_BYTES` | `1048576` | 1 MiB. Old turns are evicted past this. | ## Providers -`sprout-agent` speaks two HTTP dialects. Pick with `SPROUT_AGENT_PROVIDER`. +`buzz-agent` speaks two HTTP dialects. Pick with `BUZZ_AGENT_PROVIDER`. -| Provider | `SPROUT_AGENT_PROVIDER` | Endpoint (auto) | Tested with | +| Provider | `BUZZ_AGENT_PROVIDER` | Endpoint (auto) | Tested with | |---|---|---|---| | Anthropic | `anthropic` | `POST {base}/v1/messages` | claude-sonnet-4-5, claude-opus-4 | | OpenAI | `openai` | `POST {base}/responses` | gpt-5, gpt-5-mini, o4-mini, gpt-4o | @@ -170,7 +170,7 @@ Everything is environment variables. No flags, no config files. (We are a subpro | Block Gateway | `openai` | `POST {base}/chat/completions` | gpt-5, claude | | Databricks | `databricks` | `POST {host}/serving-endpoints/{model}/invocations` | goose-claude-4-6-sonnet | -If `SPROUT_AGENT_PROVIDER=anthropic` is selected without `ANTHROPIC_API_KEY`, or `SPROUT_AGENT_PROVIDER=openai` is selected without `OPENAI_COMPAT_API_KEY`, the agent automatically falls back to Databricks OAuth when `DATABRICKS_HOST` and `DATABRICKS_MODEL` are set. The same Databricks fallback applies when `SPROUT_AGENT_PROVIDER` is unset. Explicit Anthropic/OpenAI API keys always win. +If `BUZZ_AGENT_PROVIDER=anthropic` is selected without `ANTHROPIC_API_KEY`, or `BUZZ_AGENT_PROVIDER=openai` is selected without `OPENAI_COMPAT_API_KEY`, the agent automatically falls back to Databricks OAuth when `DATABRICKS_HOST` and `DATABRICKS_MODEL` are set. The same Databricks fallback applies when `BUZZ_AGENT_PROVIDER` is unset. Explicit Anthropic/OpenAI API keys always win. `provider=openai` speaks two HTTP dialects: the [Responses API](https://platform.openai.com/docs/api-reference/responses) (`/v1/responses`, required for GPT-5 / o-series tool-calling on OpenAI's own service) and the [Chat Completions API](https://platform.openai.com/docs/api-reference/chat) (`/chat/completions`, the broadly-supported OpenAI-compatible wire format). @@ -219,19 +219,19 @@ The trust boundary is **the operator who launched the agent**. The harness, MCP | MCP child env | Whitelist (`PATH`, `HOME`, `TERM`, `LANG`, `LC_ALL`, `TMPDIR`) plus what the client explicitly passes. Your `ANTHROPIC_API_KEY` does not leak into MCP children. | | MCP child lifetime | Process group via `setpgid(0,0)` in `pre_exec`. On transport break or shutdown: `killpg(SIGKILL)`. Grandchildren die too. | | Server poisoning | After a timeout or transport break, the offending server is marked dead. Future calls trigger a lazy restart with exponential backoff. Other servers keep working. | -| Frame size | `SPROUT_AGENT_MAX_LINE_BYTES` (default 4 MiB). Oversize → connection killed. | +| Frame size | `BUZZ_AGENT_MAX_LINE_BYTES` (default 4 MiB). Oversize → connection killed. | | LLM response size | 16 MiB hard cap. Both `Content-Length` precheck and streaming-buffer cap. | | Cancellation | `tokio::select! { biased; _ = cancel.changed() => ... }` at every loop boundary. Cancel always wins the race. | -| Session isolation | Unlimited concurrent sessions by default (configurable via `SPROUT_AGENT_MAX_SESSIONS`). One prompt per session at a time. Each session gets its own MCP servers. | +| Session isolation | Unlimited concurrent sessions by default (configurable via `BUZZ_AGENT_MAX_SESSIONS`). One prompt per session at a time. Each session gets its own MCP servers. | | `tool_use ↔ tool_result` pairing | Encoded in the type system. Every `ToolCall` and `ToolResult` carries a `provider_id: String` (not `Option`). | ### Bounded Everything | Limit | Default | Where | |---|---|---| -| Inbound JSON-RPC frame | 4 MiB | `SPROUT_AGENT_MAX_LINE_BYTES` | +| Inbound JSON-RPC frame | 4 MiB | `BUZZ_AGENT_MAX_LINE_BYTES` | | Single prompt | 1 MiB | `MAX_PROMPT_BYTES` | -| History window | 1 MiB | `SPROUT_AGENT_MAX_HISTORY_BYTES` | +| History window | 1 MiB | `BUZZ_AGENT_MAX_HISTORY_BYTES` | | LLM response body | 16 MiB | `MAX_LLM_RESPONSE_BYTES` | | LLM error body | 4 KiB | `MAX_LLM_ERROR_BODY_BYTES` | | Tool result body | 256 KiB | `MAX_TOOL_RESULT_BYTES` | @@ -240,9 +240,9 @@ The trust boundary is **the operator who launched the agent**. The harness, MCP | Tool description bytes | 1 KiB | `MAX_DESCRIPTION_BYTES` | | Tool schema bytes | 4 KiB | `MAX_SCHEMA_BYTES` (oversize → replaced with `{}`) | | Tool calls per turn | 64 | `MAX_TOOL_CALLS_PER_TURN` | -| Loop rounds | 0 (unlimited) | `SPROUT_AGENT_MAX_ROUNDS` | -| LLM call timeout | 120 s | `SPROUT_AGENT_LLM_TIMEOUT_SECS` | -| Tool call timeout | 660 s | `SPROUT_AGENT_TOOL_TIMEOUT_SECS` | +| Loop rounds | 0 (unlimited) | `BUZZ_AGENT_MAX_ROUNDS` | +| LLM call timeout | 120 s | `BUZZ_AGENT_LLM_TIMEOUT_SECS` | +| Tool call timeout | 660 s | `BUZZ_AGENT_TOOL_TIMEOUT_SECS` | ## What This Is NOT @@ -285,13 +285,13 @@ One reader, one writer, up to 8 concurrent prompt tasks (one per session). ## Building ```bash -cargo build --release -p sprout-agent +cargo build --release -p buzz-agent ``` ## Testing ```bash -cargo test -p sprout-agent +cargo test -p buzz-agent ``` Test strategy is **real subprocess, no mocks**: diff --git a/crates/sprout-agent/sprout-agent.png b/crates/buzz-agent/sprout-agent.png similarity index 100% rename from crates/sprout-agent/sprout-agent.png rename to crates/buzz-agent/sprout-agent.png diff --git a/crates/sprout-agent/src/agent.rs b/crates/buzz-agent/src/agent.rs similarity index 99% rename from crates/sprout-agent/src/agent.rs rename to crates/buzz-agent/src/agent.rs index 6e81c9dc0..5d05d6847 100644 --- a/crates/sprout-agent/src/agent.rs +++ b/crates/buzz-agent/src/agent.rs @@ -568,7 +568,7 @@ fn format_hook_output_body(hook: &str, server: &str, text: &str) -> String { /// be unique per pair so the LLM wire format (which keys tool results by /// id) stays valid across multiple objections in one session. fn synthetic_hook_id(hook: &str, server: &str, ordinal: u64) -> String { - format!("sprout_hook_{hook}_{server}_{ordinal}") + format!("buzz_hook_{hook}_{server}_{ordinal}") } /// Append a synthetic Assistant tool-call + ToolResult pair for each hook diff --git a/crates/sprout-agent/src/auth.rs b/crates/buzz-agent/src/auth.rs similarity index 97% rename from crates/sprout-agent/src/auth.rs rename to crates/buzz-agent/src/auth.rs index 8f740348a..53b753672 100644 --- a/crates/sprout-agent/src/auth.rs +++ b/crates/buzz-agent/src/auth.rs @@ -65,7 +65,7 @@ impl TokenSource for StaticTokenSource { /// /// The `discovery_url` must return a JSON document with at least /// `authorization_endpoint` and `token_endpoint` (RFC 8414). The -/// `cache_namespace` is the directory under `~/.config/sprout-agent/oauth/` +/// `cache_namespace` is the directory under `~/.config/buzz-agent/oauth/` /// the token JSON lives in — separates providers' caches cleanly. #[derive(Debug, Clone)] pub struct PkceOAuthConfig { @@ -74,7 +74,7 @@ pub struct PkceOAuthConfig { pub scopes: Vec, pub cache_namespace: String, /// When `Some`, the engine writes tokens here instead of - /// `~/.config/sprout-agent/oauth//`. Production code + /// `~/.config/buzz-agent/oauth//`. Production code /// leaves this `None`. Integration tests use it to avoid stomping on /// a shared `$HOME` when running in parallel. pub cache_dir_override: Option, @@ -290,7 +290,7 @@ fn cache_path_for(cfg: &PkceOAuthConfig) -> Result { .map_err(|_| AgentError::Llm("oauth cache: $HOME not set".into()))?; PathBuf::from(home) .join(".config") - .join("sprout-agent") + .join("buzz-agent") .join("oauth") .join(&cfg.cache_namespace) } @@ -392,9 +392,9 @@ async fn browser_pkce_flow( } match result { Ok(_) => Html( - "

Sprout: signed in

You can close this window.

".to_string(), + "

Buzz: signed in

You can close this window.

".to_string(), ), - Err(e) => Html(format!("

Sprout auth failed

{e}
")), + Err(e) => Html(format!("

Buzz auth failed

{e}
")), } } }), @@ -525,7 +525,7 @@ mod tests { cache_dir_override: None, }; let p = cache_path_for(&cfg).unwrap(); - assert!(p.to_string_lossy().contains("/sprout-agent/oauth/demo/")); + assert!(p.to_string_lossy().contains("/buzz-agent/oauth/demo/")); assert!(p.extension().and_then(|s| s.to_str()) == Some("json")); } diff --git a/crates/sprout-agent/src/config.rs b/crates/buzz-agent/src/config.rs similarity index 85% rename from crates/sprout-agent/src/config.rs rename to crates/buzz-agent/src/config.rs index f4ede30d0..f11eb33a5 100644 --- a/crates/sprout-agent/src/config.rs +++ b/crates/buzz-agent/src/config.rs @@ -17,7 +17,7 @@ pub const HANDOFF_PROMPT_MAX_BYTES: usize = 32 * 1024; pub const HANDOFF_MAX_TOOL_NAMES: usize = 20; const DEFAULT_SYSTEM_PROMPT: &str = - "You are sprout-agent. Use the provided tools to act. Tool calls are your only output."; + "You are buzz-agent. Use the provided tools to act. Tool calls are your only output."; #[derive(Debug, Clone, Copy, PartialEq)] pub enum Provider { @@ -61,7 +61,7 @@ pub struct Config { /// handoff threshold for this budget, before the next request can exceed /// the window and 400. Default 200_000 — matching Claude 4.x windows; /// operators lower/raise it for other models. Set via - /// `SPROUT_AGENT_MAX_CONTEXT_TOKENS`. + /// `BUZZ_AGENT_MAX_CONTEXT_TOKENS`. pub max_context_tokens: u64, pub max_handoffs: usize, pub max_parallel_tools: usize, @@ -87,7 +87,7 @@ impl Config { let databricks_host = env("DATABRICKS_HOST"); let databricks_model = env("DATABRICKS_MODEL"); let provider = resolve_provider( - env("SPROUT_AGENT_PROVIDER").as_deref(), + env("BUZZ_AGENT_PROVIDER").as_deref(), env("ANTHROPIC_API_KEY").as_deref(), env("OPENAI_COMPAT_API_KEY").as_deref(), databricks_host.as_deref(), @@ -97,7 +97,7 @@ impl Config { // Universal model override — any provider will use this when its own // model env var is absent. Useful for wrapper scripts that set a single // var regardless of which provider is active. - let sprout_agent_model = env("SPROUT_AGENT_MODEL"); + let buzz_agent_model = env("BUZZ_AGENT_MODEL"); // OPENAI_COMPAT_API is only read when provider=openai, so a stray // bad value can't break an Anthropic-only deployment. @@ -110,7 +110,7 @@ impl Config { req("ANTHROPIC_API_KEY")?, resolve_model( env("ANTHROPIC_MODEL").as_deref(), - sprout_agent_model.as_deref(), + buzz_agent_model.as_deref(), ) .ok_or_else(|| "config: ANTHROPIC_MODEL required".to_string())?, env_or("ANTHROPIC_BASE_URL", "https://api.anthropic.com"), @@ -120,7 +120,7 @@ impl Config { req("OPENAI_COMPAT_API_KEY")?, resolve_model( env("OPENAI_COMPAT_MODEL").as_deref(), - sprout_agent_model.as_deref(), + buzz_agent_model.as_deref(), ) .ok_or_else(|| "config: OPENAI_COMPAT_MODEL required".to_string())?, env_or("OPENAI_COMPAT_BASE_URL", "https://api.openai.com/v1"), @@ -128,15 +128,15 @@ impl Config { ), Provider::Databricks => ( env("DATABRICKS_TOKEN").unwrap_or_default(), - resolve_model(databricks_model.as_deref(), sprout_agent_model.as_deref()) + resolve_model(databricks_model.as_deref(), buzz_agent_model.as_deref()) .ok_or_else(|| "config: DATABRICKS_MODEL required".to_string())?, databricks_host.ok_or_else(|| "config: DATABRICKS_HOST required".to_string())?, OpenAiApi::Chat, // Databricks invocations is chat-shaped ), }; - let system_prompt = match (env("SPROUT_AGENT_SYSTEM_PROMPT"), env("SPROUT_AGENT_SYSTEM_PROMPT_FILE")) { + let system_prompt = match (env("BUZZ_AGENT_SYSTEM_PROMPT"), env("BUZZ_AGENT_SYSTEM_PROMPT_FILE")) { (Some(_), Some(_)) => return Err( - "config: SPROUT_AGENT_SYSTEM_PROMPT and SPROUT_AGENT_SYSTEM_PROMPT_FILE are mutually exclusive".into()), + "config: BUZZ_AGENT_SYSTEM_PROMPT and BUZZ_AGENT_SYSTEM_PROMPT_FILE are mutually exclusive".into()), (Some(s), _) => s, (_, Some(p)) => std::fs::read_to_string(&p).map_err(|e| format!("config: read {p}: {e}"))?, _ => DEFAULT_SYSTEM_PROMPT.to_owned(), @@ -149,30 +149,27 @@ impl Config { base_url, anthropic_api_version: env_or("ANTHROPIC_API_VERSION", "2023-06-01"), openai_api, - max_rounds: parse_env("SPROUT_AGENT_MAX_ROUNDS", 0)?, - max_output_tokens: parse_env("SPROUT_AGENT_MAX_OUTPUT_TOKENS", 32_768)?, - llm_timeout: Duration::from_secs(parse_env("SPROUT_AGENT_LLM_TIMEOUT_SECS", 120)?), - tool_timeout: Duration::from_secs(parse_env("SPROUT_AGENT_TOOL_TIMEOUT_SECS", 660)?), + max_rounds: parse_env("BUZZ_AGENT_MAX_ROUNDS", 0)?, + max_output_tokens: parse_env("BUZZ_AGENT_MAX_OUTPUT_TOKENS", 32_768)?, + llm_timeout: Duration::from_secs(parse_env("BUZZ_AGENT_LLM_TIMEOUT_SECS", 120)?), + tool_timeout: Duration::from_secs(parse_env("BUZZ_AGENT_TOOL_TIMEOUT_SECS", 660)?), mcp_init_timeout: Duration::from_secs(parse_env( - "SPROUT_AGENT_MCP_INIT_TIMEOUT_SECS", + "BUZZ_AGENT_MCP_INIT_TIMEOUT_SECS", 30, )?), - mcp_max_restart_attempts: parse_env("SPROUT_AGENT_MCP_RESTART_MAX_ATTEMPTS", 3u32)?, - mcp_restart_base_ms: parse_env("SPROUT_AGENT_MCP_RESTART_BASE_MS", 500u64)?, - mcp_restart_max_ms: parse_env("SPROUT_AGENT_MCP_RESTART_MAX_MS", 30_000u64)?, - max_sessions: parse_env("SPROUT_AGENT_MAX_SESSIONS", usize::MAX)?, - max_line_bytes: parse_env("SPROUT_AGENT_MAX_LINE_BYTES", 4 * 1024 * 1024)?, - max_history_bytes: parse_env("SPROUT_AGENT_MAX_HISTORY_BYTES", 16 * 1024 * 1024)?, - max_context_tokens: parse_env("SPROUT_AGENT_MAX_CONTEXT_TOKENS", 200_000u64)?, - max_handoffs: parse_env("SPROUT_AGENT_MAX_HANDOFFS", 10)?, - max_parallel_tools: parse_env("SPROUT_AGENT_MAX_PARALLEL_TOOLS", 8usize)?, - hook_timeout: Duration::from_millis(parse_env( - "SPROUT_AGENT_HOOK_TIMEOUT_MS", - 2500u64, - )?), - stop_max_rejections: parse_env("SPROUT_AGENT_STOP_MAX_REJECTIONS", 3u32)?, + mcp_max_restart_attempts: parse_env("BUZZ_AGENT_MCP_RESTART_MAX_ATTEMPTS", 3u32)?, + mcp_restart_base_ms: parse_env("BUZZ_AGENT_MCP_RESTART_BASE_MS", 500u64)?, + mcp_restart_max_ms: parse_env("BUZZ_AGENT_MCP_RESTART_MAX_MS", 30_000u64)?, + max_sessions: parse_env("BUZZ_AGENT_MAX_SESSIONS", usize::MAX)?, + max_line_bytes: parse_env("BUZZ_AGENT_MAX_LINE_BYTES", 4 * 1024 * 1024)?, + max_history_bytes: parse_env("BUZZ_AGENT_MAX_HISTORY_BYTES", 16 * 1024 * 1024)?, + max_context_tokens: parse_env("BUZZ_AGENT_MAX_CONTEXT_TOKENS", 200_000u64)?, + max_handoffs: parse_env("BUZZ_AGENT_MAX_HANDOFFS", 10)?, + max_parallel_tools: parse_env("BUZZ_AGENT_MAX_PARALLEL_TOOLS", 8usize)?, + hook_timeout: Duration::from_millis(parse_env("BUZZ_AGENT_HOOK_TIMEOUT_MS", 2500u64)?), + stop_max_rejections: parse_env("BUZZ_AGENT_STOP_MAX_REJECTIONS", 3u32)?, hook_servers: parse_hook_servers_env("MCP_HOOK_SERVERS"), - hints_enabled: parse_env("SPROUT_AGENT_NO_HINTS", 0u8)? == 0, + hints_enabled: parse_env("BUZZ_AGENT_NO_HINTS", 0u8)? == 0, }; cfg.validate()?; Ok(cfg) @@ -184,51 +181,52 @@ impl Config { const MIN_TIMEOUT: Duration = Duration::from_secs(1); if self.max_output_tokens < 1 { - return Err("config: SPROUT_AGENT_MAX_OUTPUT_TOKENS must be >= 1".into()); + return Err("config: BUZZ_AGENT_MAX_OUTPUT_TOKENS must be >= 1".into()); } if self.max_context_tokens <= u64::from(self.max_output_tokens) { return Err(format!( - "config: SPROUT_AGENT_MAX_CONTEXT_TOKENS ({}) must be > SPROUT_AGENT_MAX_OUTPUT_TOKENS ({}) — the context window must leave room for the response", + "config: BUZZ_AGENT_MAX_CONTEXT_TOKENS ({}) must be > BUZZ_AGENT_MAX_OUTPUT_TOKENS ({}) — the context window must leave room for the response", self.max_context_tokens, self.max_output_tokens )); } if self.max_history_bytes < MIN_HISTORY_BYTES { return Err(format!( - "config: SPROUT_AGENT_MAX_HISTORY_BYTES must be >= {MIN_HISTORY_BYTES}" + "config: BUZZ_AGENT_MAX_HISTORY_BYTES must be >= {MIN_HISTORY_BYTES}" )); } if self.max_history_bytes < MAX_PROMPT_BYTES { return Err(format!( - "config: SPROUT_AGENT_MAX_HISTORY_BYTES ({}) must be >= MAX_PROMPT_BYTES ({MAX_PROMPT_BYTES})", + "config: BUZZ_AGENT_MAX_HISTORY_BYTES ({}) must be >= MAX_PROMPT_BYTES ({MAX_PROMPT_BYTES})", self.max_history_bytes )); } if self.max_line_bytes < MIN_LINE_BYTES { return Err(format!( - "config: SPROUT_AGENT_MAX_LINE_BYTES must be >= {MIN_LINE_BYTES}" + "config: BUZZ_AGENT_MAX_LINE_BYTES must be >= {MIN_LINE_BYTES}" )); } if self.llm_timeout < MIN_TIMEOUT { - return Err("config: SPROUT_AGENT_LLM_TIMEOUT_SECS must be >= 1".into()); + return Err("config: BUZZ_AGENT_LLM_TIMEOUT_SECS must be >= 1".into()); } if self.tool_timeout < MIN_TIMEOUT { - return Err("config: SPROUT_AGENT_TOOL_TIMEOUT_SECS must be >= 1".into()); + return Err("config: BUZZ_AGENT_TOOL_TIMEOUT_SECS must be >= 1".into()); } if self.mcp_init_timeout < MIN_TIMEOUT { - return Err("config: SPROUT_AGENT_MCP_INIT_TIMEOUT_SECS must be >= 1".into()); + return Err("config: BUZZ_AGENT_MCP_INIT_TIMEOUT_SECS must be >= 1".into()); } if self.max_parallel_tools < 1 { - return Err("config: SPROUT_AGENT_MAX_PARALLEL_TOOLS must be >= 1".into()); + return Err("config: BUZZ_AGENT_MAX_PARALLEL_TOOLS must be >= 1".into()); } if self.mcp_max_restart_attempts < 1 { - return Err("config: SPROUT_AGENT_MCP_RESTART_MAX_ATTEMPTS must be >= 1".into()); + return Err("config: BUZZ_AGENT_MCP_RESTART_MAX_ATTEMPTS must be >= 1".into()); } if self.mcp_restart_base_ms < 1 { - return Err("config: SPROUT_AGENT_MCP_RESTART_BASE_MS must be >= 1".into()); + return Err("config: BUZZ_AGENT_MCP_RESTART_BASE_MS must be >= 1".into()); } if self.mcp_restart_max_ms < self.mcp_restart_base_ms { return Err( - "config: SPROUT_AGENT_MCP_RESTART_MAX_MS must be >= SPROUT_AGENT_MCP_RESTART_BASE_MS".into(), + "config: BUZZ_AGENT_MCP_RESTART_MAX_MS must be >= BUZZ_AGENT_MCP_RESTART_BASE_MS" + .into(), ); } Ok(()) @@ -298,13 +296,13 @@ fn resolve_provider( ), "databricks" => Ok(Provider::Databricks), _ => Err(format!( - "config: SPROUT_AGENT_PROVIDER={raw} not supported" + "config: BUZZ_AGENT_PROVIDER={raw} not supported" )), } } None if databricks_ready => Ok(Provider::Databricks), None => Err( - "config: SPROUT_AGENT_PROVIDER required (or set DATABRICKS_HOST and DATABRICKS_MODEL for Databricks OAuth fallback)".into(), + "config: BUZZ_AGENT_PROVIDER required (or set DATABRICKS_HOST and DATABRICKS_MODEL for Databricks OAuth fallback)".into(), ), } } @@ -592,13 +590,13 @@ mod tests { assert!(err.contains("OPENAI_COMPAT_API_KEY required")); let err = resolve_provider(None, None, None, Some("https://dbc.example"), None).unwrap_err(); - assert!(err.contains("SPROUT_AGENT_PROVIDER required")); + assert!(err.contains("BUZZ_AGENT_PROVIDER required")); } #[test] fn resolve_provider_unsupported_error_preserves_user_casing() { let err = resolve_provider(Some("OpenAIish"), None, None, None, None).unwrap_err(); - assert!(err.contains("SPROUT_AGENT_PROVIDER=OpenAIish")); + assert!(err.contains("BUZZ_AGENT_PROVIDER=OpenAIish")); } #[test] diff --git a/crates/sprout-agent/src/handoff.rs b/crates/buzz-agent/src/handoff.rs similarity index 100% rename from crates/sprout-agent/src/handoff.rs rename to crates/buzz-agent/src/handoff.rs diff --git a/crates/sprout-agent/src/hints.rs b/crates/buzz-agent/src/hints.rs similarity index 97% rename from crates/sprout-agent/src/hints.rs rename to crates/buzz-agent/src/hints.rs index de6d37f39..39d0dfada 100644 --- a/crates/sprout-agent/src/hints.rs +++ b/crates/buzz-agent/src/hints.rs @@ -368,11 +368,11 @@ mod tests { std::fs::write(cwd.join("AGENTS.md"), "Project-level hints.").unwrap(); - let skill_dir = cwd.join(".agents/skills/sprout-cli"); + let skill_dir = cwd.join(".agents/skills/buzz-cli"); std::fs::create_dir_all(&skill_dir).unwrap(); std::fs::write( skill_dir.join("SKILL.md"), - "---\nname: sprout-cli\ndescription: CLI reference for Sprout managed agents\n---\nUse `sprout` to manage agents.\n", + "---\nname: buzz-cli\ndescription: CLI reference for Buzz managed agents\n---\nUse `buzz` to manage agents.\n", ) .unwrap(); @@ -392,12 +392,12 @@ mod tests { "missing Available Skills" ); assert!( - result.contains("sprout-cli: CLI reference for Sprout managed agents"), + result.contains("buzz-cli: CLI reference for Buzz managed agents"), "missing skill bullet" ); - assert!(result.contains("### sprout-cli"), "missing skill header"); + assert!(result.contains("### buzz-cli"), "missing skill header"); assert!( - result.contains("Use `sprout` to manage agents."), + result.contains("Use `buzz` to manage agents."), "missing skill body" ); } diff --git a/crates/sprout-agent/src/lib.rs b/crates/buzz-agent/src/lib.rs similarity index 97% rename from crates/sprout-agent/src/lib.rs rename to crates/buzz-agent/src/lib.rs index 016149e68..442447c64 100644 --- a/crates/sprout-agent/src/lib.rs +++ b/crates/buzz-agent/src/lib.rs @@ -73,7 +73,7 @@ pub fn run() -> Result<(), Box> { Ok(()) } -/// `sprout-agent auth ` — run the interactive auth flow for a +/// `buzz-agent auth ` — run the interactive auth flow for a /// provider and persist the result, then exit. Today the only provider is /// `databricks` (OAuth 2.0 PKCE). Reads `DATABRICKS_HOST` from env; needs /// a browser on the machine. @@ -95,13 +95,11 @@ async fn auth_subcommand(args: &[String]) -> Result<(), Box Err(format!("auth: unknown provider {other:?}").into()), - None => Err("auth: provider required (try: sprout-agent auth databricks)".into()), + None => Err("auth: provider required (try: buzz-agent auth databricks)".into()), } } @@ -229,7 +227,7 @@ async fn initialize(id: Value, params: Value, wire_tx: &WireSender) { "promptCapabilities": { "image": false, "audio": false, "embeddedContext": false }, "mcpCapabilities": { "http": false, "sse": false }, }, - "agentInfo": { "name": "sprout-agent", "version": env!("CARGO_PKG_VERSION") }, + "agentInfo": { "name": "buzz-agent", "version": env!("CARGO_PKG_VERSION") }, }), ), ) diff --git a/crates/sprout-agent/src/llm.rs b/crates/buzz-agent/src/llm.rs similarity index 99% rename from crates/sprout-agent/src/llm.rs rename to crates/buzz-agent/src/llm.rs index 92e3fca3f..b9201bcf3 100644 --- a/crates/sprout-agent/src/llm.rs +++ b/crates/buzz-agent/src/llm.rs @@ -12,7 +12,7 @@ use crate::types::{ /// Databricks OAuth client_id — the public Databricks-published CLI client. /// PKCE-only, no secret. Same identifier goose uses, so a user's browser -/// consent for `databricks-cli` covers sprout-agent too. +/// consent for `databricks-cli` covers buzz-agent too. const DATABRICKS_CLIENT_ID: &str = "databricks-cli"; const DATABRICKS_OAUTH_SCOPES: &[&str] = &["all-apis", "offline_access"]; diff --git a/crates/sprout-agent/src/main.rs b/crates/buzz-agent/src/main.rs similarity index 66% rename from crates/sprout-agent/src/main.rs rename to crates/buzz-agent/src/main.rs index 1dd65b63d..c76b7f184 100644 --- a/crates/sprout-agent/src/main.rs +++ b/crates/buzz-agent/src/main.rs @@ -1,5 +1,5 @@ fn main() { - if let Err(e) = sprout_agent::run() { + if let Err(e) = buzz_agent::run() { eprintln!("Error: {e}"); std::process::exit(1); } diff --git a/crates/sprout-agent/src/mcp.rs b/crates/buzz-agent/src/mcp.rs similarity index 99% rename from crates/sprout-agent/src/mcp.rs rename to crates/buzz-agent/src/mcp.rs index 34a20d6b6..9e96ba201 100644 --- a/crates/sprout-agent/src/mcp.rs +++ b/crates/buzz-agent/src/mcp.rs @@ -42,12 +42,12 @@ const PASSTHROUGH_ENV: &[&str] = &[ "GIT_ASKPASS", "GIT_SSH_COMMAND", "GIT_CONFIG_GLOBAL", - // Sprout identity — dev-mcp writes NOSTR_PRIVATE_KEY to a keyfile then - // removes it from its own env (children never see it). SPROUT_PRIVATE_KEY - // and SPROUT_RELAY_URL are kept for the sprout CLI. + // Buzz identity — dev-mcp writes NOSTR_PRIVATE_KEY to a keyfile then + // removes it from its own env (children never see it). BUZZ_PRIVATE_KEY + // and BUZZ_RELAY_URL are kept for the buzz CLI. "NOSTR_PRIVATE_KEY", - "SPROUT_PRIVATE_KEY", - "SPROUT_RELAY_URL", + "BUZZ_PRIVATE_KEY", + "BUZZ_RELAY_URL", ]; type Client = RunningService; diff --git a/crates/sprout-agent/src/types.rs b/crates/buzz-agent/src/types.rs similarity index 100% rename from crates/sprout-agent/src/types.rs rename to crates/buzz-agent/src/types.rs diff --git a/crates/sprout-agent/src/wire.rs b/crates/buzz-agent/src/wire.rs similarity index 99% rename from crates/sprout-agent/src/wire.rs rename to crates/buzz-agent/src/wire.rs index 60494b63a..ba69eb1d9 100644 --- a/crates/sprout-agent/src/wire.rs +++ b/crates/buzz-agent/src/wire.rs @@ -83,7 +83,7 @@ pub fn classify(msg: &Value) -> Inbound { params, }, (Some(m), None) => Inbound::Notification { method: m, params }, - // Bare responses (id present, no method) are unexpected — sprout-agent + // Bare responses (id present, no method) are unexpected — buzz-agent // does not issue requests to the client. Ignore silently. (None, Some(_)) => Inbound::Ignored, (None, None) => Inbound::Invalid { diff --git a/crates/sprout-agent/tests/bin/fake_mcp.rs b/crates/buzz-agent/tests/bin/fake_mcp.rs similarity index 100% rename from crates/sprout-agent/tests/bin/fake_mcp.rs rename to crates/buzz-agent/tests/bin/fake_mcp.rs diff --git a/crates/sprout-agent/tests/databricks_oauth.rs b/crates/buzz-agent/tests/databricks_oauth.rs similarity index 95% rename from crates/sprout-agent/tests/databricks_oauth.rs rename to crates/buzz-agent/tests/databricks_oauth.rs index 8e43ac05c..25f1e120b 100644 --- a/crates/sprout-agent/tests/databricks_oauth.rs +++ b/crates/buzz-agent/tests/databricks_oauth.rs @@ -2,11 +2,11 @@ //! //! No browser dance — we cover the silent-refresh and cache-hit paths //! against a stubbed OIDC server (axum). The interactive browser flow is -//! exercised manually via the `sprout-agent auth databricks` subcommand +//! exercised manually via the `buzz-agent auth databricks` subcommand //! (see `lib.rs::auth_subcommand`). //! //! The second test module (further down) is an ACP-level envelope -//! regression: it spawns the real `sprout-agent` binary with +//! regression: it spawns the real `buzz-agent` binary with //! `DATABRICKS_TOKEN` set and a stub HTTP server, then asserts the wire //! shape we send to Databricks. @@ -17,9 +17,9 @@ use std::time::{SystemTime, UNIX_EPOCH}; use axum::extract::Form; use axum::{routing::get, routing::post, Json, Router}; +use buzz_agent::auth::{PkceOAuthConfig, PkceOAuthTokenSource, TokenSource}; use serde::Deserialize; use serde_json::json; -use sprout_agent::auth::{PkceOAuthConfig, PkceOAuthTokenSource, TokenSource}; use tempfile::TempDir; #[derive(Deserialize)] @@ -211,7 +211,7 @@ async fn refreshed_token_is_persisted_to_disk() { // ──────────────────────────────────────────────────────────────────────────── // ACP-level envelope regression test. // -// Boots the real sprout-agent binary with `DATABRICKS_TOKEN` set (so the +// Boots the real buzz-agent binary with `DATABRICKS_TOKEN` set (so the // OAuth dance is skipped) pointed at a stub HTTP server that captures every // inbound request. Asserts the wire-level shape Databricks model serving // requires: path is `/serving-endpoints//invocations`, Authorization @@ -332,21 +332,21 @@ impl Drop for AgentHarness { impl AgentHarness { async fn spawn_databricks(base_url: &str, model: &str) -> Self { - let bin = env!("CARGO_BIN_EXE_sprout-agent"); + let bin = env!("CARGO_BIN_EXE_buzz-agent"); let mut cmd = tokio::process::Command::new(bin); - cmd.env("SPROUT_AGENT_PROVIDER", "databricks") + cmd.env("BUZZ_AGENT_PROVIDER", "databricks") .env("DATABRICKS_HOST", base_url) .env("DATABRICKS_MODEL", model) .env("DATABRICKS_TOKEN", "test-bearer") - .env("SPROUT_AGENT_LLM_TIMEOUT_SECS", "5") - .env("SPROUT_AGENT_TOOL_TIMEOUT_SECS", "5") - .env("SPROUT_AGENT_MAX_ROUNDS", "2") - .env("SPROUT_AGENT_MCP_INIT_TIMEOUT_SECS", "2") + .env("BUZZ_AGENT_LLM_TIMEOUT_SECS", "5") + .env("BUZZ_AGENT_TOOL_TIMEOUT_SECS", "5") + .env("BUZZ_AGENT_MAX_ROUNDS", "2") + .env("BUZZ_AGENT_MCP_INIT_TIMEOUT_SECS", "2") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::null()) .kill_on_drop(true); - let mut child = cmd.spawn().expect("spawn sprout-agent"); + let mut child = cmd.spawn().expect("spawn buzz-agent"); let stdin = child.stdin.take().unwrap(); let stdout = BufReader::new(child.stdout.take().unwrap()); Self { diff --git a/crates/sprout-agent/tests/fake_llm.rs b/crates/buzz-agent/tests/fake_llm.rs similarity index 94% rename from crates/sprout-agent/tests/fake_llm.rs rename to crates/buzz-agent/tests/fake_llm.rs index 8e07b2a77..740288819 100644 --- a/crates/sprout-agent/tests/fake_llm.rs +++ b/crates/buzz-agent/tests/fake_llm.rs @@ -1,4 +1,4 @@ -//! Integration test: fake LLM HTTP server + sprout-agent subprocess. +//! Integration test: fake LLM HTTP server + buzz-agent subprocess. //! //! Drives the agent through the ACP wire protocol and verifies: //! - initialize / session/new responses @@ -70,20 +70,20 @@ struct Harness { impl Harness { async fn spawn(base_url: &str) -> Self { - let bin = env!("CARGO_BIN_EXE_sprout-agent"); + let bin = env!("CARGO_BIN_EXE_buzz-agent"); let mut cmd = tokio::process::Command::new(bin); - cmd.env("SPROUT_AGENT_PROVIDER", "openai") + cmd.env("BUZZ_AGENT_PROVIDER", "openai") .env("OPENAI_COMPAT_API_KEY", "test") .env("OPENAI_COMPAT_MODEL", "fake-model") .env("OPENAI_COMPAT_BASE_URL", base_url) - .env("SPROUT_AGENT_LLM_TIMEOUT_SECS", "5") - .env("SPROUT_AGENT_TOOL_TIMEOUT_SECS", "5") - .env("SPROUT_AGENT_MAX_ROUNDS", "4") + .env("BUZZ_AGENT_LLM_TIMEOUT_SECS", "5") + .env("BUZZ_AGENT_TOOL_TIMEOUT_SECS", "5") + .env("BUZZ_AGENT_MAX_ROUNDS", "4") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) .kill_on_drop(true); - let mut child = cmd.spawn().expect("spawn sprout-agent"); + let mut child = cmd.spawn().expect("spawn buzz-agent"); let stdin = child.stdin.take().unwrap(); let stdout = BufReader::new(child.stdout.take().unwrap()); Self { @@ -174,7 +174,7 @@ async fn init_session(h: &mut Harness) -> String { .await; let r = h.recv().await; assert_eq!(r["result"]["protocolVersion"], 1); - assert_eq!(r["result"]["agentInfo"]["name"], "sprout-agent"); + assert_eq!(r["result"]["agentInfo"]["name"], "buzz-agent"); h.send("session/new", json!({"cwd":"/tmp","mcpServers":[]})) .await; let r = h.recv().await; @@ -313,13 +313,13 @@ async fn rejects_oversized_line() { // Set a tiny max line and send something larger; agent must abort with an // io error and not OOM. let url = spawn_fake_llm(vec![]).await; - let bin = env!("CARGO_BIN_EXE_sprout-agent"); + let bin = env!("CARGO_BIN_EXE_buzz-agent"); let mut cmd = tokio::process::Command::new(bin); - cmd.env("SPROUT_AGENT_PROVIDER", "openai") + cmd.env("BUZZ_AGENT_PROVIDER", "openai") .env("OPENAI_COMPAT_API_KEY", "test") .env("OPENAI_COMPAT_MODEL", "fake-model") .env("OPENAI_COMPAT_BASE_URL", &url) - .env("SPROUT_AGENT_MAX_LINE_BYTES", "256") + .env("BUZZ_AGENT_MAX_LINE_BYTES", "256") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::null()) diff --git a/crates/sprout-agent/tests/golden_transcripts.rs b/crates/buzz-agent/tests/golden_transcripts.rs similarity index 96% rename from crates/sprout-agent/tests/golden_transcripts.rs rename to crates/buzz-agent/tests/golden_transcripts.rs index 44c1e2b81..e691e4411 100644 --- a/crates/sprout-agent/tests/golden_transcripts.rs +++ b/crates/buzz-agent/tests/golden_transcripts.rs @@ -17,14 +17,14 @@ struct Harness { impl Harness { async fn spawn(extra: &[(&str, &str)]) -> Self { - let bin = env!("CARGO_BIN_EXE_sprout-agent"); + let bin = env!("CARGO_BIN_EXE_buzz-agent"); let mut cmd = tokio::process::Command::new(bin); - cmd.env("SPROUT_AGENT_PROVIDER", "openai") + cmd.env("BUZZ_AGENT_PROVIDER", "openai") .env("OPENAI_COMPAT_API_KEY", "test") .env("OPENAI_COMPAT_MODEL", "fake-model") - .env("SPROUT_AGENT_LLM_TIMEOUT_SECS", "5") - .env("SPROUT_AGENT_TOOL_TIMEOUT_SECS", "5") - .env("SPROUT_AGENT_MAX_ROUNDS", "4") + .env("BUZZ_AGENT_LLM_TIMEOUT_SECS", "5") + .env("BUZZ_AGENT_TOOL_TIMEOUT_SECS", "5") + .env("BUZZ_AGENT_MAX_ROUNDS", "4") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::null()) @@ -32,7 +32,7 @@ impl Harness { for (k, v) in extra { cmd.env(k, v); } - let mut child = cmd.spawn().expect("spawn sprout-agent"); + let mut child = cmd.spawn().expect("spawn buzz-agent"); let stdin = child.stdin.take().unwrap(); let stdout = BufReader::new(child.stdout.take().unwrap()); Self { @@ -186,7 +186,7 @@ async fn handshake(h: &mut Harness) -> String { .await; let init = h.recv_for_id(init_id).await; assert_eq!(init["result"]["protocolVersion"], 1); - assert_eq!(init["result"]["agentInfo"]["name"], "sprout-agent"); + assert_eq!(init["result"]["agentInfo"]["name"], "buzz-agent"); assert_eq!( init["result"]["agentCapabilities"]["promptCapabilities"]["image"], false @@ -470,13 +470,13 @@ async fn test_concurrent_prompt_rejected() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_oversized_line_kills_agent() { let url = spawn_fake_llm(vec![]).await; - let bin = env!("CARGO_BIN_EXE_sprout-agent"); + let bin = env!("CARGO_BIN_EXE_buzz-agent"); let mut cmd = tokio::process::Command::new(bin); - cmd.env("SPROUT_AGENT_PROVIDER", "openai") + cmd.env("BUZZ_AGENT_PROVIDER", "openai") .env("OPENAI_COMPAT_API_KEY", "test") .env("OPENAI_COMPAT_MODEL", "fake-model") .env("OPENAI_COMPAT_BASE_URL", &url) - .env("SPROUT_AGENT_MAX_LINE_BYTES", "256") + .env("BUZZ_AGENT_MAX_LINE_BYTES", "256") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::null()) diff --git a/crates/sprout-agent/tests/hints_integration.rs b/crates/buzz-agent/tests/hints_integration.rs similarity index 96% rename from crates/sprout-agent/tests/hints_integration.rs rename to crates/buzz-agent/tests/hints_integration.rs index d3070a050..d5f266f82 100644 --- a/crates/sprout-agent/tests/hints_integration.rs +++ b/crates/buzz-agent/tests/hints_integration.rs @@ -94,16 +94,16 @@ struct Harness { impl Harness { async fn spawn_with_env(base_url: &str, extra: &[(&str, &str)]) -> Self { - let bin = env!("CARGO_BIN_EXE_sprout-agent"); + let bin = env!("CARGO_BIN_EXE_buzz-agent"); let mut cmd = tokio::process::Command::new(bin); - cmd.env("SPROUT_AGENT_PROVIDER", "openai") + cmd.env("BUZZ_AGENT_PROVIDER", "openai") .env("OPENAI_COMPAT_API_KEY", "test") .env("OPENAI_COMPAT_MODEL", "fake-model") .env("OPENAI_COMPAT_BASE_URL", base_url) - .env("SPROUT_AGENT_LLM_TIMEOUT_SECS", "5") - .env("SPROUT_AGENT_TOOL_TIMEOUT_SECS", "5") - .env("SPROUT_AGENT_MAX_ROUNDS", "8") - .env("SPROUT_AGENT_MCP_INIT_TIMEOUT_SECS", "2"); + .env("BUZZ_AGENT_LLM_TIMEOUT_SECS", "5") + .env("BUZZ_AGENT_TOOL_TIMEOUT_SECS", "5") + .env("BUZZ_AGENT_MAX_ROUNDS", "8") + .env("BUZZ_AGENT_MCP_INIT_TIMEOUT_SECS", "2"); for (k, v) in extra { cmd.env(k, v); } @@ -111,7 +111,7 @@ impl Harness { .stdout(Stdio::piped()) .stderr(Stdio::inherit()) .kill_on_drop(true); - let mut child = cmd.spawn().expect("spawn sprout-agent"); + let mut child = cmd.spawn().expect("spawn buzz-agent"); let stdin = child.stdin.take().unwrap(); let stdout = BufReader::new(child.stdout.take().unwrap()); Self { @@ -199,7 +199,7 @@ async fn init_session(h: &mut Harness, cwd: &str) -> String { async fn hints_loaded_from_cwd_agents_md() { let tmp = tempfile::TempDir::new().unwrap(); let cwd = tmp.path(); - let marker = "SPROUT_HINTS_MARKER_42"; + let marker = "BUZZ_HINTS_MARKER_42"; std::fs::write(cwd.join("AGENTS.md"), marker).unwrap(); let llm = spawn_capturing_llm(vec![openai_text("done")]).await; @@ -224,7 +224,7 @@ async fn hints_loaded_from_cwd_agents_md() { h.shutdown().await; } -/// SPROUT_AGENT_NO_HINTS=1 suppresses hint loading. +/// BUZZ_AGENT_NO_HINTS=1 suppresses hint loading. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn hints_suppressed_with_env_var() { let tmp = tempfile::TempDir::new().unwrap(); @@ -233,7 +233,7 @@ async fn hints_suppressed_with_env_var() { std::fs::write(cwd.join("AGENTS.md"), marker).unwrap(); let llm = spawn_capturing_llm(vec![openai_text("done")]).await; - let mut h = Harness::spawn_with_env(&llm.url, &[("SPROUT_AGENT_NO_HINTS", "1")]).await; + let mut h = Harness::spawn_with_env(&llm.url, &[("BUZZ_AGENT_NO_HINTS", "1")]).await; let sid = init_session(&mut h, cwd.to_str().unwrap()).await; let p = h diff --git a/crates/sprout-agent/tests/openai_auto_upgrade.rs b/crates/buzz-agent/tests/openai_auto_upgrade.rs similarity index 95% rename from crates/sprout-agent/tests/openai_auto_upgrade.rs rename to crates/buzz-agent/tests/openai_auto_upgrade.rs index 69f559174..f85a52bfa 100644 --- a/crates/sprout-agent/tests/openai_auto_upgrade.rs +++ b/crates/buzz-agent/tests/openai_auto_upgrade.rs @@ -6,7 +6,7 @@ //! 2. accepts a POST to /responses, replies 200 with a Responses-shaped //! JSON envelope. //! -//! Spawns `sprout-agent` with `provider=openai` + `OPENAI_COMPAT_API=auto` +//! Spawns `buzz-agent` with `provider=openai` + `OPENAI_COMPAT_API=auto` //! pointed at the fake server, drives one prompt through the ACP wire //! protocol, and verifies the prompt completes with `stopReason=end_turn` //! — which can only happen if the second (Responses) request succeeded. @@ -127,24 +127,24 @@ fn spawn_fake_provider() -> (String, Arc, Arc) { async fn openai_auto_upgrades_chat_to_responses_on_databricks_signal() { let (base_url, chat_hits, resp_hits) = spawn_fake_provider(); - let bin = env!("CARGO_BIN_EXE_sprout-agent"); + let bin = env!("CARGO_BIN_EXE_buzz-agent"); let mut cmd = Command::new(bin); - cmd.env("SPROUT_AGENT_PROVIDER", "openai") + cmd.env("BUZZ_AGENT_PROVIDER", "openai") .env("OPENAI_COMPAT_API_KEY", "test") .env("OPENAI_COMPAT_MODEL", "gpt-5.5") .env("OPENAI_COMPAT_BASE_URL", &base_url) // No OPENAI_COMPAT_API — must default to "auto" so the upgrade // path is enabled. .env_remove("OPENAI_COMPAT_API") - .env("SPROUT_AGENT_LLM_TIMEOUT_SECS", "5") - .env("SPROUT_AGENT_MAX_ROUNDS", "4") - .env("SPROUT_AGENT_MCP_INIT_TIMEOUT_SECS", "2") + .env("BUZZ_AGENT_LLM_TIMEOUT_SECS", "5") + .env("BUZZ_AGENT_MAX_ROUNDS", "4") + .env("BUZZ_AGENT_MCP_INIT_TIMEOUT_SECS", "2") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) .kill_on_drop(true); - let mut child = cmd.spawn().expect("spawn sprout-agent"); + let mut child = cmd.spawn().expect("spawn buzz-agent"); let mut stdin = child.stdin.take().unwrap(); let mut stdout = BufReader::new(child.stdout.take().unwrap()); diff --git a/crates/sprout-agent/tests/regressions.rs b/crates/buzz-agent/tests/regressions.rs similarity index 96% rename from crates/sprout-agent/tests/regressions.rs rename to crates/buzz-agent/tests/regressions.rs index c80eb6b71..3fdaa9c59 100644 --- a/crates/sprout-agent/tests/regressions.rs +++ b/crates/buzz-agent/tests/regressions.rs @@ -97,16 +97,16 @@ struct Harness { impl Harness { async fn spawn_with_env(base_url: &str, extra: &[(&str, &str)]) -> Self { - let bin = env!("CARGO_BIN_EXE_sprout-agent"); + let bin = env!("CARGO_BIN_EXE_buzz-agent"); let mut cmd = tokio::process::Command::new(bin); - cmd.env("SPROUT_AGENT_PROVIDER", "openai") + cmd.env("BUZZ_AGENT_PROVIDER", "openai") .env("OPENAI_COMPAT_API_KEY", "test") .env("OPENAI_COMPAT_MODEL", "fake-model") .env("OPENAI_COMPAT_BASE_URL", base_url) - .env("SPROUT_AGENT_LLM_TIMEOUT_SECS", "5") - .env("SPROUT_AGENT_TOOL_TIMEOUT_SECS", "5") - .env("SPROUT_AGENT_MAX_ROUNDS", "8") - .env("SPROUT_AGENT_MCP_INIT_TIMEOUT_SECS", "2"); + .env("BUZZ_AGENT_LLM_TIMEOUT_SECS", "5") + .env("BUZZ_AGENT_TOOL_TIMEOUT_SECS", "5") + .env("BUZZ_AGENT_MAX_ROUNDS", "8") + .env("BUZZ_AGENT_MCP_INIT_TIMEOUT_SECS", "2"); for (k, v) in extra { cmd.env(k, v); } @@ -114,7 +114,7 @@ impl Harness { .stdout(Stdio::piped()) .stderr(Stdio::inherit()) .kill_on_drop(true); - let mut child = cmd.spawn().expect("spawn sprout-agent"); + let mut child = cmd.spawn().expect("spawn buzz-agent"); let stdin = child.stdin.take().unwrap(); let stdout = BufReader::new(child.stdout.take().unwrap()); Self { @@ -540,8 +540,8 @@ async fn history_budget_evicts_old_turns() { let mut h = Harness::spawn_with_env( &llm.url, &[ - ("SPROUT_AGENT_MAX_HISTORY_BYTES", &BUDGET.to_string()), - ("SPROUT_AGENT_MAX_HANDOFFS", "0"), // exercise truncation, not handoff + ("BUZZ_AGENT_MAX_HISTORY_BYTES", &BUDGET.to_string()), + ("BUZZ_AGENT_MAX_HANDOFFS", "0"), // exercise truncation, not handoff ], ) .await; @@ -777,7 +777,7 @@ async fn hook_stop_blocks_premature_end() { &llm.url, &[ ("MCP_HOOK_SERVERS", "fake"), - ("SPROUT_AGENT_STOP_MAX_REJECTIONS", "10"), + ("BUZZ_AGENT_STOP_MAX_REJECTIONS", "10"), ], ) .await; @@ -858,7 +858,7 @@ async fn hook_stop_budget_exhausted() { &llm.url, &[ ("MCP_HOOK_SERVERS", "fake"), - ("SPROUT_AGENT_STOP_MAX_REJECTIONS", "1"), + ("BUZZ_AGENT_STOP_MAX_REJECTIONS", "1"), ], ) .await; @@ -907,7 +907,7 @@ async fn hook_stop_consecutive_end_turn() { &[ ("MCP_HOOK_SERVERS", "fake"), // Set high so we don't trip the budget instead. - ("SPROUT_AGENT_STOP_MAX_REJECTIONS", "10"), + ("BUZZ_AGENT_STOP_MAX_REJECTIONS", "10"), ], ) .await; @@ -1046,9 +1046,9 @@ async fn hook_post_compact_injects_after_handoff() { &llm.url, &[ ("MCP_HOOK_SERVERS", "fake"), - ("SPROUT_AGENT_MAX_HISTORY_BYTES", &(1024 * 1024).to_string()), + ("BUZZ_AGENT_MAX_HISTORY_BYTES", &(1024 * 1024).to_string()), // Allow at least one handoff. - ("SPROUT_AGENT_MAX_HANDOFFS", "3"), + ("BUZZ_AGENT_MAX_HANDOFFS", "3"), ], ) .await; @@ -1157,13 +1157,13 @@ async fn token_usage_over_budget_triggers_handoff() { let mut h = Harness::spawn_with_env( &llm.url, &[ - ("SPROUT_AGENT_MAX_CONTEXT_TOKENS", "1000"), - ("SPROUT_AGENT_MAX_OUTPUT_TOKENS", "100"), - ("SPROUT_AGENT_MAX_HANDOFFS", "3"), + ("BUZZ_AGENT_MAX_CONTEXT_TOKENS", "1000"), + ("BUZZ_AGENT_MAX_OUTPUT_TOKENS", "100"), + ("BUZZ_AGENT_MAX_HANDOFFS", "3"), // Huge byte budget so the byte path can NOT be what fires — only // the token gate can explain a handoff on these tiny prompts. ( - "SPROUT_AGENT_MAX_HISTORY_BYTES", + "BUZZ_AGENT_MAX_HISTORY_BYTES", &(16 * 1024 * 1024).to_string(), ), ], @@ -1238,13 +1238,13 @@ async fn stale_usage_plus_history_growth_triggers_handoff() { let mut h = Harness::spawn_with_env( &llm.url, &[ - ("SPROUT_AGENT_MAX_CONTEXT_TOKENS", "10000"), - ("SPROUT_AGENT_MAX_OUTPUT_TOKENS", "1000"), - ("SPROUT_AGENT_MAX_HANDOFFS", "3"), + ("BUZZ_AGENT_MAX_CONTEXT_TOKENS", "10000"), + ("BUZZ_AGENT_MAX_OUTPUT_TOKENS", "1000"), + ("BUZZ_AGENT_MAX_HANDOFFS", "3"), // Huge byte budget so the None-path byte fallback can't be what // fires — only the token-mode growth estimate can explain it. ( - "SPROUT_AGENT_MAX_HISTORY_BYTES", + "BUZZ_AGENT_MAX_HISTORY_BYTES", &(16 * 1024 * 1024).to_string(), ), ], @@ -1277,7 +1277,7 @@ async fn stale_usage_plus_history_growth_triggers_handoff() { h.shutdown().await; } -/// `_Stop` hook that takes longer than `SPROUT_AGENT_HOOK_TIMEOUT_MS` +/// `_Stop` hook that takes longer than `BUZZ_AGENT_HOOK_TIMEOUT_MS` /// must be treated as no-objection (fail-open). Agent stops normally. /// /// Note on server-kill-on-timeout: `call_hooks` calls `kill_server` on a @@ -1298,7 +1298,7 @@ async fn hook_stop_timeout_failopen() { &[ ("MCP_HOOK_SERVERS", "fake"), // Hook delay (3s) >> hook timeout (200ms) → fail-open. - ("SPROUT_AGENT_HOOK_TIMEOUT_MS", "200"), + ("BUZZ_AGENT_HOOK_TIMEOUT_MS", "200"), ], ) .await; @@ -1344,20 +1344,20 @@ async fn hook_stop_timeout_failopen() { } /// When a session is cancelled while a tool call is in-flight, the agent -/// sends `notifications/cancelled` to the MCP server. With sprout-dev-mcp, +/// sends `notifications/cancelled` to the MCP server. With buzz-dev-mcp, /// this cancels the CancellationToken and kills the running shell process /// group. We verify: /// 1. The prompt completes in under 5s (not 60s). /// 2. The `sleep 60` process is actually dead after cancel. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn cancel_kills_inflight_tool_via_mcp_notification() { - // sprout-dev-mcp is a separate crate; locate its binary relative to - // the sprout-agent test binary (they share the same target dir). - let self_bin = std::path::PathBuf::from(env!("CARGO_BIN_EXE_sprout-agent")); - let dev_mcp_bin = self_bin.parent().unwrap().join("sprout-dev-mcp"); + // buzz-dev-mcp is a separate crate; locate its binary relative to + // the buzz-agent test binary (they share the same target dir). + let self_bin = std::path::PathBuf::from(env!("CARGO_BIN_EXE_buzz-agent")); + let dev_mcp_bin = self_bin.parent().unwrap().join("buzz-dev-mcp"); if !dev_mcp_bin.exists() { eprintln!( - "SKIP: sprout-dev-mcp not built at {}; run `cargo build -p sprout-dev-mcp` first", + "SKIP: buzz-dev-mcp not built at {}; run `cargo build -p buzz-dev-mcp` first", dev_mcp_bin.display() ); return; @@ -1366,7 +1366,7 @@ async fn cancel_kills_inflight_tool_via_mcp_notification() { // Use a unique marker (PID + timestamp) to avoid stale-file collisions. let marker = format!( - "sprout_cancel_test_{}_{:x}", + "buzz_cancel_test_{}_{:x}", std::process::id(), std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) @@ -1467,14 +1467,14 @@ async fn cancel_kills_inflight_tool_via_mcp_notification() { } /// Protocol-level test: verify that `notifications/cancelled` is sent to -/// any MCP server (not just sprout-dev-mcp) when a session is cancelled +/// any MCP server (not just buzz-dev-mcp) when a session is cancelled /// during an in-flight tool call. Uses fake_mcp with FAKE_MCP_CANCEL_LOG /// to capture the raw notification on stdin. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn cancel_sends_notifications_cancelled_to_any_mcp_server() { let cancel_log = std::env::temp_dir() .join(format!( - "sprout_cancel_proto_{}_{:x}.log", + "buzz_cancel_proto_{}_{:x}.log", std::process::id(), std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) diff --git a/crates/sprout-audit/Cargo.toml b/crates/buzz-audit/Cargo.toml similarity index 83% rename from crates/sprout-audit/Cargo.toml rename to crates/buzz-audit/Cargo.toml index 4300932ff..ff7bafb37 100644 --- a/crates/sprout-audit/Cargo.toml +++ b/crates/buzz-audit/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "sprout-audit" +name = "buzz-audit" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true repository.workspace = true -description = "Hash-chain audit log for Sprout" +description = "Hash-chain audit log for Buzz" [dependencies] -sprout-core = { workspace = true } +buzz-core = { workspace = true } sqlx = { workspace = true } tokio = { workspace = true } serde = { workspace = true } diff --git a/crates/sprout-audit/src/action.rs b/crates/buzz-audit/src/action.rs similarity index 100% rename from crates/sprout-audit/src/action.rs rename to crates/buzz-audit/src/action.rs diff --git a/crates/sprout-audit/src/entry.rs b/crates/buzz-audit/src/entry.rs similarity index 100% rename from crates/sprout-audit/src/entry.rs rename to crates/buzz-audit/src/entry.rs diff --git a/crates/sprout-audit/src/error.rs b/crates/buzz-audit/src/error.rs similarity index 100% rename from crates/sprout-audit/src/error.rs rename to crates/buzz-audit/src/error.rs diff --git a/crates/sprout-audit/src/hash.rs b/crates/buzz-audit/src/hash.rs similarity index 100% rename from crates/sprout-audit/src/hash.rs rename to crates/buzz-audit/src/hash.rs diff --git a/crates/sprout-audit/src/lib.rs b/crates/buzz-audit/src/lib.rs similarity index 100% rename from crates/sprout-audit/src/lib.rs rename to crates/buzz-audit/src/lib.rs diff --git a/crates/sprout-audit/src/schema.rs b/crates/buzz-audit/src/schema.rs similarity index 100% rename from crates/sprout-audit/src/schema.rs rename to crates/buzz-audit/src/schema.rs diff --git a/crates/sprout-audit/src/service.rs b/crates/buzz-audit/src/service.rs similarity index 98% rename from crates/sprout-audit/src/service.rs rename to crates/buzz-audit/src/service.rs index d2da43a83..0edc2e89b 100644 --- a/crates/sprout-audit/src/service.rs +++ b/crates/buzz-audit/src/service.rs @@ -3,7 +3,7 @@ use futures_util::FutureExt as _; use sqlx::{Acquire, PgPool, Row}; use tracing::{debug, instrument, warn}; -use sprout_core::kind::KIND_AUTH; +use buzz_core::kind::KIND_AUTH; use crate::{ action::AuditAction, @@ -13,7 +13,7 @@ use crate::{ schema::AUDIT_SCHEMA_SQL, }; -/// Advisory lock key derived from a stable hash of "sprout_audit". +/// Advisory lock key derived from a stable hash of "buzz_audit". const AUDIT_LOCK_KEY: i64 = 0x5370_7275_7441_7564; // "SprutAud" as hex /// Append-only audit log service backed by Postgres. @@ -266,7 +266,7 @@ mod tests { async fn test_pool() -> Option { let url = std::env::var("DATABASE_URL") - .unwrap_or_else(|_| "postgres://sprout:sprout_dev@localhost:5432/sprout".into()); + .unwrap_or_else(|_| "postgres://buzz:buzz_dev@localhost:5432/buzz".into()); PgPool::connect(&url).await.ok() } diff --git a/crates/sprout-auth/Cargo.toml b/crates/buzz-auth/Cargo.toml similarity index 83% rename from crates/sprout-auth/Cargo.toml rename to crates/buzz-auth/Cargo.toml index 2328b54d9..56b8943a6 100644 --- a/crates/sprout-auth/Cargo.toml +++ b/crates/buzz-auth/Cargo.toml @@ -1,18 +1,18 @@ [package] -name = "sprout-auth" +name = "buzz-auth" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true repository.workspace = true -description = "Authentication and authorization for Sprout" +description = "Authentication and authorization for Buzz" [features] test-utils = [] dev = [] [dependencies] -sprout-core = { workspace = true } +buzz-core = { workspace = true } nostr = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/sprout-auth/src/access.rs b/crates/buzz-auth/src/access.rs similarity index 95% rename from crates/sprout-auth/src/access.rs rename to crates/buzz-auth/src/access.rs index 727947a87..978d67ed8 100644 --- a/crates/sprout-auth/src/access.rs +++ b/crates/buzz-auth/src/access.rs @@ -1,7 +1,7 @@ //! Channel access enforcement. //! -//! Defines [`ChannelAccessChecker`] so `sprout-auth` can enforce access -//! without depending on `sprout-db` directly. +//! Defines [`ChannelAccessChecker`] so `buzz-auth` can enforce access +//! without depending on `buzz-db` directly. use std::collections::HashSet; use std::future::Future; @@ -14,9 +14,9 @@ use crate::scope::Scope; /// Async trait for checking channel membership. /// -/// Implemented by the database layer (`sprout-db`) in production. The `sprout-auth` +/// Implemented by the database layer (`buzz-db`) in production. The `buzz-auth` /// crate defines the trait so it can enforce access rules without a direct dependency -/// on `sprout-db`. +/// on `buzz-db`. pub trait ChannelAccessChecker: Send + Sync { /// Return the set of channel UUIDs accessible to `pubkey`. fn accessible_channel_ids( diff --git a/crates/sprout-auth/src/error.rs b/crates/buzz-auth/src/error.rs similarity index 98% rename from crates/sprout-auth/src/error.rs rename to crates/buzz-auth/src/error.rs index d060f6bc3..3ac71b58f 100644 --- a/crates/sprout-auth/src/error.rs +++ b/crates/buzz-auth/src/error.rs @@ -1,4 +1,4 @@ -//! Error types for sprout-auth. +//! Error types for buzz-auth. /// All errors that can occur during authentication and authorization. /// diff --git a/crates/sprout-auth/src/lib.rs b/crates/buzz-auth/src/lib.rs similarity index 97% rename from crates/sprout-auth/src/lib.rs rename to crates/buzz-auth/src/lib.rs index fb8cf534a..fcb39010c 100644 --- a/crates/sprout-auth/src/lib.rs +++ b/crates/buzz-auth/src/lib.rs @@ -1,6 +1,6 @@ #![deny(unsafe_code)] #![warn(missing_docs)] -//! `sprout-auth` — Authentication and authorization for the Sprout relay. +//! `buzz-auth` — Authentication and authorization for the Buzz relay. //! //! ## Auth paths //! @@ -137,7 +137,7 @@ impl AuthService { /// Derive a deterministic Nostr pubkey from a username string. /// -/// Uses `SHA-256("sprout-test-key:{username}")` as the secret key material. +/// Uses `SHA-256("buzz-test-key:{username}")` as the secret key material. /// This matches the derivation used by the desktop's `set_test_identity` function, /// allowing the relay to resolve usernames to Nostr pubkeys in dev mode. /// @@ -151,7 +151,7 @@ impl AuthService { #[cfg(any(test, feature = "dev"))] pub fn derive_pubkey_from_username(username: &str) -> Result { use sha2::{Digest, Sha256}; - let seed = format!("sprout-test-key:{username}"); + let seed = format!("buzz-test-key:{username}"); let hash: [u8; 32] = Sha256::digest(seed.as_bytes()).into(); let secret_key = nostr::SecretKey::from_slice(&hash) .map_err(|e| AuthError::Internal(format!("key derivation failed: {e}")))?; diff --git a/crates/sprout-auth/src/nip42.rs b/crates/buzz-auth/src/nip42.rs similarity index 98% rename from crates/sprout-auth/src/nip42.rs rename to crates/buzz-auth/src/nip42.rs index a97150401..8ee7c9089 100644 --- a/crates/sprout-auth/src/nip42.rs +++ b/crates/buzz-auth/src/nip42.rs @@ -53,7 +53,7 @@ pub fn verify_nip42_event( return Err(AuthError::InvalidSignature); } - sprout_core::verify_event(event).map_err(|_| AuthError::InvalidSignature)?; + buzz_core::verify_event(event).map_err(|_| AuthError::InvalidSignature)?; let challenge = event .tags diff --git a/crates/sprout-auth/src/nip98.rs b/crates/buzz-auth/src/nip98.rs similarity index 99% rename from crates/sprout-auth/src/nip98.rs rename to crates/buzz-auth/src/nip98.rs index 5aefc4da0..277cbe27c 100644 --- a/crates/sprout-auth/src/nip98.rs +++ b/crates/buzz-auth/src/nip98.rs @@ -14,7 +14,7 @@ //! //! 1. Parse JSON into a `nostr::Event` //! 2. Verify `kind == 27235` (`Kind::HttpAuth`) -//! 3. Verify Schnorr signature via `sprout_core::verify_event` +//! 3. Verify Schnorr signature via `buzz_core::verify_event` //! 4. Verify `created_at` within ±60 seconds of server time //! 5. Verify `["u", ]` tag matches `expected_url` (normalised: case-insensitive //! scheme/host, trailing slash stripped) @@ -71,7 +71,7 @@ pub fn verify_nip98_event( } // 3. Verify Schnorr signature (also verifies event ID hash). - sprout_core::verify_event(&event) + buzz_core::verify_event(&event) .map_err(|_| AuthError::Nip98Invalid("invalid Schnorr signature".to_string()))?; // 4. Verify created_at within ±60 seconds of now. diff --git a/crates/sprout-auth/src/rate_limit.rs b/crates/buzz-auth/src/rate_limit.rs similarity index 92% rename from crates/sprout-auth/src/rate_limit.rs rename to crates/buzz-auth/src/rate_limit.rs index 321ea541a..a77da4fa7 100644 --- a/crates/sprout-auth/src/rate_limit.rs +++ b/crates/buzz-auth/src/rate_limit.rs @@ -1,7 +1,7 @@ //! Rate limiting types and interface. //! //! Defines the [`RateLimiter`] trait. The Redis-backed implementation lives in -//! `sprout-relay` / `sprout-pubsub`. Fixed-window counter algorithm. +//! `buzz-relay` / `buzz-pubsub`. Fixed-window counter algorithm. //! //! ⚠️ Fixed windows allow up to 2× burst at boundaries. Upgrade to sliding //! window or token bucket for strict limiting. @@ -144,7 +144,7 @@ impl Default for RateLimitConfig { /// Async rate-limiting interface. /// -/// The Redis-backed production implementation lives in `sprout-relay` / `sprout-pubsub`. +/// The Redis-backed production implementation lives in `buzz-relay` / `buzz-pubsub`. /// A no-op `AlwaysAllowRateLimiter` is provided for unit tests. /// /// ⚠️ The fixed-window algorithm used by the Redis implementation allows up to 2× @@ -171,18 +171,18 @@ pub trait RateLimiter: Send + Sync { ) -> impl std::future::Future> + Send; } -/// Redis key for pubkey-based rate limit: `sprout:ratelimit::` +/// Redis key for pubkey-based rate limit: `buzz:ratelimit::` pub fn rate_limit_key(pubkey: &PublicKey, limit_type: &LimitType) -> String { format!( - "sprout:ratelimit:{}:{}", + "buzz:ratelimit:{}:{}", pubkey.to_hex(), limit_type.key_suffix() ) } -/// Redis key for IP-based rate limit: `sprout:ratelimit:ip::conn` +/// Redis key for IP-based rate limit: `buzz:ratelimit:ip::conn` pub fn ip_rate_limit_key(ip: &IpAddr) -> String { - format!("sprout:ratelimit:ip:{}:conn", ip) + format!("buzz:ratelimit:ip:{}:conn", ip) } /// Always-allow rate limiter for unit tests. @@ -221,17 +221,14 @@ mod tests { fn rate_limit_key_format() { let keys = Keys::generate(); let key = rate_limit_key(&keys.public_key(), &LimitType::Messages); - assert!(key.starts_with("sprout:ratelimit:")); + assert!(key.starts_with("buzz:ratelimit:")); assert!(key.ends_with(":msg")); } #[test] fn ip_rate_limit_key_format() { let ip = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)); - assert_eq!( - ip_rate_limit_key(&ip), - "sprout:ratelimit:ip:192.168.1.1:conn" - ); + assert_eq!(ip_rate_limit_key(&ip), "buzz:ratelimit:ip:192.168.1.1:conn"); } #[tokio::test] diff --git a/crates/sprout-auth/src/scope.rs b/crates/buzz-auth/src/scope.rs similarity index 100% rename from crates/sprout-auth/src/scope.rs rename to crates/buzz-auth/src/scope.rs diff --git a/crates/sprout-cli/Cargo.toml b/crates/buzz-cli/Cargo.toml similarity index 77% rename from crates/sprout-cli/Cargo.toml rename to crates/buzz-cli/Cargo.toml index cf5751452..3fa874ebd 100644 --- a/crates/sprout-cli/Cargo.toml +++ b/crates/buzz-cli/Cargo.toml @@ -1,22 +1,22 @@ [package] -name = "sprout-cli" +name = "buzz-cli" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true repository.workspace = true -description = "Agent-first CLI for Sprout relay" +description = "Agent-first CLI for Buzz relay" [lib] -name = "sprout_cli" +name = "buzz_cli" path = "src/lib.rs" [[bin]] -name = "sprout" +name = "buzz" path = "src/main.rs" [dependencies] -# CLI argument parsing — derive macros + env var support (SPROUT_API_TOKEN auto-wired) +# 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 @@ -32,15 +32,15 @@ serde_json = { workspace = true } # Structured error types with exit code mapping thiserror = { workspace = true } -# Nostr event signing — used in `sprout auth`, auto-mint, and signed event writes +# 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 } # Typed event builders for all write operations -sprout-sdk = { workspace = true } -sprout-core = { workspace = true } +buzz-sdk = { workspace = true } +buzz-core = { workspace = true } # Base64 encoding — NIP-98 event serialization for Authorization header base64 = "0.22" @@ -61,7 +61,7 @@ infer = "0.19" url = { workspace = true } # Persona pack parsing, validation, and resolution -sprout-persona = { path = "../sprout-persona" } +buzz-persona = { path = "../buzz-persona" } # WebSocket client — ephemeral event publish (kind:20001 is WS-only on the relay) -sprout-ws-client = { path = "../sprout-ws-client" } +buzz-ws-client = { path = "../buzz-ws-client" } diff --git a/crates/sprout-cli/README.md b/crates/buzz-cli/README.md similarity index 62% rename from crates/sprout-cli/README.md rename to crates/buzz-cli/README.md index 8e10accec..14e570a38 100644 --- a/crates/sprout-cli/README.md +++ b/crates/buzz-cli/README.md @@ -1,23 +1,23 @@ -# Sprout CLI +# Buzz CLI -Agent-first command-line interface for Sprout relay. JSON in, JSON out. +Agent-first command-line interface for Buzz relay. JSON in, JSON out. ## Install ```bash -cargo install --path crates/sprout-cli +cargo install --path crates/buzz-cli ``` ## Authentication | Env Var | Mode | Use Case | |---------|------|----------| -| `SPROUT_PRIVATE_KEY` | NIP-98 Schnorr signature | Agents with a keypair | +| `BUZZ_PRIVATE_KEY` | NIP-98 Schnorr signature | Agents with a keypair | ```bash # Private key identity (NIP-98 signed requests) -export SPROUT_PRIVATE_KEY="nsec1..." -sprout channels list +export BUZZ_PRIVATE_KEY="nsec1..." +buzz channels list ``` ## Usage @@ -26,56 +26,56 @@ All output is JSON on stdout. Errors are JSON on stderr. Exit codes: 0=ok, 1=use ```bash # Set relay URL (defaults to http://localhost:3000) -export SPROUT_RELAY_URL="https://relay.example.com" +export BUZZ_RELAY_URL="https://relay.example.com" # Messages -sprout messages send --channel --content "Hello" -sprout messages send --channel --content "Reply" --reply-to --broadcast -sprout messages send --channel --content - < message.md # read body from stdin -sprout messages get --channel --limit 20 -sprout messages thread --channel --event -sprout messages search --query "architecture" -sprout messages edit --event --content "Updated text" -sprout messages delete --event +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 get --channel --limit 20 +buzz messages thread --channel --event +buzz messages search --query "architecture" +buzz messages edit --event --content "Updated text" +buzz messages delete --event # Diffs -sprout messages send-diff --channel --diff - --repo https://github.com/org/repo --commit abc123 < diff.patch +buzz messages send-diff --channel --diff - --repo https://github.com/org/repo --commit abc123 < diff.patch # Channels -sprout channels list -sprout channels create --name "my-channel" --type stream --visibility open -sprout channels join --channel -sprout channels topic --channel --topic "New topic" +buzz channels list +buzz channels create --name "my-channel" --type stream --visibility open +buzz channels join --channel +buzz channels topic --channel --topic "New topic" # Reactions -sprout reactions add --event --emoji "👍" -sprout reactions get --event +buzz reactions add --event --emoji "👍" +buzz reactions get --event # Users & Presence -sprout users get # your own profile -sprout users get --pubkey # single user -sprout users get --pubkey --pubkey # batch (max 200) -sprout users set-presence --status online +buzz users get # your own profile +buzz users get --pubkey # single user +buzz users get --pubkey --pubkey # batch (max 200) +buzz users set-presence --status online # DMs -sprout dms open --pubkey -sprout dms list +buzz dms open --pubkey +buzz dms list # Workflows -sprout workflows list --channel -sprout workflows trigger --workflow -sprout workflows approve --token -sprout workflows approve --token --approved false --note "needs revision" +buzz workflows list --channel +buzz workflows trigger --workflow +buzz workflows approve --token +buzz workflows approve --token --approved false --note "needs revision" # Forum -sprout messages vote --event --direction up +buzz messages vote --event --direction up # Canvas -sprout canvas get --channel -sprout canvas set --channel --content "# Welcome" +buzz canvas get --channel +buzz canvas set --channel --content "# Welcome" # Pipe to jq -sprout channels list | jq '.[].name' +buzz channels list | jq '.[].name' ``` ## 54 Subcommands across 12 Groups @@ -140,9 +140,9 @@ sprout channels list | jq '.[].name' ## Architecture ``` -sprout [flags] +buzz [flags] │ - ├─ main.rs ──▶ commands/*.rs ──▶ client.rs ──▶ Sprout Relay REST API + ├─ main.rs ──▶ commands/*.rs ──▶ client.rs ──▶ Buzz Relay REST API │ (clap) (handlers) (reqwest) │ ├─ validate.rs (UUID, hex, content size, percent-encode) diff --git a/crates/sprout-cli/TESTING.md b/crates/buzz-cli/TESTING.md similarity index 68% rename from crates/sprout-cli/TESTING.md rename to crates/buzz-cli/TESTING.md index c08367f93..c18b4673c 100644 --- a/crates/sprout-cli/TESTING.md +++ b/crates/buzz-cli/TESTING.md @@ -1,4 +1,4 @@ -# sprout-cli Live Testing Guide +# buzz-cli Live Testing Guide Manual testing runbook for verifying every CLI command against a local relay. An agent or developer follows this step by step, running each command and @@ -12,9 +12,9 @@ Docker services running and healthy: ```bash docker compose ps -# sprout-postgres healthy -# sprout-redis healthy -# sprout-typesense healthy +# buzz-postgres healthy +# buzz-redis healthy +# buzz-typesense healthy ``` If not running: `just setup` from the repo root. @@ -26,10 +26,10 @@ Tools: `jq`, `curl`, Rust toolchain. ## 2. Build the CLI ```bash -cargo build -p sprout-cli +cargo build -p buzz-cli ``` -Use `cargo run -p sprout-cli --` or the built binary at `target/debug/sprout`. +Use `cargo run -p buzz-cli --` or the built binary at `target/debug/buzz`. --- @@ -38,9 +38,9 @@ Use `cargo run -p sprout-cli --` or the built binary at `target/debug/sprout`. In a separate terminal: ```bash -cd REPOS/sprout-nostr +cd REPOS/buzz-nostr set -a && source .env && set +a -cargo run -p sprout-relay +cargo run -p buzz-relay ``` Verify: @@ -50,33 +50,33 @@ curl -s http://localhost:3000/_liveness # "ok" or 200 status ``` -The `.env` should have `SPROUT_REQUIRE_AUTH_TOKEN=false` for local dev. +The `.env` should have `BUZZ_REQUIRE_AUTH_TOKEN=false` for local dev. --- ## 4. Mint Test Credentials -### Option A: sprout-admin (full scopes including admin) +### Option A: buzz-admin (full scopes including admin) This mints a token with all CLI-relevant scopes (including `admin:channels`) via direct DB access. Use this for testing admin operations (archive, delete-channel, add/remove-channel-member). ```bash -DATABASE_URL=postgres://sprout:sprout_dev@localhost:5432/sprout \ -cargo run -p sprout-admin -- mint-token \ +DATABASE_URL=postgres://buzz:buzz_dev@localhost:5432/buzz \ +cargo run -p buzz-admin -- mint-token \ --name "cli-test" \ --scopes "messages:read,messages:write,channels:read,channels:write,users:read,users:write,files:read,files:write,admin:channels" ``` This generates a keypair and prints: -- **Private key (nsec)** — save for `SPROUT_PRIVATE_KEY` testing +- **Private key (nsec)** — save for `BUZZ_PRIVATE_KEY` testing Export: ```bash -export SPROUT_RELAY_URL="http://localhost:3000" -export SPROUT_PRIVATE_KEY="nsec1..." # from the mint output +export BUZZ_RELAY_URL="http://localhost:3000" +export BUZZ_PRIVATE_KEY="nsec1..." # from the mint output ``` ### Scope reference @@ -98,10 +98,10 @@ export SPROUT_PRIVATE_KEY="nsec1..." # from the mint output ## 5. Unit Tests ```bash -cargo test -p sprout-cli -# Expected: see cargo test -p sprout-cli for current count +cargo test -p buzz-cli +# Expected: see cargo test -p buzz-cli for current count -cargo clippy -p sprout-cli -- -D warnings +cargo clippy -p buzz-cli -- -D warnings # Expected: zero warnings ``` @@ -117,59 +117,59 @@ earlier ones create resources that later ones need. ```bash # channels create (stream) -sprout channels create --name "test-stream" --type stream --visibility open \ +buzz channels create --name "test-stream" --type stream --visibility open \ --description "CLI test channel" | jq . # Save the channel ID: -CHANNEL_ID=$(sprout channels create --name "test-cli" --type stream --visibility open | jq -r '.channel_id') +CHANNEL_ID=$(buzz channels create --name "test-cli" --type stream --visibility open | jq -r '.channel_id') # Expected: {"event_id":"...","accepted":true,"message":"...","channel_id":""} # channels create (forum) — needed for messages vote later -FORUM_ID=$(sprout channels create --name "test-forum" --type forum --visibility open | jq -r '.channel_id') +FORUM_ID=$(buzz channels create --name "test-forum" --type forum --visibility open | jq -r '.channel_id') # channels list -sprout channels list | jq . +buzz channels list | jq . # Expected: [{"channel_id":"...","name":"...","description":"...","created_at":N}] -sprout channels list --visibility open | jq . -sprout channels list --member | jq . +buzz channels list --visibility open | jq . +buzz channels list --member | jq . # channels get -sprout channels get --channel "$CHANNEL_ID" | jq . +buzz channels get --channel "$CHANNEL_ID" | jq . # Expected: {"channel_id":"...","name":"...","description":"...","created_at":N,"pubkey":"..."} or null # channels update -sprout channels update --channel "$CHANNEL_ID" --name "test-cli-updated" \ +buzz channels update --channel "$CHANNEL_ID" --name "test-cli-updated" \ --description "Updated" | jq . # Expected: {"event_id":"...","accepted":true,"message":"..."} # channels topic -sprout channels topic --channel "$CHANNEL_ID" --topic "Test topic" | jq . +buzz channels topic --channel "$CHANNEL_ID" --topic "Test topic" | jq . # Expected: {"event_id":"...","accepted":true,"message":"..."} # channels purpose -sprout channels purpose --channel "$CHANNEL_ID" --purpose "Testing" | jq . +buzz channels purpose --channel "$CHANNEL_ID" --purpose "Testing" | jq . # Expected: {"event_id":"...","accepted":true,"message":"..."} # channels join (may already be a member from create) -sprout channels join --channel "$CHANNEL_ID" | jq . +buzz channels join --channel "$CHANNEL_ID" | jq . # Expected: {"event_id":"...","accepted":true,"message":"..."} # channels leave # NOTE: Fails with 400 "cannot remove the last owner" if this identity is the # sole owner (which it is after channels create). To test leave successfully, # first add-member a second pubkey as owner. The relay enforces ≥1 owner. -sprout channels leave --channel "$CHANNEL_ID" | jq . +buzz channels leave --channel "$CHANNEL_ID" | jq . # Expected: {"event_id":"...","accepted":true,"message":"..."} (or 400 if last owner) # Re-join so we can send messages -sprout channels join --channel "$CHANNEL_ID" | jq . +buzz channels join --channel "$CHANNEL_ID" | jq . # Expected: {"event_id":"...","accepted":true,"message":"..."} # channels archive (requires admin:channels scope) -sprout channels archive --channel "$CHANNEL_ID" | jq . +buzz channels archive --channel "$CHANNEL_ID" | jq . # Expected: {"event_id":"...","accepted":true,"message":"..."} # channels unarchive -sprout channels unarchive --channel "$CHANNEL_ID" | jq . +buzz channels unarchive --channel "$CHANNEL_ID" | jq . # Expected: {"event_id":"...","accepted":true,"message":"..."} ``` @@ -177,13 +177,13 @@ sprout channels unarchive --channel "$CHANNEL_ID" | jq . ```bash # canvas set -sprout canvas set --channel "$CHANNEL_ID" --content "# Test Canvas" | jq . +buzz canvas set --channel "$CHANNEL_ID" --content "# Test Canvas" | jq . # canvas set from stdin -echo "# Canvas from stdin" | sprout canvas set --channel "$CHANNEL_ID" --content - | jq . +echo "# Canvas from stdin" | buzz canvas set --channel "$CHANNEL_ID" --content - | jq . # canvas get -sprout canvas get --channel "$CHANNEL_ID" +buzz canvas get --channel "$CHANNEL_ID" # Expected: raw markdown string, or: null ``` @@ -191,44 +191,44 @@ sprout canvas get --channel "$CHANNEL_ID" ```bash # messages send -MSG=$(sprout messages send --channel "$CHANNEL_ID" --content "Hello from CLI test" | jq .) +MSG=$(buzz messages send --channel "$CHANNEL_ID" --content "Hello from CLI test" | jq .) echo "$MSG" EVENT_ID=$(echo "$MSG" | jq -r '.event_id') # messages send with reply + broadcast -REPLY=$(sprout messages send --channel "$CHANNEL_ID" --content "Reply" \ +REPLY=$(buzz messages send --channel "$CHANNEL_ID" --content "Reply" \ --reply-to "$EVENT_ID" --broadcast | jq .) echo "$REPLY" REPLY_ID=$(echo "$REPLY" | jq -r '.event_id') # messages send with mentions — @name in content is auto-resolved, no flag needed -sprout messages send --channel "$CHANNEL_ID" --content "Hey @someone" | jq . +buzz messages send --channel "$CHANNEL_ID" --content "Hey @someone" | jq . # messages send with NIP-27 nostr:npub1… inline mention — auto-resolved to p-tag -sprout messages send --channel "$CHANNEL_ID" \ +buzz messages send --channel "$CHANNEL_ID" \ --content "Check with nostr:npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg on this" | jq . # messages send from stdin — safe path for content with shell metacharacters # (backticks, $vars, code blocks) that would otherwise be expanded by the shell. echo 'Body with `backticks` and $vars stays literal.' \ - | sprout messages send --channel "$CHANNEL_ID" --content - | jq . + | buzz messages send --channel "$CHANNEL_ID" --content - | jq . # messages get -sprout messages get --channel "$CHANNEL_ID" | jq . -sprout messages get --channel "$CHANNEL_ID" --limit 5 | jq . +buzz messages get --channel "$CHANNEL_ID" | jq . +buzz messages get --channel "$CHANNEL_ID" --limit 5 | jq . # messages thread -sprout messages thread --channel "$CHANNEL_ID" --event "$EVENT_ID" | jq . +buzz messages thread --channel "$CHANNEL_ID" --event "$EVENT_ID" | jq . # messages search -sprout messages search --query "Hello" | jq . -sprout messages search --query "CLI test" --limit 5 | jq . +buzz messages search --query "Hello" | jq . +buzz messages search --query "CLI test" --limit 5 | jq . # messages edit -sprout messages edit --event "$EVENT_ID" --content "Edited by CLI test" | jq . +buzz messages edit --event "$EVENT_ID" --content "Edited by CLI test" | jq . # messages delete -sprout messages delete --event "$REPLY_ID" | jq . +buzz messages delete --event "$REPLY_ID" | jq . ``` ### 6.4 Diff Messages @@ -239,14 +239,14 @@ echo '--- a/foo.rs +++ b/foo.rs @@ -1,3 +1,3 @@ -fn old() {} -+fn new() {}' | sprout messages send-diff \ ++fn new() {}' | buzz messages send-diff \ --channel "$CHANNEL_ID" \ --diff - \ --repo "https://github.com/example/repo" \ --commit "abcdef1234567890abcdef1234567890abcdef12" | jq . # messages send-diff with metadata -echo "diff content" | sprout messages send-diff \ +echo "diff content" | buzz messages send-diff \ --channel "$CHANNEL_ID" \ --diff - \ --repo "https://github.com/example/repo" \ @@ -256,7 +256,7 @@ echo "diff content" | sprout messages send-diff \ --description "Refactored main" | jq . # messages send-diff with branch + PR metadata -echo "diff content" | sprout messages send-diff \ +echo "diff content" | buzz messages send-diff \ --channel "$CHANNEL_ID" \ --diff - \ --repo "https://github.com/example/repo" \ @@ -271,40 +271,40 @@ echo "diff content" | sprout messages send-diff \ ```bash # Send a message to react to -REACT_MSG=$(sprout messages send --channel "$CHANNEL_ID" --content "React to this") +REACT_MSG=$(buzz messages send --channel "$CHANNEL_ID" --content "React to this") REACT_ID=$(echo "$REACT_MSG" | jq -r '.event_id') # reactions add -sprout reactions add --event "$REACT_ID" --emoji "👍" | jq . +buzz reactions add --event "$REACT_ID" --emoji "👍" | jq . # reactions get -sprout reactions get --event "$REACT_ID" | jq . +buzz reactions get --event "$REACT_ID" | jq . # Expected: {"reactions":[{"emoji":"...","count":N,"pubkeys":["..."]}]} # reactions remove -sprout reactions remove --event "$REACT_ID" --emoji "👍" | jq . +buzz reactions remove --event "$REACT_ID" --emoji "👍" | jq . ``` ### 6.6 DMs ```bash # dms list -sprout dms list | jq . +buzz dms list | jq . # Expected: [{"dm_id":"...","participants":["..."],"created_at":N}] # dms open (needs a real pubkey — use your own or a test one) # Get your own pubkey first: -MY_PUBKEY=$(sprout users get | jq -r '.[0].pubkey // empty') +MY_PUBKEY=$(buzz users get | jq -r '.[0].pubkey // empty') echo "My pubkey: $MY_PUBKEY" # dms open with a synthetic pubkey (relay will create the user) -DM_RESULT=$(sprout dms open --pubkey "0000000000000000000000000000000000000000000000000000000000000001") +DM_RESULT=$(buzz dms open --pubkey "0000000000000000000000000000000000000000000000000000000000000001") echo "$DM_RESULT" | jq . # Expected: {"event_id":"...","accepted":true,"message":"...","dm_id":""} DM_ID=$(echo "$DM_RESULT" | jq -r '.dm_id') # dms add-member (requires messages:write scope — NOT admin:channels) -sprout dms add-member --channel "$DM_ID" \ +buzz dms add-member --channel "$DM_ID" \ --pubkey "0000000000000000000000000000000000000000000000000000000000000002" | jq . ``` @@ -312,25 +312,25 @@ sprout dms add-member --channel "$DM_ID" \ ```bash # users get — own profile (0 pubkeys) -sprout users get | jq . +buzz users get | jq . # Expected: [{...profile...}] — always returns an array, even for single results # users get — single pubkey -sprout users get --pubkey "$MY_PUBKEY" | jq . +buzz users get --pubkey "$MY_PUBKEY" | jq . # users get — batch (2+ pubkeys) -sprout users get --pubkey "$MY_PUBKEY" --pubkey "$MY_PUBKEY" | jq . +buzz users get --pubkey "$MY_PUBKEY" --pubkey "$MY_PUBKEY" | jq . # users set-profile -sprout users set-profile --name "CLI Test Agent" --about "Testing sprout-cli" | jq . +buzz users set-profile --name "CLI Test Agent" --about "Testing buzz-cli" | jq . # users presence -sprout users presence --pubkeys "$MY_PUBKEY" | jq . +buzz users presence --pubkeys "$MY_PUBKEY" | jq . # users set-presence -sprout users set-presence --status online | jq . -sprout users set-presence --status away | jq . -sprout users set-presence --status offline | jq . +buzz users set-presence --status online | jq . +buzz users set-presence --status away | jq . +buzz users set-presence --status offline | jq . # Note: set-presence may fail — kind:20001 is ephemeral and rejected by the HTTP bridge ``` @@ -338,16 +338,16 @@ sprout users set-presence --status offline | jq . ```bash # channels add-member -sprout channels add-member --channel "$CHANNEL_ID" \ +buzz channels add-member --channel "$CHANNEL_ID" \ --pubkey "0000000000000000000000000000000000000000000000000000000000000001" \ --role member | jq . # channels members -sprout channels members --channel "$CHANNEL_ID" | jq . +buzz channels members --channel "$CHANNEL_ID" | jq . # Expected: [{"pubkey":"...","role":"..."}] # channels remove-member -sprout channels remove-member --channel "$CHANNEL_ID" \ +buzz channels remove-member --channel "$CHANNEL_ID" \ --pubkey "0000000000000000000000000000000000000000000000000000000000000001" | jq . ``` @@ -358,7 +358,7 @@ sprout channels remove-member --channel "$CHANNEL_ID" \ # NOTE: trigger uses `on:` tag (serde internally tagged enum). # Valid triggers: message_posted, reaction_added, diff_posted, schedule, webhook # Steps use `action:` tag: send_message, send_dm, set_channel_topic, add_reaction, etc. -WF=$(sprout workflows create --channel "$CHANNEL_ID" \ +WF=$(buzz workflows create --channel "$CHANNEL_ID" \ --yaml 'name: test-wf trigger: on: webhook @@ -370,14 +370,14 @@ echo "$WF" WF_ID=$(echo "$WF" | jq -r '.workflow_id') # workflows list -sprout workflows list --channel "$CHANNEL_ID" | jq . +buzz workflows list --channel "$CHANNEL_ID" | jq . # workflows get -sprout workflows get --workflow "$WF_ID" | jq . +buzz workflows get --workflow "$WF_ID" | jq . # Expected: {"workflow_id":"...","content":"","created_at":N,"pubkey":"..."} or null # workflows update (requires --channel) -sprout workflows update --channel "$CHANNEL_ID" --workflow "$WF_ID" \ +buzz workflows update --channel "$CHANNEL_ID" --workflow "$WF_ID" \ --yaml 'name: test-wf-updated trigger: on: webhook @@ -390,28 +390,28 @@ steps: # NOTE: May return 400 "workflow not found" — the relay indexes workflow # definitions into a DB table asynchronously. If the definition event hasn't # been indexed yet, the trigger handler won't find it. -sprout workflows trigger --workflow "$WF_ID" | jq . +buzz workflows trigger --workflow "$WF_ID" | jq . # workflows runs -sprout workflows runs --workflow "$WF_ID" | jq . +buzz workflows runs --workflow "$WF_ID" | jq . # Expected: [] — relay stores runs in DB, not as Nostr events; empty is normal # workflows approve — requires a workflow run waiting for approval # This is hard to test ad-hoc without a workflow that has an approval gate. # Test the validation instead: -sprout workflows approve --token "00000000-0000-0000-0000-000000000000" 2>&1 || true +buzz workflows approve --token "00000000-0000-0000-0000-000000000000" 2>&1 || true # Should fail with relay error (token not found), not a validation error -# To test the deny path: sprout workflows approve --token --approved false +# To test the deny path: buzz workflows approve --token --approved false # workflows delete -sprout workflows delete --workflow "$WF_ID" | jq . +buzz workflows delete --workflow "$WF_ID" | jq . ``` ### 6.10 Feed ```bash -sprout feed get | jq . -sprout feed get --limit 5 | jq . +buzz feed get | jq . +buzz feed get --limit 5 | jq . # Expected: [{id,pubkey,kind,content,created_at,tags}] — sig-stripped, sorted newest-first ``` @@ -419,16 +419,16 @@ sprout feed get --limit 5 | jq . ```bash # Send a forum post (kind 45001) to the forum channel -FORUM_POST=$(sprout messages send --channel "$FORUM_ID" \ +FORUM_POST=$(buzz messages send --channel "$FORUM_ID" \ --content "Forum post for vote testing" --kind 45001 | jq .) echo "$FORUM_POST" FORUM_EVENT_ID=$(echo "$FORUM_POST" | jq -r '.event_id') # messages vote (up) -sprout messages vote --event "$FORUM_EVENT_ID" --direction up | jq . +buzz messages vote --event "$FORUM_EVENT_ID" --direction up | jq . # messages vote (down) -sprout messages vote --event "$FORUM_EVENT_ID" --direction down | jq . +buzz messages vote --event "$FORUM_EVENT_ID" --direction down | jq . ``` ### 6.12 Notes (NIP-23 long-form, kind:30023) @@ -439,34 +439,34 @@ not JSON — except `get`/`ls`, which emit JSON. ```bash # set (first publish — --title required, body from stdin) -cat <<'EOF' | sprout notes set --name dco-check --title "DCO Check" \ +cat <<'EOF' | buzz notes set --name dco-check --title "DCO Check" \ --summary "How we verify DCO" --tag dco --tag ci --content - Run `git log --format='%(trailers:key=Signed-off-by)'` ... EOF # → prints event_id / naddr / coordinate / slug / title # set (edit — omit --title to carry it forward; published_at preserved) -echo "Updated body." | sprout notes set --name dco-check --content - +echo "Updated body." | buzz notes set --name dco-check --content - # get by name (own author resolves directly; cross-author #d query otherwise) -sprout notes get --name dco-check | jq . -sprout notes get --name dco-check --content-only +buzz notes get --name dco-check | jq . +buzz notes get --name dco-check --content-only # get by naddr (exact coordinate; paste the naddr from a set/get above) -sprout notes get --naddr "$NADDR" | jq . +buzz notes get --naddr "$NADDR" | jq . # ls (own by default; --author all across the team; --tag filters) -sprout notes ls | jq . -sprout notes ls --tag dco | jq . -sprout notes ls --author all --limit 10 | jq . +buzz notes ls | jq . +buzz notes ls --tag dco | jq . +buzz notes ls --author all --limit 10 | jq . # rm (NIP-09 a-tag deletion; subsequent get must 404) -sprout notes rm --name dco-check +buzz notes rm --name dco-check # → prints deleted / deletion -sprout notes get --name dco-check # exits non-zero: not found +buzz notes get --name dco-check # exits non-zero: not found # rm of a slug you never published → NotFound, no kind:5 emitted -sprout notes rm --name does-not-exist # exits non-zero +buzz notes rm --name does-not-exist # exits non-zero ``` --- @@ -477,37 +477,37 @@ Verify the CLI produces correct JSON on stderr and correct exit codes. ```bash # Exit 1: Invalid UUID -sprout channels get --channel "not-a-uuid" 2>&1; echo "exit: $?" +buzz channels get --channel "not-a-uuid" 2>&1; echo "exit: $?" # stderr: {"error":"user_error","message":"invalid UUID: not-a-uuid"} # exit: 1 # Exit 1: Invalid hex64 -sprout messages delete --event "not-hex" 2>&1; echo "exit: $?" +buzz messages delete --event "not-hex" 2>&1; echo "exit: $?" # stderr: {"error":"user_error","message":"must be a 64-character hex string: not-hex"} # exit: 1 # Exit 1: Invalid --type value (clap validates the enum — multi-line error) -sprout channels create --name x --type invalid --visibility open 2>&1; echo "exit: $?" +buzz channels create --name x --type invalid --visibility open 2>&1; echo "exit: $?" # stderr: {"error":"user_error","message":"error: invalid value 'invalid' for '--type '\n [possible values: stream, forum]\n..."} # exit: 1 # Exit 1: Invalid --direction value -sprout messages vote --event "$(printf '0%.0s' {1..64})" \ +buzz messages vote --event "$(printf '0%.0s' {1..64})" \ --direction sideways 2>&1; echo "exit: $?" # exit: 1 # Exit 1: Empty body guard -sprout users set-profile 2>&1; echo "exit: $?" +buzz users set-profile 2>&1; echo "exit: $?" # exit: 1 (at least one field required) # Exit 3: No auth configured -env -u SPROUT_PRIVATE_KEY \ - cargo run -p sprout-cli -- channels list 2>&1; echo "exit: $?" -# stderr: {"error":"auth_error","message":"auth error: SPROUT_PRIVATE_KEY is required (use --private-key or set env var)"} +env -u BUZZ_PRIVATE_KEY \ + cargo run -p buzz-cli -- channels list 2>&1; echo "exit: $?" +# stderr: {"error":"auth_error","message":"auth error: BUZZ_PRIVATE_KEY is required (use --private-key or set env var)"} # exit: 3 # Not-found returns null, not an error (exit 0) -sprout channels get --channel "00000000-0000-0000-0000-000000000000" +buzz channels get --channel "00000000-0000-0000-0000-000000000000" # stdout: null # exit: 0 ``` @@ -519,14 +519,14 @@ sprout channels get --channel "00000000-0000-0000-0000-000000000000" Test authentication. ```bash -# Private key (SPROUT_PRIVATE_KEY) -SPROUT_PRIVATE_KEY="nsec1..." sprout channels list | jq . +# Private key (BUZZ_PRIVATE_KEY) +BUZZ_PRIVATE_KEY="nsec1..." buzz channels list | jq . # Should succeed # No auth → exit 3 -env -u SPROUT_PRIVATE_KEY \ - cargo run -p sprout-cli -- channels list 2>&1; echo "exit: $?" -# stderr: {"error":"auth_error","message":"auth error: SPROUT_PRIVATE_KEY is required (use --private-key or set env var)"} +env -u BUZZ_PRIVATE_KEY \ + cargo run -p buzz-cli -- channels list 2>&1; echo "exit: $?" +# stderr: {"error":"auth_error","message":"auth error: BUZZ_PRIVATE_KEY is required (use --private-key or set env var)"} # exit: 3 ``` @@ -536,8 +536,8 @@ env -u SPROUT_PRIVATE_KEY \ ```bash # Delete test channels -sprout channels delete --channel "$CHANNEL_ID" | jq . -sprout channels delete --channel "$FORUM_ID" | jq . +buzz channels delete --channel "$CHANNEL_ID" | jq . +buzz channels delete --channel "$FORUM_ID" | jq . ``` --- diff --git a/crates/sprout-cli/src/client.rs b/crates/buzz-cli/src/client.rs similarity index 97% rename from crates/sprout-cli/src/client.rs rename to crates/buzz-cli/src/client.rs index e45e8eb0c..860d345c0 100644 --- a/crates/sprout-cli/src/client.rs +++ b/crates/buzz-cli/src/client.rs @@ -117,12 +117,12 @@ fn sign_nip98( } // --------------------------------------------------------------------------- -// SproutClient +// BuzzClient // --------------------------------------------------------------------------- -pub struct SproutClient { +pub struct BuzzClient { http: reqwest::Client, - relay_url: String, // base URL, no trailing slash, e.g. "https://relay.sprout.place" + relay_url: String, // base URL, no trailing slash, e.g. "https://relay.buzz.place" keys: Keys, /// Optional NIP-OA auth tag injected into every signed event. auth_tag: Option, @@ -130,7 +130,7 @@ pub struct SproutClient { auth_tag_json: Option, } -impl SproutClient { +impl BuzzClient { pub fn new( relay_url: String, keys: Keys, @@ -290,12 +290,12 @@ impl SproutClient { /// Publish an ephemeral event via WebSocket with NIP-42 authentication. /// /// The relay rejects ephemeral kinds (20000–29999) over HTTP. Delegates to - /// `sprout_ws_client::publish_event` which handles connect, NIP-42 auth, + /// `buzz_ws_client::publish_event` which handles connect, NIP-42 auth, /// EVENT send, OK wait, and graceful close. pub async fn publish_ephemeral_event(&self, event: nostr::Event) -> Result { let ws_url = to_ws_url(&self.relay_url); let ok = - sprout_ws_client::publish_event(&ws_url, event, &self.keys, self.auth_tag.as_ref(), 10) + buzz_ws_client::publish_event(&ws_url, event, &self.keys, self.auth_tag.as_ref(), 10) .await .map_err(|e| CliError::Other(e.to_string()))?; @@ -440,9 +440,9 @@ impl SproutClient { .map(|s| s.to_string()) }) .unwrap_or(body); - if status == 403 && std::env::var("SPROUT_AUTH_TAG").is_ok() { + if status == 403 && std::env::var("BUZZ_AUTH_TAG").is_ok() { let message = format!( - "{message} (SPROUT_AUTH_TAG is set — it may be stale or revoked; try unsetting it)" + "{message} (BUZZ_AUTH_TAG is set — it may be stale or revoked; try unsetting it)" ); return Err(CliError::Relay { status, @@ -463,7 +463,7 @@ impl SproutClient { // --------------------------------------------------------------------------- /// Normalize a relay URL: ws:// → http://, wss:// → https://, strip trailing slash. -/// SPROUT_RELAY_URL may be ws/wss (copied from MCP config). +/// BUZZ_RELAY_URL may be ws/wss (copied from MCP config). pub fn normalize_relay_url(url: &str) -> String { url.replace("wss://", "https://") .replace("ws://", "http://") diff --git a/crates/sprout-cli/src/commands/channels.rs b/crates/buzz-cli/src/commands/channels.rs similarity index 88% rename from crates/sprout-cli/src/commands/channels.rs rename to crates/buzz-cli/src/commands/channels.rs index 61be28cba..8e4984f38 100644 --- a/crates/sprout-cli/src/commands/channels.rs +++ b/crates/buzz-cli/src/commands/channels.rs @@ -2,7 +2,7 @@ use uuid::Uuid; use crate::client::{ extract_d_tag, extract_p_tags, extract_tag_value, normalize_write_response, - print_create_response, SproutClient, + print_create_response, BuzzClient, }; use crate::error::CliError; use crate::validate::{parse_uuid, read_or_stdin, validate_hex64, validate_uuid}; @@ -21,7 +21,7 @@ fn extract_channel_metadata(e: &serde_json::Value) -> serde_json::Value { } pub async fn cmd_list_channels( - client: &SproutClient, + client: &BuzzClient, visibility: Option<&str>, member: Option, limit: Option, @@ -117,7 +117,7 @@ pub async fn cmd_list_channels( /// (private channels they're not a member of), so we just post-filter the /// returned events by name and project them into a stable JSON shape. pub async fn cmd_search_channels( - client: &SproutClient, + client: &BuzzClient, query: &str, exact: bool, include_archived: bool, @@ -195,7 +195,7 @@ impl ChannelSummary { "d" => channel_id = val.map(str::to_string), "name" => name = val.map(str::to_string), "t" => channel_type = val.map(str::to_string), - // NIP-29 emits both `private` and `public` (Sprout adds the latter). + // NIP-29 emits both `private` and `public` (Buzz adds the latter). // The presence of either tag is the source of truth; tag value is unused. "private" => visibility = Some("private".to_string()), "public" => visibility = Some("public".to_string()), @@ -229,7 +229,7 @@ fn name_matches(name: &str, needle_lower: &str, exact: bool) -> bool { } } -pub async fn cmd_get_channel(client: &SproutClient, channel_id: &str) -> Result<(), CliError> { +pub async fn cmd_get_channel(client: &BuzzClient, channel_id: &str) -> Result<(), CliError> { validate_uuid(channel_id)?; let filter = serde_json::json!({ "kinds": [39000], @@ -250,7 +250,7 @@ pub async fn cmd_get_channel(client: &SproutClient, channel_id: &str) -> Result< } pub async fn cmd_list_channel_members( - client: &SproutClient, + client: &BuzzClient, channel_id: &str, ) -> Result<(), CliError> { validate_uuid(channel_id)?; @@ -267,7 +267,7 @@ pub async fn cmd_list_channel_members( Ok(()) } -pub async fn cmd_get_canvas(client: &SproutClient, channel_id: &str) -> Result<(), CliError> { +pub async fn cmd_get_canvas(client: &BuzzClient, channel_id: &str) -> Result<(), CliError> { validate_uuid(channel_id)?; let filter = serde_json::json!({ "kinds": [40100], @@ -292,7 +292,7 @@ pub async fn cmd_get_canvas(client: &SproutClient, channel_id: &str) -> Result<( // --------------------------------------------------------------------------- pub async fn cmd_create_channel( - client: &SproutClient, + client: &BuzzClient, name: &str, channel_type: &str, visibility: &str, @@ -318,17 +318,17 @@ pub async fn cmd_create_channel( let channel_uuid = Uuid::new_v4(); let vis = match visibility { - "open" => sprout_sdk::Visibility::Open, - "private" => sprout_sdk::Visibility::Private, + "open" => buzz_sdk::Visibility::Open, + "private" => buzz_sdk::Visibility::Private, _ => unreachable!(), }; let ct = match channel_type { - "stream" => sprout_sdk::ChannelKind::Stream, - "forum" => sprout_sdk::ChannelKind::Forum, + "stream" => buzz_sdk::ChannelKind::Stream, + "forum" => buzz_sdk::ChannelKind::Forum, _ => unreachable!(), }; let builder = - sprout_sdk::build_create_channel(channel_uuid, name, Some(vis), Some(ct), description) + buzz_sdk::build_create_channel(channel_uuid, name, Some(vis), Some(ct), description) .map_err(|e| CliError::Other(format!("build_create_channel failed: {e}")))?; let event = client.sign_event(builder)?; @@ -338,7 +338,7 @@ pub async fn cmd_create_channel( } pub async fn cmd_update_channel( - client: &SproutClient, + client: &BuzzClient, channel_id: &str, name: Option<&str>, description: Option<&str>, @@ -350,7 +350,7 @@ pub async fn cmd_update_channel( } let channel_uuid = parse_uuid(channel_id)?; - let builder = sprout_sdk::build_update_channel(channel_uuid, name, description, None, None) + let builder = buzz_sdk::build_update_channel(channel_uuid, name, description, None, None) .map_err(|e| CliError::Other(format!("build_update_channel failed: {e}")))?; let event = client.sign_event(builder)?; @@ -360,13 +360,13 @@ pub async fn cmd_update_channel( } pub async fn cmd_set_channel_topic( - client: &SproutClient, + client: &BuzzClient, channel_id: &str, topic: &str, ) -> Result<(), CliError> { let channel_uuid = parse_uuid(channel_id)?; - let builder = sprout_sdk::build_set_topic(channel_uuid, topic) + let builder = buzz_sdk::build_set_topic(channel_uuid, topic) .map_err(|e| CliError::Other(format!("build_set_topic failed: {e}")))?; let event = client.sign_event(builder)?; @@ -376,13 +376,13 @@ pub async fn cmd_set_channel_topic( } pub async fn cmd_set_channel_purpose( - client: &SproutClient, + client: &BuzzClient, channel_id: &str, purpose: &str, ) -> Result<(), CliError> { let channel_uuid = parse_uuid(channel_id)?; - let builder = sprout_sdk::build_set_purpose(channel_uuid, purpose) + let builder = buzz_sdk::build_set_purpose(channel_uuid, purpose) .map_err(|e| CliError::Other(format!("build_set_purpose failed: {e}")))?; let event = client.sign_event(builder)?; @@ -391,10 +391,10 @@ pub async fn cmd_set_channel_purpose( Ok(()) } -pub async fn cmd_join_channel(client: &SproutClient, channel_id: &str) -> Result<(), CliError> { +pub async fn cmd_join_channel(client: &BuzzClient, channel_id: &str) -> Result<(), CliError> { let channel_uuid = parse_uuid(channel_id)?; - let builder = sprout_sdk::build_join(channel_uuid) + let builder = buzz_sdk::build_join(channel_uuid) .map_err(|e| CliError::Other(format!("build_join failed: {e}")))?; let event = client.sign_event(builder)?; @@ -403,10 +403,10 @@ pub async fn cmd_join_channel(client: &SproutClient, channel_id: &str) -> Result Ok(()) } -pub async fn cmd_leave_channel(client: &SproutClient, channel_id: &str) -> Result<(), CliError> { +pub async fn cmd_leave_channel(client: &BuzzClient, channel_id: &str) -> Result<(), CliError> { let channel_uuid = parse_uuid(channel_id)?; - let builder = sprout_sdk::build_leave(channel_uuid) + let builder = buzz_sdk::build_leave(channel_uuid) .map_err(|e| CliError::Other(format!("build_leave failed: {e}")))?; let event = client.sign_event(builder)?; @@ -415,10 +415,10 @@ pub async fn cmd_leave_channel(client: &SproutClient, channel_id: &str) -> Resul Ok(()) } -pub async fn cmd_archive_channel(client: &SproutClient, channel_id: &str) -> Result<(), CliError> { +pub async fn cmd_archive_channel(client: &BuzzClient, channel_id: &str) -> Result<(), CliError> { let channel_uuid = parse_uuid(channel_id)?; - let builder = sprout_sdk::build_archive(channel_uuid) + let builder = buzz_sdk::build_archive(channel_uuid) .map_err(|e| CliError::Other(format!("build_archive failed: {e}")))?; let event = client.sign_event(builder)?; @@ -427,13 +427,10 @@ pub async fn cmd_archive_channel(client: &SproutClient, channel_id: &str) -> Res Ok(()) } -pub async fn cmd_unarchive_channel( - client: &SproutClient, - channel_id: &str, -) -> Result<(), CliError> { +pub async fn cmd_unarchive_channel(client: &BuzzClient, channel_id: &str) -> Result<(), CliError> { let channel_uuid = parse_uuid(channel_id)?; - let builder = sprout_sdk::build_unarchive(channel_uuid) + let builder = buzz_sdk::build_unarchive(channel_uuid) .map_err(|e| CliError::Other(format!("build_unarchive failed: {e}")))?; let event = client.sign_event(builder)?; @@ -442,10 +439,10 @@ pub async fn cmd_unarchive_channel( Ok(()) } -pub async fn cmd_delete_channel(client: &SproutClient, channel_id: &str) -> Result<(), CliError> { +pub async fn cmd_delete_channel(client: &BuzzClient, channel_id: &str) -> Result<(), CliError> { let channel_uuid = parse_uuid(channel_id)?; - let builder = sprout_sdk::build_delete_channel(channel_uuid) + let builder = buzz_sdk::build_delete_channel(channel_uuid) .map_err(|e| CliError::Other(format!("build_delete_channel failed: {e}")))?; let event = client.sign_event(builder)?; @@ -455,7 +452,7 @@ pub async fn cmd_delete_channel(client: &SproutClient, channel_id: &str) -> Resu } pub async fn cmd_add_channel_member( - client: &SproutClient, + client: &BuzzClient, channel_id: &str, pubkey: &str, role: Option<&str>, @@ -465,18 +462,18 @@ pub async fn cmd_add_channel_member( let typed_role = match role { None => None, - Some("owner") => Some(sprout_sdk::MemberRole::Owner), - Some("admin") => Some(sprout_sdk::MemberRole::Admin), - Some("member") => Some(sprout_sdk::MemberRole::Member), - Some("guest") => Some(sprout_sdk::MemberRole::Guest), - Some("bot") => Some(sprout_sdk::MemberRole::Bot), + Some("owner") => Some(buzz_sdk::MemberRole::Owner), + Some("admin") => Some(buzz_sdk::MemberRole::Admin), + Some("member") => Some(buzz_sdk::MemberRole::Member), + Some("guest") => Some(buzz_sdk::MemberRole::Guest), + Some("bot") => Some(buzz_sdk::MemberRole::Bot), Some(other) => { return Err(CliError::Usage(format!( "--role must be owner/admin/member/guest/bot (got: {other})" ))) } }; - let builder = sprout_sdk::build_add_member(channel_uuid, pubkey, typed_role) + let builder = buzz_sdk::build_add_member(channel_uuid, pubkey, typed_role) .map_err(|e| CliError::Other(format!("build_add_member failed: {e}")))?; let event = client.sign_event(builder)?; @@ -486,14 +483,14 @@ pub async fn cmd_add_channel_member( } pub async fn cmd_remove_channel_member( - client: &SproutClient, + client: &BuzzClient, channel_id: &str, pubkey: &str, ) -> Result<(), CliError> { validate_hex64(pubkey)?; let channel_uuid = parse_uuid(channel_id)?; - let builder = sprout_sdk::build_remove_member(channel_uuid, pubkey) + let builder = buzz_sdk::build_remove_member(channel_uuid, pubkey) .map_err(|e| CliError::Other(format!("build_remove_member failed: {e}")))?; let event = client.sign_event(builder)?; @@ -503,7 +500,7 @@ pub async fn cmd_remove_channel_member( } /// Set the channel addition policy — sign and submit a kind:10100 (agent profile) event. -pub async fn cmd_set_add_policy(client: &SproutClient, policy: &str) -> Result<(), CliError> { +pub async fn cmd_set_add_policy(client: &BuzzClient, policy: &str) -> Result<(), CliError> { match policy { "anyone" | "owner_only" | "nobody" => {} _ => { @@ -516,7 +513,7 @@ pub async fn cmd_set_add_policy(client: &SproutClient, policy: &str) -> Result<( let content = serde_json::json!({ "channel_add_policy": policy }).to_string(); use nostr::{EventBuilder, Kind}; let builder = EventBuilder::new( - Kind::Custom(sprout_sdk::kind::KIND_AGENT_PROFILE as u16), + Kind::Custom(buzz_sdk::kind::KIND_AGENT_PROFILE as u16), &content, ) .tags([]); @@ -528,14 +525,14 @@ pub async fn cmd_set_add_policy(client: &SproutClient, policy: &str) -> Result<( } pub async fn cmd_set_canvas( - client: &SproutClient, + client: &BuzzClient, channel_id: &str, content: &str, ) -> Result<(), CliError> { let content = read_or_stdin(content)?; let channel_uuid = parse_uuid(channel_id)?; - let builder = sprout_sdk::build_set_canvas(channel_uuid, &content) + let builder = buzz_sdk::build_set_canvas(channel_uuid, &content) .map_err(|e| CliError::Other(format!("build_set_canvas failed: {e}")))?; let event = client.sign_event(builder)?; @@ -550,7 +547,7 @@ pub async fn cmd_set_canvas( pub async fn dispatch( cmd: crate::ChannelsCmd, - client: &SproutClient, + client: &BuzzClient, format: &crate::OutputFormat, ) -> Result<(), CliError> { use crate::ChannelsCmd; @@ -614,7 +611,7 @@ pub async fn dispatch( } } -pub async fn dispatch_canvas(cmd: crate::CanvasCmd, client: &SproutClient) -> Result<(), CliError> { +pub async fn dispatch_canvas(cmd: crate::CanvasCmd, client: &BuzzClient) -> Result<(), CliError> { use crate::CanvasCmd; match cmd { CanvasCmd::Get { channel } => cmd_get_canvas(client, &channel).await, @@ -635,7 +632,7 @@ mod tests { fn from_event_extracts_known_tags() { let ev = event(json!([ ["d", "11111111-1111-1111-1111-111111111111"], - ["name", "sprout-chat-composer"], + ["name", "buzz-chat-composer"], ["t", "stream"], ["public"], ["about", "About text"], @@ -644,7 +641,7 @@ mod tests { ])); let s = ChannelSummary::from_event(&ev).expect("parse"); assert_eq!(s.channel_id, "11111111-1111-1111-1111-111111111111"); - assert_eq!(s.name, "sprout-chat-composer"); + assert_eq!(s.name, "buzz-chat-composer"); assert_eq!(s.channel_type.as_deref(), Some("stream")); assert_eq!(s.visibility.as_deref(), Some("public")); assert!(!s.archived); @@ -704,14 +701,14 @@ mod tests { #[test] fn name_matches_substring_case_insensitive() { - assert!(name_matches("Sprout-Chat-Composer", "composer", false)); - assert!(name_matches("Sprout-Chat-Composer", "sprout", false)); + assert!(name_matches("Buzz-Chat-Composer", "composer", false)); + assert!(name_matches("Buzz-Chat-Composer", "buzz", false)); assert!(!name_matches("design", "composer", false)); } #[test] fn name_matches_exact_case_insensitive() { - assert!(name_matches("Sprout", "sprout", true)); - assert!(!name_matches("Sprout-Chat", "sprout", true)); + assert!(name_matches("Buzz", "buzz", true)); + assert!(!name_matches("Buzz-Chat", "buzz", true)); } } diff --git a/crates/sprout-cli/src/commands/dms.rs b/crates/buzz-cli/src/commands/dms.rs similarity index 89% rename from crates/sprout-cli/src/commands/dms.rs rename to crates/buzz-cli/src/commands/dms.rs index a3c049296..7f0baf53e 100644 --- a/crates/sprout-cli/src/commands/dms.rs +++ b/crates/buzz-cli/src/commands/dms.rs @@ -1,11 +1,11 @@ use uuid::Uuid; -use crate::client::{extract_d_tag, normalize_write_response, SproutClient}; +use crate::client::{extract_d_tag, normalize_write_response, BuzzClient}; use crate::error::CliError; use crate::validate::{parse_uuid, sdk_err, validate_hex64}; /// List DM conversations by querying kind:41001 (relay-confirmed DMs) filtered by our pubkey. -pub async fn cmd_list_dms(client: &SproutClient, limit: Option) -> Result<(), CliError> { +pub async fn cmd_list_dms(client: &BuzzClient, limit: Option) -> Result<(), CliError> { let my_pk = client.keys().public_key().to_hex(); let limit = limit.unwrap_or(50).min(200); let filter = serde_json::json!({ @@ -48,7 +48,7 @@ pub async fn cmd_list_dms(client: &SproutClient, limit: Option) -> Result<( } /// Open a DM with one or more users — sign and submit a kind:41010 event with a d-tag. -pub async fn cmd_open_dm(client: &SproutClient, pubkeys: &[String]) -> Result<(), CliError> { +pub async fn cmd_open_dm(client: &BuzzClient, pubkeys: &[String]) -> Result<(), CliError> { if pubkeys.is_empty() || pubkeys.len() > 8 { return Err(CliError::Usage("--pubkey: must provide 1-8 pubkeys".into())); } @@ -93,14 +93,14 @@ pub async fn cmd_open_dm(client: &SproutClient, pubkeys: &[String]) -> Result<() } /// Hide a DM channel — sign and submit a kind:41012 event with h-tag. -pub async fn cmd_hide_dm(client: &SproutClient, channel_id: &str) -> Result<(), CliError> { +pub async fn cmd_hide_dm(client: &BuzzClient, channel_id: &str) -> Result<(), CliError> { let channel_uuid = parse_uuid(channel_id)?; use nostr::{EventBuilder, Kind, Tag}; let tags = vec![Tag::parse(["h", &channel_uuid.to_string()]) .map_err(|e| CliError::Other(format!("tag error: {e}")))?]; let builder = - EventBuilder::new(Kind::Custom(sprout_sdk::kind::KIND_DM_HIDE as u16), "").tags(tags); + EventBuilder::new(Kind::Custom(buzz_sdk::kind::KIND_DM_HIDE as u16), "").tags(tags); let event = client.sign_event(builder)?; let resp = client.submit_event(event).await?; @@ -110,14 +110,14 @@ pub async fn cmd_hide_dm(client: &SproutClient, channel_id: &str) -> Result<(), /// Add a member to a DM group — sign and submit a kind:41011 event. pub async fn cmd_add_dm_member( - client: &SproutClient, + client: &BuzzClient, channel_id: &str, pubkey: &str, ) -> Result<(), CliError> { let channel_uuid = parse_uuid(channel_id)?; validate_hex64(pubkey)?; - let builder = sprout_sdk::build_dm_add_member(channel_uuid, pubkey).map_err(sdk_err)?; + let builder = buzz_sdk::build_dm_add_member(channel_uuid, pubkey).map_err(sdk_err)?; let event = client.sign_event(builder)?; let resp = client.submit_event(event).await?; @@ -129,7 +129,7 @@ pub async fn cmd_add_dm_member( // Dispatch // --------------------------------------------------------------------------- -pub async fn dispatch(cmd: crate::DmsCmd, client: &SproutClient) -> Result<(), CliError> { +pub async fn dispatch(cmd: crate::DmsCmd, client: &BuzzClient) -> Result<(), CliError> { use crate::DmsCmd; match cmd { DmsCmd::List { limit } => cmd_list_dms(client, limit).await, diff --git a/crates/sprout-cli/src/commands/emoji.rs b/crates/buzz-cli/src/commands/emoji.rs similarity index 89% rename from crates/sprout-cli/src/commands/emoji.rs rename to crates/buzz-cli/src/commands/emoji.rs index 6e8fac1b3..ee51b0c94 100644 --- a/crates/sprout-cli/src/commands/emoji.rs +++ b/crates/buzz-cli/src/commands/emoji.rs @@ -1,12 +1,12 @@ use std::io::Read; -use crate::client::{normalize_write_response, SproutClient}; +use crate::client::{normalize_write_response, BuzzClient}; use crate::error::CliError; -use sprout_sdk::CustomEmoji; +use buzz_sdk::CustomEmoji; /// d-tag for a member's own custom emoji set (kind:30030). Mirrors the SDK /// constant; the workspace palette is the union of every member's own set. -const CUSTOM_EMOJI_SET_D_TAG: &str = sprout_sdk::CUSTOM_EMOJI_SET_D_TAG; +const CUSTOM_EMOJI_SET_D_TAG: &str = buzz_sdk::CUSTOM_EMOJI_SET_D_TAG; /// Custom emoji entry in CLI output. #[derive(Debug, serde::Serialize)] @@ -59,10 +59,10 @@ fn union_custom_emoji(events: &[serde_json::Value]) -> Vec { } /// List the workspace custom emoji palette: the union of every member's -/// own kind:30030 set (d=`sprout:custom-emoji`). -async fn cmd_list(client: &SproutClient) -> Result<(), CliError> { +/// own kind:30030 set (d=`buzz:custom-emoji`). +async fn cmd_list(client: &BuzzClient) -> Result<(), CliError> { let filter = serde_json::json!({ - "kinds": [sprout_sdk::kind::KIND_EMOJI_SET], + "kinds": [buzz_sdk::kind::KIND_EMOJI_SET], "#d": [CUSTOM_EMOJI_SET_D_TAG], }); let raw = client.query(&filter).await?; @@ -76,10 +76,10 @@ async fn cmd_list(client: &SproutClient) -> Result<(), CliError> { /// Fetch the caller's own current custom emoji set (latest kind:30030 under /// the d-tag, authored by the caller). Empty when none published yet. -async fn fetch_own_emoji(client: &SproutClient) -> Result, CliError> { +async fn fetch_own_emoji(client: &BuzzClient) -> Result, CliError> { let me = client.keys().public_key().to_hex(); let filter = serde_json::json!({ - "kinds": [sprout_sdk::kind::KIND_EMOJI_SET], + "kinds": [buzz_sdk::kind::KIND_EMOJI_SET], "#d": [CUSTOM_EMOJI_SET_D_TAG], "authors": [me], "limit": 1, @@ -101,8 +101,8 @@ async fn fetch_own_emoji(client: &SproutClient) -> Result, CliE } /// Publish the caller's own (replaced) kind:30030 set, signed as the caller. -async fn publish_own_set(client: &SproutClient, emojis: &[CustomEmoji]) -> Result<(), CliError> { - let builder = sprout_sdk::build_custom_emoji_set(emojis) +async fn publish_own_set(client: &BuzzClient, emojis: &[CustomEmoji]) -> Result<(), CliError> { + let builder = buzz_sdk::build_custom_emoji_set(emojis) .map_err(|e| CliError::Other(format!("build_custom_emoji_set failed: {e}")))?; let event = client.sign_event(builder)?; let resp = client.submit_event(event).await?; @@ -111,8 +111,8 @@ async fn publish_own_set(client: &SproutClient, emojis: &[CustomEmoji]) -> Resul } /// Add/update a shortcode in the caller's own set (read-modify-write). -async fn cmd_set(client: &SproutClient, shortcode: &str, url: &str) -> Result<(), CliError> { - let normalized = sprout_sdk::normalize_custom_emoji_shortcode(shortcode) +async fn cmd_set(client: &BuzzClient, shortcode: &str, url: &str) -> Result<(), CliError> { + let normalized = buzz_sdk::normalize_custom_emoji_shortcode(shortcode) .map_err(|e| CliError::Other(format!("invalid shortcode: {e}")))?; let mut emojis = fetch_own_emoji(client).await?; emojis.retain(|e| e.shortcode != normalized); @@ -124,8 +124,8 @@ async fn cmd_set(client: &SproutClient, shortcode: &str, url: &str) -> Result<() } /// Remove a shortcode from the caller's own set (read-modify-write). -async fn cmd_rm(client: &SproutClient, shortcode: &str) -> Result<(), CliError> { - let normalized = sprout_sdk::normalize_custom_emoji_shortcode(shortcode) +async fn cmd_rm(client: &BuzzClient, shortcode: &str) -> Result<(), CliError> { + let normalized = buzz_sdk::normalize_custom_emoji_shortcode(shortcode) .map_err(|e| CliError::Other(format!("invalid shortcode: {e}")))?; let mut emojis = fetch_own_emoji(client).await?; let before = emojis.len(); @@ -181,7 +181,7 @@ fn write_output(output: &str, file: Option<&str>) -> Result<(), CliError> { /// Export custom emojis to stdout or a file. async fn cmd_export( - client: &SproutClient, + client: &BuzzClient, file: Option<&str>, scope: &crate::EmojiScope, ) -> Result<(), CliError> { @@ -202,7 +202,7 @@ async fn cmd_export( } crate::EmojiScope::Workspace => { let filter = serde_json::json!({ - "kinds": [sprout_sdk::kind::KIND_EMOJI_SET], + "kinds": [buzz_sdk::kind::KIND_EMOJI_SET], "#d": [CUSTOM_EMOJI_SET_D_TAG], }); let raw = client.query(&filter).await?; @@ -218,7 +218,7 @@ async fn cmd_export( /// Import custom emojis from stdin or a file into the caller's own set. async fn cmd_import( - client: &SproutClient, + client: &BuzzClient, file: Option<&str>, replace: bool, dry_run: bool, @@ -247,7 +247,7 @@ async fn cmd_import( .get("url") .and_then(|v| v.as_str()) .ok_or_else(|| CliError::Usage(format!("emojis[{i}]: missing \"url\" field")))?; - let normalized = sprout_sdk::normalize_custom_emoji_shortcode(shortcode) + let normalized = buzz_sdk::normalize_custom_emoji_shortcode(shortcode) .map_err(|e| CliError::Usage(format!("emojis[{i}]: invalid shortcode: {e}")))?; import_entries.push(CustomEmoji { shortcode: normalized, @@ -298,7 +298,7 @@ async fn cmd_import( // Dispatch // --------------------------------------------------------------------------- -pub async fn dispatch(cmd: crate::EmojiCmd, client: &SproutClient) -> Result<(), CliError> { +pub async fn dispatch(cmd: crate::EmojiCmd, client: &BuzzClient) -> Result<(), CliError> { use crate::EmojiCmd; match cmd { EmojiCmd::List => cmd_list(client).await, @@ -322,14 +322,14 @@ mod tests { let events = vec![ serde_json::json!({ "tags": [ - ["d", "sprout:custom-emoji"], + ["d", "buzz:custom-emoji"], ["emoji", "zort", "https://example.com/zort.png"], ["emoji", "narf", "https://example.com/narf.png"] ] }), serde_json::json!({ "tags": [ - ["d", "sprout:custom-emoji"], + ["d", "buzz:custom-emoji"], // exact duplicate (same shortcode+url) — collapses ["emoji", "narf", "https://example.com/narf.png"], // same shortcode, different url — both kept (distinct pair) diff --git a/crates/sprout-cli/src/commands/feed.rs b/crates/buzz-cli/src/commands/feed.rs similarity index 96% rename from crates/sprout-cli/src/commands/feed.rs rename to crates/buzz-cli/src/commands/feed.rs index 7a9155534..7594c1928 100644 --- a/crates/sprout-cli/src/commands/feed.rs +++ b/crates/buzz-cli/src/commands/feed.rs @@ -1,13 +1,13 @@ use std::cmp::Reverse; -use crate::client::{normalize_events, SproutClient}; +use crate::client::{normalize_events, BuzzClient}; use crate::error::CliError; const VALID_FEED_TYPES: &[&str] = &["mentions", "needs_action", "activity", "agent_activity"]; /// Get activity feed — query events mentioning our pubkey (via p-tag). pub async fn cmd_get_feed( - client: &SproutClient, + client: &BuzzClient, since: Option, limit: Option, types: Option<&str>, @@ -70,7 +70,7 @@ pub async fn cmd_get_feed( pub async fn dispatch( cmd: crate::FeedCmd, - client: &SproutClient, + client: &BuzzClient, format: &crate::OutputFormat, ) -> Result<(), CliError> { use crate::FeedCmd; diff --git a/crates/sprout-cli/src/commands/mem.rs b/crates/buzz-cli/src/commands/mem.rs similarity index 93% rename from crates/sprout-cli/src/commands/mem.rs rename to crates/buzz-cli/src/commands/mem.rs index cb5bdb262..b777e14cf 100644 --- a/crates/sprout-cli/src/commands/mem.rs +++ b/crates/buzz-cli/src/commands/mem.rs @@ -1,15 +1,15 @@ -//! `sprout mem` — agent-side engram management (NIP-AE). +//! `buzz mem` — agent-side engram management (NIP-AE). //! //! Subcommands: -//! - `sprout mem ls` — list non-tombstoned memories -//! - `sprout mem get ` — print the value to stdout -//! - `sprout mem hash ` — print sha256(value) hex -//! - `sprout mem set ` — write a value (use `-` for stdin) -//! - `sprout mem patch ` — apply a unified diff to the current value -//! - `sprout mem rm ` — publish a tombstone +//! - `buzz mem ls` — list non-tombstoned memories +//! - `buzz mem get ` — print the value to stdout +//! - `buzz mem hash ` — print sha256(value) hex +//! - `buzz mem set ` — write a value (use `-` for stdin) +//! - `buzz mem patch ` — apply a unified diff to the current value +//! - `buzz mem rm ` — publish a tombstone //! -//! By default, the caller's `SPROUT_PRIVATE_KEY` is the agent's nsec. The -//! agent's owner pubkey is resolved from `SPROUT_AUTH_TAG` (NIP-OA attestation) +//! By default, the caller's `BUZZ_PRIVATE_KEY` is the agent's nsec. The +//! agent's owner pubkey is resolved from `BUZZ_AUTH_TAG` (NIP-OA attestation) //! or the `--owner` flag. Read commands also support owner-side recovery via //! `--agent `: the CLI identity is treated as the owner and decrypts //! the agent's engrams through the same agent↔owner NIP-44 conversation key. @@ -19,25 +19,25 @@ use std::time::SystemTime; use sha2::{Digest, Sha256}; -use nostr::PublicKey; -use sprout_core::engram::{ +use buzz_core::engram::{ self, conversation_key, d_tag, normalize_slug, select_head, validate_and_decrypt, Body, Listing, }; -use sprout_core::kind::KIND_AGENT_ENGRAM; +use buzz_core::kind::KIND_AGENT_ENGRAM; +use nostr::PublicKey; -use crate::client::SproutClient; +use crate::client::BuzzClient; use crate::error::CliError; /// Resolve the agent's owner pubkey: explicit `--owner` flag wins, otherwise /// fall back to the NIP-OA `auth_tag` (which carries owner pubkey in slot 1). -fn resolve_owner(client: &SproutClient, owner_flag: Option<&str>) -> Result { +fn resolve_owner(client: &BuzzClient, owner_flag: Option<&str>) -> Result { if let Some(s) = owner_flag { return PublicKey::from_hex(s) .map_err(|e| CliError::Usage(format!("--owner must be a 64-hex pubkey: {e}"))); } let tag = client.auth_tag_owner_hex().ok_or_else(|| { CliError::Usage( - "owner pubkey required (set SPROUT_AUTH_TAG with a NIP-OA attestation or pass --owner)" + "owner pubkey required (set BUZZ_AUTH_TAG with a NIP-OA attestation or pass --owner)" .into(), ) })?; @@ -48,11 +48,11 @@ fn resolve_owner(client: &SproutClient, owner_flag: Option<&str>) -> Result`; the CLI identity is then the owner and the supplied /// pubkey is the agent author to query/decrypt. fn resolve_reader( - client: &SproutClient, + client: &BuzzClient, owner_flag: Option<&str>, agent_flag: Option<&str>, ) -> Result<(PublicKey, PublicKey, PublicKey), CliError> { @@ -90,7 +90,7 @@ fn now_secs() -> u64 { /// `message` field starts with `"duplicate:"` when the write was rejected /// as already-superseded by a later head (NIP-33 LWW). In that case we /// surface a `Conflict` so callers don't lie about success. -async fn submit_engram(client: &SproutClient, event: nostr::Event) -> Result<(), CliError> { +async fn submit_engram(client: &BuzzClient, event: nostr::Event) -> Result<(), CliError> { let raw = client.submit_event(event).await?; let parsed: serde_json::Value = serde_json::from_str(&raw) .map_err(|e| CliError::Other(format!("relay response is not JSON: {e} ({raw})")))?; @@ -134,7 +134,7 @@ fn parse_events(json: &str) -> Result, CliError> { /// Fetch the head event for `slug`, returning `(Option, Option)`. async fn fetch_head( - client: &SproutClient, + client: &BuzzClient, agent: &PublicKey, owner: &PublicKey, slug: &str, @@ -185,9 +185,9 @@ async fn fetch_head( Ok((Some(head), body)) } -/// `sprout mem ls` — list non-tombstoned memory entries. +/// `buzz mem ls` — list non-tombstoned memory entries. pub async fn cmd_ls( - client: &SproutClient, + client: &BuzzClient, owner_flag: Option<&str>, agent_flag: Option<&str>, json: bool, @@ -271,11 +271,11 @@ pub async fn cmd_ls( Ok(()) } -/// `sprout mem get ` — print value (memory) or profile (core) to stdout. +/// `buzz mem get ` — print value (memory) or profile (core) to stdout. /// /// Exit codes: 0 on found, 1 on absent or tombstoned. pub async fn cmd_get( - client: &SproutClient, + client: &BuzzClient, raw_slug: &str, owner_flag: Option<&str>, agent_flag: Option<&str>, @@ -291,7 +291,7 @@ pub async fn cmd_get( Err(CliError::NotFound(format!("tombstoned: {slug}"))) } Some(Body::Memory { value: Some(v), .. }) => { - // Raw stdout, no trailing newline — round-trips with `sprout mem set foo -`. + // Raw stdout, no trailing newline — round-trips with `buzz mem set foo -`. std::io::stdout() .write_all(v.as_bytes()) .map_err(|e| CliError::Other(e.to_string())) @@ -302,7 +302,7 @@ pub async fn cmd_get( } } -/// `sprout mem set ` — write a value or core profile. +/// `buzz mem set ` — write a value or core profile. /// /// Pass `-` to read the value from stdin. /// @@ -312,7 +312,7 @@ pub async fn cmd_get( /// otherwise commit an empty value — silently destroying the slug. /// A literal `""` positional argument is still accepted (explicit intent). pub async fn cmd_set( - client: &SproutClient, + client: &BuzzClient, raw_slug: &str, raw_value: &str, owner_flag: Option<&str>, @@ -339,7 +339,7 @@ pub async fn cmd_set( if buf.is_empty() && !allow_empty { return Err(CliError::Usage( "refusing to write empty value from stdin (an upstream pipeline step likely \ - failed). Pass --allow-empty to confirm, or use `sprout mem rm ` to \ + failed). Pass --allow-empty to confirm, or use `buzz mem rm ` to \ tombstone." .into(), )); @@ -482,7 +482,7 @@ fn verify_hunks_at_declared_position( /// Used by `mem hash` and `mem patch` — they both need "the value or fail". /// Returns `(head_event, value)` so the caller can preserve monotonic ordering. async fn fetch_value( - client: &SproutClient, + client: &BuzzClient, agent: &PublicKey, owner: &PublicKey, slug: &str, @@ -499,14 +499,14 @@ async fn fetch_value( } } -/// `sprout mem hash ` — print sha256(value) in hex to stdout. +/// `buzz mem hash ` — print sha256(value) in hex to stdout. /// /// The output is a 64-character hex digest followed by a newline (line- /// oriented for shell use). Use this to capture a base-hash before editing, -/// then pass it to `sprout mem patch --base-hash ` to make the edit +/// then pass it to `buzz mem patch --base-hash ` to make the edit /// safe against concurrent writes. pub async fn cmd_hash( - client: &SproutClient, + client: &BuzzClient, raw_slug: &str, owner_flag: Option<&str>, agent_flag: Option<&str>, @@ -519,7 +519,7 @@ pub async fn cmd_hash( Ok(()) } -/// `sprout mem patch ` — apply a unified diff to the current value. +/// `buzz mem patch ` — apply a unified diff to the current value. /// /// Reads a unified diff from stdin (or `--patch-file `), fetches the /// current head, applies the diff with **strict context matching** (no @@ -536,7 +536,7 @@ pub async fn cmd_hash( /// can chain edits. #[allow(clippy::too_many_arguments)] pub async fn cmd_patch( - client: &SproutClient, + client: &BuzzClient, raw_slug: &str, patch_path: Option<&str>, base_hash: Option<&str>, @@ -558,7 +558,7 @@ pub async fn cmd_patch( } (None, false) => { return Err(CliError::Usage( - "missing --base-hash (run `sprout mem hash ` to get it). \ + "missing --base-hash (run `buzz mem hash ` to get it). \ Pass --no-base-hash to skip this check at your own risk." .into(), )); @@ -659,7 +659,7 @@ pub async fn cmd_patch( if new_value.is_empty() && !allow_empty { return Err(CliError::Usage( "refusing to write empty value (patch result is empty). \ - Pass --allow-empty to confirm, or use `sprout mem rm ` to tombstone." + Pass --allow-empty to confirm, or use `buzz mem rm ` to tombstone." .into(), )); } @@ -697,14 +697,14 @@ pub async fn cmd_patch( Ok(()) } -/// `sprout mem rm ` — publish a tombstone (`value: null`). +/// `buzz mem rm ` — publish a tombstone (`value: null`). /// /// `rm core` writes a tombstone-shaped body, but a core tombstone has no /// well-defined semantics in NIP-AE (the spec only defines tombstones for /// memory entries). We refuse it and tell the operator to overwrite `core` /// with an empty profile instead. pub async fn cmd_rm( - client: &SproutClient, + client: &BuzzClient, raw_slug: &str, owner_flag: Option<&str>, ) -> Result<(), CliError> { @@ -712,7 +712,7 @@ pub async fn cmd_rm( normalize_slug(raw_slug).map_err(|e| CliError::Usage(format!("invalid slug: {e}")))?; if slug == engram::CORE_SLUG { return Err(CliError::Usage( - "core cannot be tombstoned; overwrite it with `sprout mem set core ''` instead".into(), + "core cannot be tombstoned; overwrite it with `buzz mem set core ''` instead".into(), )); } let owner = resolve_owner(client, owner_flag)?; @@ -738,7 +738,7 @@ pub async fn cmd_rm( // Dispatch // --------------------------------------------------------------------------- -pub async fn dispatch(cmd: crate::MemCmd, client: &SproutClient) -> Result<(), CliError> { +pub async fn dispatch(cmd: crate::MemCmd, client: &BuzzClient) -> Result<(), CliError> { use crate::MemCmd; match cmd { MemCmd::Ls { owner, agent, json } => { @@ -789,8 +789,8 @@ mod tests { // verify base-hash from the shell. Hard-coded vectors from the NIST and // common quick-check inputs. - fn test_client(keys: nostr::Keys) -> SproutClient { - SproutClient::new("http://127.0.0.1:9".into(), keys, None, None).unwrap() + fn test_client(keys: nostr::Keys) -> BuzzClient { + BuzzClient::new("http://127.0.0.1:9".into(), keys, None, None).unwrap() } #[test] diff --git a/crates/sprout-cli/src/commands/messages.rs b/crates/buzz-cli/src/commands/messages.rs similarity index 96% rename from crates/sprout-cli/src/commands/messages.rs rename to crates/buzz-cli/src/commands/messages.rs index 380250ae1..d3449df36 100644 --- a/crates/sprout-cli/src/commands/messages.rs +++ b/crates/buzz-cli/src/commands/messages.rs @@ -1,14 +1,14 @@ +use buzz_sdk::{DiffMeta, ThreadRef, VoteDirection}; use nostr::PublicKey; -use sprout_sdk::{DiffMeta, ThreadRef, VoteDirection}; use uuid::Uuid; -use crate::client::{normalize_events, normalize_write_response, SproutClient}; +use crate::client::{normalize_events, normalize_write_response, BuzzClient}; use crate::error::CliError; use crate::validate::{ infer_language, parse_event_id, parse_uuid, read_or_stdin, truncate_diff, validate_content_size, validate_hex64, validate_uuid, MAX_DIFF_BYTES, }; -use sprout_sdk::mentions::{ +use buzz_sdk::mentions::{ extract_at_mentions_with_known, extract_nostr_uris, merge_mentions, strip_code_regions, MENTION_CAP, }; @@ -60,7 +60,7 @@ fn find_root_from_tags(tags: &serde_json::Value) -> Option { /// /// Ensures CLI-sent replies thread correctly using the same NIP-10 logic. async fn resolve_thread_ref( - client: &SproutClient, + client: &BuzzClient, parent_event_id: &str, ) -> Result { let parent_eid = parse_event_id(parent_event_id)?; @@ -90,7 +90,7 @@ async fn resolve_thread_ref( /// Resolve the channel UUID for an event by querying for it via POST /query. /// Extracts the `h` tag value from the returned event's tags. -async fn resolve_channel_id(client: &SproutClient, event_id: &str) -> Result { +async fn resolve_channel_id(client: &BuzzClient, event_id: &str) -> Result { let filter = serde_json::json!({ "ids": [event_id] }); @@ -130,7 +130,7 @@ async fn resolve_channel_id(client: &SproutClient, event_id: &str) -> Result Vec { @@ -205,7 +205,7 @@ async fn resolve_content_mentions( /// Fetch raw events for `filter` via the relay's `/query` endpoint. /// Returns `None` on any I/O or parse failure. async fn fetch_events( - client: &SproutClient, + client: &BuzzClient, filter: &serde_json::Value, ) -> Option> { let raw = client.query(filter).await.ok()?; @@ -215,7 +215,7 @@ async fn fetch_events( /// Extract member pubkeys (the `p` tag values) from a single 39002 event. async fn fetch_member_pubkeys( - client: &SproutClient, + client: &BuzzClient, filter: &serde_json::Value, ) -> Option> { let events = fetch_events(client, filter).await?; @@ -269,7 +269,7 @@ fn format_events(normalized: &str, format: &crate::OutputFormat) -> String { } pub async fn cmd_get_messages( - client: &SproutClient, + client: &BuzzClient, channel_id: &str, limit: Option, before: Option, @@ -310,7 +310,7 @@ pub async fn cmd_get_messages( } pub async fn cmd_get_thread( - client: &SproutClient, + client: &BuzzClient, channel_id: &str, event_id: &str, limit: Option, @@ -346,7 +346,7 @@ pub async fn cmd_get_thread( } pub async fn cmd_search( - client: &SproutClient, + client: &BuzzClient, query: &str, limit: Option, format: &crate::OutputFormat, @@ -378,7 +378,7 @@ pub struct SendMessageParams { } pub async fn cmd_send_message( - client: &SproutClient, + client: &BuzzClient, mut p: SendMessageParams, ) -> Result<(), CliError> { // Allow '-' to read content from stdin. This keeps callers from having to @@ -436,14 +436,14 @@ pub async fn cmd_send_message( let builder = match p.kind { Some(45001) => { - sprout_sdk::build_forum_post(channel_uuid, &final_content, &mention_refs, &media_tags) + buzz_sdk::build_forum_post(channel_uuid, &final_content, &mention_refs, &media_tags) .map_err(|e| CliError::Other(format!("build_forum_post failed: {e}")))? } Some(45003) => { let tr = thread_ref.as_ref().ok_or_else(|| { CliError::Usage("--reply-to is required for forum comments (kind 45003)".into()) })?; - sprout_sdk::build_forum_comment( + buzz_sdk::build_forum_comment( channel_uuid, &final_content, tr, @@ -452,7 +452,7 @@ pub async fn cmd_send_message( ) .map_err(|e| CliError::Other(format!("build_forum_comment failed: {e}")))? } - None | Some(9) => sprout_sdk::build_message( + None | Some(9) => buzz_sdk::build_message( channel_uuid, &final_content, thread_ref.as_ref(), @@ -490,10 +490,7 @@ pub struct SendDiffParams { pub reply_to: Option, } -pub async fn cmd_send_diff_message( - client: &SproutClient, - p: SendDiffParams, -) -> Result<(), CliError> { +pub async fn cmd_send_diff_message(client: &BuzzClient, p: SendDiffParams) -> Result<(), CliError> { if let Some(r) = &p.reply_to { validate_hex64(r)?; } @@ -556,7 +553,7 @@ pub async fn cmd_send_diff_message( }; let builder = - sprout_sdk::build_diff_message(channel_uuid, &diff, &diff_meta, thread_ref.as_ref()) + buzz_sdk::build_diff_message(channel_uuid, &diff, &diff_meta, thread_ref.as_ref()) .map_err(|e| CliError::Other(format!("build_diff_message failed: {e}")))?; let event = client.sign_event(builder)?; @@ -566,14 +563,14 @@ pub async fn cmd_send_diff_message( Ok(()) } -pub async fn cmd_delete_message(client: &SproutClient, event_id: &str) -> Result<(), CliError> { +pub async fn cmd_delete_message(client: &BuzzClient, event_id: &str) -> Result<(), CliError> { validate_hex64(event_id)?; // Resolve channel_id from the event's h-tag let channel_uuid = resolve_channel_id(client, event_id).await?; let target_eid = parse_event_id(event_id)?; - let builder = sprout_sdk::build_delete_message(channel_uuid, target_eid) + let builder = buzz_sdk::build_delete_message(channel_uuid, target_eid) .map_err(|e| CliError::Other(format!("build_delete_message failed: {e}")))?; let event = client.sign_event(builder)?; @@ -585,7 +582,7 @@ pub async fn cmd_delete_message(client: &SproutClient, event_id: &str) -> Result /// Edit a message you previously sent. pub async fn cmd_edit_message( - client: &SproutClient, + client: &BuzzClient, event_id: &str, content: &str, ) -> Result<(), CliError> { @@ -596,7 +593,7 @@ pub async fn cmd_edit_message( let channel_uuid = resolve_channel_id(client, event_id).await?; let target_eid = parse_event_id(event_id)?; - let builder = sprout_sdk::build_edit(channel_uuid, target_eid, content) + let builder = buzz_sdk::build_edit(channel_uuid, target_eid, content) .map_err(|e| CliError::Other(format!("build_edit failed: {e}")))?; let event = client.sign_event(builder)?; @@ -608,7 +605,7 @@ pub async fn cmd_edit_message( /// Vote on a forum post or comment. pub async fn cmd_vote_on_post( - client: &SproutClient, + client: &BuzzClient, event_id: &str, direction: &str, ) -> Result<(), CliError> { @@ -627,7 +624,7 @@ pub async fn cmd_vote_on_post( let channel_uuid = resolve_channel_id(client, event_id).await?; let target_eid = parse_event_id(event_id)?; - let builder = sprout_sdk::build_vote(channel_uuid, target_eid, vote_dir) + let builder = buzz_sdk::build_vote(channel_uuid, target_eid, vote_dir) .map_err(|e| CliError::Other(format!("build_vote failed: {e}")))?; let event = client.sign_event(builder)?; @@ -643,7 +640,7 @@ pub async fn cmd_vote_on_post( pub async fn dispatch( cmd: crate::MessagesCmd, - client: &SproutClient, + client: &BuzzClient, format: &crate::OutputFormat, ) -> Result<(), CliError> { use crate::MessagesCmd; @@ -738,10 +735,10 @@ pub async fn dispatch( #[cfg(test)] mod tests { use super::{find_root_from_tags, parse_member_pubkeys}; - use serde_json::json; - use sprout_sdk::mentions::{ + use buzz_sdk::mentions::{ extract_at_mentions_with_known, extract_at_names, match_names_to_profiles, MentionProfile, }; + use serde_json::json; const ID_A: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; const ID_B: &str = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; @@ -825,7 +822,7 @@ mod tests { // events the relay returns, the CLI's parse + match wiring produces // the right pubkeys. The async I/O wrapper around them is one // straight line; the pure stages it composes are exercised here and - // in sprout-sdk. + // in buzz-sdk. /// End-to-end (sans I/O): body text → extracted names → matched /// member pubkeys, using realistic 39002 + kind:0 event JSON. diff --git a/crates/sprout-cli/src/commands/mod.rs b/crates/buzz-cli/src/commands/mod.rs similarity index 100% rename from crates/sprout-cli/src/commands/mod.rs rename to crates/buzz-cli/src/commands/mod.rs diff --git a/crates/sprout-cli/src/commands/notes.rs b/crates/buzz-cli/src/commands/notes.rs similarity index 98% rename from crates/sprout-cli/src/commands/notes.rs rename to crates/buzz-cli/src/commands/notes.rs index affe29474..0e91a2151 100644 --- a/crates/sprout-cli/src/commands/notes.rs +++ b/crates/buzz-cli/src/commands/notes.rs @@ -1,4 +1,4 @@ -//! `sprout notes` — NIP-23 long-form editable notes (kind:30023). +//! `buzz notes` — NIP-23 long-form editable notes (kind:30023). //! //! Skill-sharing knowledge base for the team. Notes are parameterized-replaceable //! events keyed by `(kind=30023, pubkey, d-tag)`; the `d` tag is the human slug. @@ -30,7 +30,7 @@ use std::time::SystemTime; use nostr::{Event, EventBuilder, Kind, PublicKey, Tag, Timestamp, ToBech32}; -use crate::client::SproutClient; +use crate::client::BuzzClient; use crate::error::CliError; use crate::validate::validate_hex64; @@ -177,7 +177,7 @@ fn parse_events(json: &str) -> Result, CliError> { /// `NoteSnapshot::from_event` on the result for carry-forward, and `rm` only /// needs its presence to decide first-publish vs. update / deletable-or-not. /// (Quinn's option (b) — single tag-parser, isolated.) -pub async fn fetch_own_note(client: &SproutClient, slug: &str) -> Result, CliError> { +pub async fn fetch_own_note(client: &BuzzClient, slug: &str) -> Result, CliError> { let me = client.keys().public_key(); let filter = serde_json::json!({ "kinds": [KIND_LONG_FORM], @@ -196,7 +196,7 @@ pub async fn fetch_own_note(client: &SproutClient, slug: &str) -> Result Result, CliError> { +pub async fn fetch_by_slug(client: &BuzzClient, slug: &str) -> Result, CliError> { let filter = serde_json::json!({ "kinds": [KIND_LONG_FORM], "#d": [slug], @@ -217,10 +217,7 @@ pub async fn fetch_by_slug(client: &SproutClient, slug: &str) -> Result Result { +pub async fn resolve_author(client: &BuzzClient, author_flag: &str) -> Result { if author_flag == "me" { return Ok(client.keys().public_key()); } @@ -378,7 +375,7 @@ fn snapshots_from_events(events: Vec) -> Result, CliErr } async fn fetch_by_coord( - client: &SproutClient, + client: &BuzzClient, coord: &nostr::nips::nip01::Coordinate, ) -> Result, CliError> { let filter = serde_json::json!({ @@ -524,7 +521,7 @@ pub const SET_STDIN_MAX_BYTES: usize = 1024 * 1024; // --------------------------------------------------------------------------- pub async fn cmd_set( - client: &SproutClient, + client: &BuzzClient, slug: &str, title: Option<&str>, summary: Option<&str>, @@ -649,7 +646,7 @@ fn validate_get_args(naddr: bool, name: bool, author: bool, latest: bool) -> Res } pub async fn cmd_get( - client: &SproutClient, + client: &BuzzClient, naddr: Option<&str>, name: Option<&str>, author: Option<&str>, @@ -708,7 +705,7 @@ pub async fn cmd_get( } pub async fn cmd_ls( - client: &SproutClient, + client: &BuzzClient, author: Option<&str>, tag: Option<&str>, limit: Option, @@ -753,7 +750,7 @@ pub fn build_rm_event(coord: &nostr::nips::nip01::Coordinate) -> Result Result<(), CliError> { +pub async fn cmd_rm(client: &BuzzClient, slug: &str) -> Result<(), CliError> { // Read-before-write: only the author can delete their own note, and we // want a clear "nothing to delete" signal rather than emitting a kind:5 // for a coordinate that was never published. @@ -788,7 +785,7 @@ pub async fn cmd_rm(client: &SproutClient, slug: &str) -> Result<(), CliError> { Ok(()) } -pub async fn dispatch(cmd: crate::NotesCmd, client: &SproutClient) -> Result<(), CliError> { +pub async fn dispatch(cmd: crate::NotesCmd, client: &BuzzClient) -> Result<(), CliError> { use crate::NotesCmd; match cmd { NotesCmd::Set { diff --git a/crates/sprout-cli/src/commands/pack.rs b/crates/buzz-cli/src/commands/pack.rs similarity index 91% rename from crates/sprout-cli/src/commands/pack.rs rename to crates/buzz-cli/src/commands/pack.rs index de5f82fff..fc2edf751 100644 --- a/crates/sprout-cli/src/commands/pack.rs +++ b/crates/buzz-cli/src/commands/pack.rs @@ -1,4 +1,4 @@ -//! `sprout pack` subcommands — local persona pack operations. +//! `buzz pack` subcommands — local persona pack operations. //! //! These commands operate on local pack directories. No relay connection needed. @@ -6,7 +6,7 @@ use std::path::Path; use crate::error::CliError; -/// Run `sprout pack validate `. +/// Run `buzz pack validate `. /// /// Calls `validate_pack()` from the persona crate, prints diagnostics, /// and exits with the appropriate code: @@ -21,14 +21,14 @@ pub fn cmd_validate(path: &str) -> Result<(), CliError> { return Err(CliError::Usage(format!("not a directory: {path}"))); } - let report = sprout_persona::validate::validate_pack(pack_dir); + let report = buzz_persona::validate::validate_pack(pack_dir); for diag in &report.diagnostics { match diag { - sprout_persona::validate::ValidationDiagnostic::Error(msg) => { + buzz_persona::validate::ValidationDiagnostic::Error(msg) => { eprintln!(" ERROR: {msg}"); } - sprout_persona::validate::ValidationDiagnostic::Warning(msg) => { + buzz_persona::validate::ValidationDiagnostic::Warning(msg) => { eprintln!(" WARN: {msg}"); } } @@ -45,7 +45,7 @@ pub fn cmd_validate(path: &str) -> Result<(), CliError> { Ok(()) } -/// Run `sprout pack inspect `. +/// Run `buzz pack inspect `. /// /// Loads and resolves a pack, then pretty-prints a summary of each persona's /// effective configuration. @@ -59,7 +59,7 @@ pub fn cmd_inspect(path: &str) -> Result<(), CliError> { } // Resolve the pack — shows fully effective config (post-merge, post-split). - let pack = sprout_persona::resolve::resolve_pack(pack_dir) + let pack = buzz_persona::resolve::resolve_pack(pack_dir) .map_err(|e| CliError::Other(format!("failed to resolve pack: {e}")))?; // Header diff --git a/crates/sprout-cli/src/commands/reactions.rs b/crates/buzz-cli/src/commands/reactions.rs similarity index 89% rename from crates/sprout-cli/src/commands/reactions.rs rename to crates/buzz-cli/src/commands/reactions.rs index 863593067..8062b6a2a 100644 --- a/crates/sprout-cli/src/commands/reactions.rs +++ b/crates/buzz-cli/src/commands/reactions.rs @@ -2,12 +2,12 @@ use std::collections::HashMap; use nostr::EventId; -use crate::client::{normalize_write_response, SproutClient}; +use crate::client::{normalize_write_response, BuzzClient}; use crate::error::CliError; use crate::validate::validate_hex64; pub async fn cmd_add_reaction( - client: &SproutClient, + client: &BuzzClient, event_id: &str, emoji: &str, emoji_url: Option<&str>, @@ -17,10 +17,10 @@ pub async fn cmd_add_reaction( EventId::parse(event_id).map_err(|e| CliError::Usage(format!("invalid event ID: {e}")))?; let builder = if let Some(url) = emoji_url { - sprout_sdk::build_custom_emoji_reaction(target_eid, emoji, url) + buzz_sdk::build_custom_emoji_reaction(target_eid, emoji, url) .map_err(|e| CliError::Other(format!("build_custom_emoji_reaction failed: {e}")))? } else { - sprout_sdk::build_reaction(target_eid, emoji) + buzz_sdk::build_reaction(target_eid, emoji) .map_err(|e| CliError::Other(format!("build_reaction failed: {e}")))? }; @@ -32,7 +32,7 @@ pub async fn cmd_add_reaction( } pub async fn cmd_remove_reaction( - client: &SproutClient, + client: &BuzzClient, event_id: &str, emoji: &str, ) -> Result<(), CliError> { @@ -67,7 +67,7 @@ pub async fn cmd_remove_reaction( let reaction_eid = EventId::parse(reaction_event_id) .map_err(|e| CliError::Other(format!("invalid reaction event ID: {e}")))?; - let builder = sprout_sdk::build_remove_reaction(reaction_eid) + let builder = buzz_sdk::build_remove_reaction(reaction_eid) .map_err(|e| CliError::Other(format!("build_remove_reaction failed: {e}")))?; let event = client.sign_event(builder)?; @@ -77,7 +77,7 @@ pub async fn cmd_remove_reaction( Ok(()) } -pub async fn cmd_get_reactions(client: &SproutClient, event_id: &str) -> Result<(), CliError> { +pub async fn cmd_get_reactions(client: &BuzzClient, event_id: &str) -> Result<(), CliError> { validate_hex64(event_id)?; let filter = serde_json::json!({ "kinds": [7], @@ -128,7 +128,7 @@ pub async fn cmd_get_reactions(client: &SproutClient, event_id: &str) -> Result< // Dispatch // --------------------------------------------------------------------------- -pub async fn dispatch(cmd: crate::ReactionsCmd, client: &SproutClient) -> Result<(), CliError> { +pub async fn dispatch(cmd: crate::ReactionsCmd, client: &BuzzClient) -> Result<(), CliError> { use crate::ReactionsCmd; match cmd { ReactionsCmd::Add { diff --git a/crates/sprout-cli/src/commands/repos.rs b/crates/buzz-cli/src/commands/repos.rs similarity index 93% rename from crates/sprout-cli/src/commands/repos.rs rename to crates/buzz-cli/src/commands/repos.rs index db680912a..c65c31a72 100644 --- a/crates/sprout-cli/src/commands/repos.rs +++ b/crates/buzz-cli/src/commands/repos.rs @@ -1,4 +1,4 @@ -use crate::client::SproutClient; +use crate::client::BuzzClient; use crate::error::CliError; use crate::validate::validate_repo_id; @@ -7,7 +7,7 @@ use crate::validate::validate_repo_id; // --------------------------------------------------------------------------- pub async fn cmd_create_repo( - client: &SproutClient, + client: &BuzzClient, repo_id: &str, name: Option<&str>, description: Option<&str>, @@ -20,7 +20,7 @@ pub async fn cmd_create_repo( let clone_refs: Vec<&str> = clone_urls.iter().map(|s| s.as_str()).collect(); let relay_refs: Vec<&str> = relays.iter().map(|s| s.as_str()).collect(); - let builder = sprout_sdk::build_repo_announcement( + let builder = buzz_sdk::build_repo_announcement( repo_id, name, description, @@ -41,7 +41,7 @@ pub async fn cmd_create_repo( // --------------------------------------------------------------------------- pub async fn cmd_get_repo( - client: &SproutClient, + client: &BuzzClient, repo_id: &str, owner: Option<&str>, ) -> Result<(), CliError> { @@ -69,7 +69,7 @@ pub async fn cmd_get_repo( // --------------------------------------------------------------------------- pub async fn cmd_list_repos( - client: &SproutClient, + client: &BuzzClient, owner: Option<&str>, limit: Option, ) -> Result<(), CliError> { @@ -100,7 +100,7 @@ pub async fn cmd_list_repos( // Dispatch // --------------------------------------------------------------------------- -pub async fn dispatch(cmd: crate::ReposCmd, client: &SproutClient) -> Result<(), CliError> { +pub async fn dispatch(cmd: crate::ReposCmd, client: &BuzzClient) -> Result<(), CliError> { use crate::ReposCmd; match cmd { ReposCmd::Create { diff --git a/crates/sprout-cli/src/commands/social.rs b/crates/buzz-cli/src/commands/social.rs similarity index 92% rename from crates/sprout-cli/src/commands/social.rs rename to crates/buzz-cli/src/commands/social.rs index a0cf9b3e1..201435407 100644 --- a/crates/sprout-cli/src/commands/social.rs +++ b/crates/buzz-cli/src/commands/social.rs @@ -1,15 +1,15 @@ -use nostr::{EventBuilder, Kind, Tag}; -use serde::Deserialize; -use sprout_sdk::kind::{ +use buzz_sdk::kind::{ KIND_BOOKMARK_LIST, KIND_BOOKMARK_SET, KIND_FOLLOW_SET, KIND_MUTE_LIST, KIND_NIP65_RELAY_LIST_METADATA, KIND_PIN_LIST, }; +use nostr::{EventBuilder, Kind, Tag}; +use serde::Deserialize; -use crate::client::{normalize_write_response, SproutClient}; +use crate::client::{normalize_write_response, BuzzClient}; use crate::error::CliError; use crate::validate::{parse_event_id, validate_hex64}; -/// A single contact entry (CLI-local, not from sprout-sdk). +/// A single contact entry (CLI-local, not from buzz-sdk). #[derive(Debug, Deserialize)] pub struct ContactEntry { pub pubkey: String, @@ -20,7 +20,7 @@ pub struct ContactEntry { } pub async fn cmd_publish_note( - client: &SproutClient, + client: &BuzzClient, content: &str, reply_to: Option<&str>, ) -> Result<(), CliError> { @@ -30,7 +30,7 @@ pub async fn cmd_publish_note( let reply_id = reply_to.map(parse_event_id).transpose()?; - let builder = sprout_sdk::build_note(content, reply_id) + let builder = buzz_sdk::build_note(content, reply_id) .map_err(|e| CliError::Other(format!("build error: {e}")))?; let event = client.sign_event(builder)?; @@ -41,7 +41,7 @@ pub async fn cmd_publish_note( } pub async fn cmd_set_contact_list( - client: &SproutClient, + client: &BuzzClient, contacts_json: &str, ) -> Result<(), CliError> { let entries: Vec = serde_json::from_str(contacts_json) @@ -58,7 +58,7 @@ pub async fn cmd_set_contact_list( }) .collect(); - let builder = sprout_sdk::build_contact_list(&contacts) + let builder = buzz_sdk::build_contact_list(&contacts) .map_err(|e| CliError::Other(format!("build error: {e}")))?; let event = client.sign_event(builder)?; @@ -69,7 +69,7 @@ pub async fn cmd_set_contact_list( } /// Get a single event by ID via POST /query. -pub async fn cmd_get_event(client: &SproutClient, event_id: &str) -> Result<(), CliError> { +pub async fn cmd_get_event(client: &BuzzClient, event_id: &str) -> Result<(), CliError> { validate_hex64(event_id)?; let filter = serde_json::json!({ "ids": [event_id] @@ -81,7 +81,7 @@ pub async fn cmd_get_event(client: &SproutClient, event_id: &str) -> Result<(), /// Get user notes (kind:1) by author pubkey. pub async fn cmd_get_user_notes( - client: &SproutClient, + client: &BuzzClient, pubkey: &str, limit: Option, before: Option, @@ -112,7 +112,7 @@ pub async fn cmd_get_user_notes( } /// Get a user's contact list (kind:3) by pubkey. -pub async fn cmd_get_contact_list(client: &SproutClient, pubkey: &str) -> Result<(), CliError> { +pub async fn cmd_get_contact_list(client: &BuzzClient, pubkey: &str) -> Result<(), CliError> { validate_hex64(pubkey)?; let filter = serde_json::json!({ "kinds": [3], @@ -160,7 +160,7 @@ fn has_d_tag(tags: &[Tag]) -> bool { } pub async fn cmd_set_list( - client: &SproutClient, + client: &BuzzClient, kind: u16, tags_json: &str, content: &str, @@ -182,7 +182,7 @@ pub async fn cmd_set_list( } pub async fn cmd_get_list( - client: &SproutClient, + client: &BuzzClient, pubkey: &str, kind: u32, d_tag: Option<&str>, @@ -212,7 +212,7 @@ pub async fn cmd_get_list( // Dispatch // --------------------------------------------------------------------------- -pub async fn dispatch(cmd: crate::SocialCmd, client: &SproutClient) -> Result<(), CliError> { +pub async fn dispatch(cmd: crate::SocialCmd, client: &BuzzClient) -> Result<(), CliError> { use crate::SocialCmd; match cmd { SocialCmd::PublishNote { content, reply_to } => { diff --git a/crates/sprout-cli/src/commands/upload.rs b/crates/buzz-cli/src/commands/upload.rs similarity index 72% rename from crates/sprout-cli/src/commands/upload.rs rename to crates/buzz-cli/src/commands/upload.rs index 7385c71ee..ed6e5addb 100644 --- a/crates/sprout-cli/src/commands/upload.rs +++ b/crates/buzz-cli/src/commands/upload.rs @@ -1,7 +1,7 @@ -use crate::client::SproutClient; +use crate::client::BuzzClient; use crate::error::CliError; -pub async fn dispatch(cmd: crate::UploadCmd, client: &SproutClient) -> Result<(), CliError> { +pub async fn dispatch(cmd: crate::UploadCmd, client: &BuzzClient) -> Result<(), CliError> { match cmd { crate::UploadCmd::File { file } => { let desc = client.upload_file(&file).await?; diff --git a/crates/sprout-cli/src/commands/users.rs b/crates/buzz-cli/src/commands/users.rs similarity index 95% rename from crates/sprout-cli/src/commands/users.rs rename to crates/buzz-cli/src/commands/users.rs index 353df67c4..608436414 100644 --- a/crates/sprout-cli/src/commands/users.rs +++ b/crates/buzz-cli/src/commands/users.rs @@ -1,8 +1,8 @@ -use crate::client::{normalize_write_response, SproutClient}; +use crate::client::{normalize_write_response, BuzzClient}; use crate::error::CliError; use crate::validate::validate_hex64; -// TODO(phase-4): Replace raw nostr::EventBuilder usage in cmd_set_presence with sprout-sdk builder +// TODO(phase-4): Replace raw nostr::EventBuilder usage in cmd_set_presence with buzz-sdk builder /// Get user profiles (kind:0 metadata events). /// @@ -10,7 +10,7 @@ use crate::validate::validate_hex64; /// - 1+ pubkeys → query those users' profiles /// - --name "foo" → NIP-50 search on kind:0, then client-side filter pub async fn cmd_get_users( - client: &SproutClient, + client: &BuzzClient, pubkeys: &[String], name: Option<&str>, format: &crate::OutputFormat, @@ -79,7 +79,7 @@ pub async fn cmd_get_users( /// Search for users by display name via NIP-50 full-text search on kind:0 profiles. /// Returns [] if the relay does not implement NIP-50 search. async fn search_by_name( - client: &SproutClient, + client: &BuzzClient, query: &str, format: &crate::OutputFormat, ) -> Result<(), CliError> { @@ -148,7 +148,7 @@ async fn search_by_name( } pub async fn cmd_set_profile( - client: &SproutClient, + client: &BuzzClient, display_name: Option<&str>, avatar_url: Option<&str>, about: Option<&str>, @@ -197,7 +197,7 @@ pub async fn cmd_set_profile( .map(|s| s.to_string()) }); - let builder = sprout_sdk::build_profile( + let builder = buzz_sdk::build_profile( merged_name.as_deref(), None, // `name` field (username) — not exposed by CLI merged_picture.as_deref(), @@ -216,7 +216,7 @@ pub async fn cmd_set_profile( /// Fetch the current user's profile metadata via POST /query (kind:0). /// Returns the parsed content JSON object, or an empty object if no profile exists. async fn fetch_current_profile( - client: &SproutClient, + client: &BuzzClient, ) -> Result, CliError> { let my_pk = client.keys().public_key().to_hex(); let filter = serde_json::json!({ @@ -244,7 +244,7 @@ async fn fetch_current_profile( } /// Get presence status for users — query kind:40902 presence snapshot events. -pub async fn cmd_get_presence(client: &SproutClient, pubkeys_csv: &str) -> Result<(), CliError> { +pub async fn cmd_get_presence(client: &BuzzClient, pubkeys_csv: &str) -> Result<(), CliError> { let pubkeys: Vec<&str> = pubkeys_csv .split(',') .map(|s| s.trim()) @@ -281,8 +281,8 @@ pub async fn cmd_get_presence(client: &SproutClient, pubkeys_csv: &str) -> Resul /// Kind 20001 is ephemeral and only accepted via WebSocket connections. This /// method connects to the relay over WS, performs NIP-42 authentication, and /// publishes the event directly — bypassing the HTTP bridge. -pub async fn cmd_set_presence(client: &SproutClient, status: &str) -> Result<(), CliError> { - let builder = sprout_sdk::build_presence_update(status).map_err(crate::validate::sdk_err)?; +pub async fn cmd_set_presence(client: &BuzzClient, status: &str) -> Result<(), CliError> { + let builder = buzz_sdk::build_presence_update(status).map_err(crate::validate::sdk_err)?; let event = client.sign_event(builder)?; let resp = client.publish_ephemeral_event(event).await?; @@ -296,7 +296,7 @@ pub async fn cmd_set_presence(client: &SproutClient, status: &str) -> Result<(), pub async fn dispatch( cmd: crate::UsersCmd, - client: &SproutClient, + client: &BuzzClient, format: &crate::OutputFormat, ) -> Result<(), CliError> { use crate::UsersCmd; diff --git a/crates/sprout-cli/src/commands/workflows.rs b/crates/buzz-cli/src/commands/workflows.rs similarity index 88% rename from crates/sprout-cli/src/commands/workflows.rs rename to crates/buzz-cli/src/commands/workflows.rs index d014b5e5d..6175d8bfb 100644 --- a/crates/sprout-cli/src/commands/workflows.rs +++ b/crates/buzz-cli/src/commands/workflows.rs @@ -1,17 +1,17 @@ use sha2::{Digest, Sha256}; -use crate::client::{extract_d_tag, normalize_write_response, print_create_response, SproutClient}; +use crate::client::{extract_d_tag, normalize_write_response, print_create_response, BuzzClient}; use crate::error::CliError; use crate::validate::{parse_uuid, read_or_stdin, sdk_err, validate_uuid}; -// TODO(phase-4): Replace raw nostr::EventBuilder usage with sprout-sdk builder functions +// TODO(phase-4): Replace raw nostr::EventBuilder usage with buzz-sdk builder functions // --------------------------------------------------------------------------- // Read commands — POST /query // --------------------------------------------------------------------------- /// List workflows in a channel — query kind:30620 workflow definition events. -pub async fn cmd_list_workflows(client: &SproutClient, channel_id: &str) -> Result<(), CliError> { +pub async fn cmd_list_workflows(client: &BuzzClient, channel_id: &str) -> Result<(), CliError> { validate_uuid(channel_id)?; let filter = serde_json::json!({ "kinds": [30620], @@ -36,7 +36,7 @@ pub async fn cmd_list_workflows(client: &SproutClient, channel_id: &str) -> Resu } /// Get a single workflow definition. -pub async fn cmd_get_workflow(client: &SproutClient, workflow_id: &str) -> Result<(), CliError> { +pub async fn cmd_get_workflow(client: &BuzzClient, workflow_id: &str) -> Result<(), CliError> { validate_uuid(workflow_id)?; let filter = serde_json::json!({ "kinds": [30620], @@ -65,7 +65,7 @@ pub async fn cmd_get_workflow(client: &SproutClient, workflow_id: &str) -> Resul /// This command will return an empty array until the relay adds event emission /// or a dedicated REST endpoint for run history. pub async fn cmd_get_workflow_runs( - client: &SproutClient, + client: &BuzzClient, workflow_id: &str, limit: Option, ) -> Result<(), CliError> { @@ -101,7 +101,7 @@ pub async fn cmd_get_workflow_runs( /// Create a workflow — sign and submit a kind:30620 event. pub async fn cmd_create_workflow( - client: &SproutClient, + client: &BuzzClient, channel_id: &str, yaml: &str, ) -> Result<(), CliError> { @@ -109,7 +109,7 @@ pub async fn cmd_create_workflow( let yaml_definition = read_or_stdin(yaml)?; let workflow_id = uuid::Uuid::new_v4(); - let builder = sprout_sdk::build_workflow_def(channel_uuid, workflow_id, &yaml_definition) + let builder = buzz_sdk::build_workflow_def(channel_uuid, workflow_id, &yaml_definition) .map_err(sdk_err)?; let event = client.sign_event(builder)?; @@ -120,7 +120,7 @@ pub async fn cmd_create_workflow( /// Update a workflow — sign and submit an updated kind:30620 event with same d-tag. pub async fn cmd_update_workflow( - client: &SproutClient, + client: &BuzzClient, channel_id: &str, workflow_id: &str, yaml: &str, @@ -129,7 +129,7 @@ pub async fn cmd_update_workflow( let wf_uuid = parse_uuid(workflow_id)?; let yaml_definition = read_or_stdin(yaml)?; - let builder = sprout_sdk::build_workflow_update(channel_uuid, wf_uuid, &yaml_definition) + let builder = buzz_sdk::build_workflow_update(channel_uuid, wf_uuid, &yaml_definition) .map_err(sdk_err)?; let event = client.sign_event(builder)?; @@ -139,12 +139,12 @@ pub async fn cmd_update_workflow( } /// Delete a workflow — sign and submit a kind:5 deletion event. -pub async fn cmd_delete_workflow(client: &SproutClient, workflow_id: &str) -> Result<(), CliError> { +pub async fn cmd_delete_workflow(client: &BuzzClient, workflow_id: &str) -> Result<(), CliError> { let wf_uuid = parse_uuid(workflow_id)?; let keys = client.keys(); let builder = - sprout_sdk::build_workflow_delete(&keys.public_key().to_hex(), wf_uuid).map_err(sdk_err)?; + buzz_sdk::build_workflow_delete(&keys.public_key().to_hex(), wf_uuid).map_err(sdk_err)?; let event = client.sign_event(builder)?; let resp = client.submit_event(event).await?; @@ -157,7 +157,7 @@ pub async fn cmd_delete_workflow(client: &SproutClient, workflow_id: &str) -> Re /// When `inputs` is provided, it is parsed as a JSON object and used as the /// event content (MCP parity). When omitted, the event content is `{}`. pub async fn cmd_trigger_workflow( - client: &SproutClient, + client: &BuzzClient, workflow_id: &str, inputs: Option<&str>, ) -> Result<(), CliError> { @@ -176,7 +176,7 @@ pub async fn cmd_trigger_workflow( let tags = vec![Tag::parse(["d", &wf_uuid.to_string()]) .map_err(|e| CliError::Other(format!("tag error: {e}")))?]; let builder = EventBuilder::new( - Kind::Custom(sprout_sdk::kind::KIND_WORKFLOW_TRIGGER as u16), + Kind::Custom(buzz_sdk::kind::KIND_WORKFLOW_TRIGGER as u16), &content, ) .tags(tags); @@ -184,7 +184,7 @@ pub async fn cmd_trigger_workflow( let resp = client.submit_event(event).await?; println!("{}", normalize_write_response(&resp)); } else { - let builder = sprout_sdk::build_workflow_trigger(wf_uuid).map_err(sdk_err)?; + let builder = buzz_sdk::build_workflow_trigger(wf_uuid).map_err(sdk_err)?; let event = client.sign_event(builder)?; let resp = client.submit_event(event).await?; println!("{}", normalize_write_response(&resp)); @@ -194,7 +194,7 @@ pub async fn cmd_trigger_workflow( /// Approve or deny a workflow step — sign and submit a kind:46030 (grant) or 46031 (deny) event. pub async fn cmd_approve_step( - client: &SproutClient, + client: &BuzzClient, approval_token: &str, approved: bool, note: Option<&str>, @@ -206,7 +206,7 @@ pub async fn cmd_approve_step( // The relay expects d-tag = hex(SHA256(token)), not the raw token UUID. let token_hash = hex::encode(Sha256::digest(approval_token.as_bytes())); let builder = - sprout_sdk::build_workflow_approval(&token_hash, approved, content).map_err(sdk_err)?; + buzz_sdk::build_workflow_approval(&token_hash, approved, content).map_err(sdk_err)?; let event = client.sign_event(builder)?; let resp = client.submit_event(event).await?; @@ -218,7 +218,7 @@ pub async fn cmd_approve_step( // Dispatch // --------------------------------------------------------------------------- -pub async fn dispatch(cmd: crate::WorkflowsCmd, client: &SproutClient) -> Result<(), CliError> { +pub async fn dispatch(cmd: crate::WorkflowsCmd, client: &BuzzClient) -> Result<(), CliError> { use crate::WorkflowsCmd; match cmd { WorkflowsCmd::List { channel } => cmd_list_workflows(client, &channel).await, diff --git a/crates/sprout-cli/src/error.rs b/crates/buzz-cli/src/error.rs similarity index 91% rename from crates/sprout-cli/src/error.rs rename to crates/buzz-cli/src/error.rs index 465ccfb49..f4cdb3685 100644 --- a/crates/sprout-cli/src/error.rs +++ b/crates/buzz-cli/src/error.rs @@ -18,16 +18,16 @@ pub enum CliError { #[error("auth error: {0}")] Auth(String), - /// Nostr key error (NIP-98 signing in `sprout auth`) + /// Nostr key error (NIP-98 signing in `buzz auth`) #[error("key error: {0}")] Key(String), /// Relay accepted the event but reported it as superseded by a newer - /// head — used by `sprout mem` set/rm to surface NIP-33 LWW conflicts. + /// head — used by `buzz mem` set/rm to surface NIP-33 LWW conflicts. #[error("conflict: {0}")] Conflict(String), - /// Requested resource was absent or tombstoned (e.g. `sprout mem get` + /// Requested resource was absent or tombstoned (e.g. `buzz mem get` /// for a slug with no head). #[error("{0}")] NotFound(String), diff --git a/crates/sprout-cli/src/lib.rs b/crates/buzz-cli/src/lib.rs similarity index 93% rename from crates/sprout-cli/src/lib.rs rename to crates/buzz-cli/src/lib.rs index ff7694bcf..c9507dceb 100644 --- a/crates/sprout-cli/src/lib.rs +++ b/crates/buzz-cli/src/lib.rs @@ -4,18 +4,18 @@ mod error; mod validate; use clap::{Parser, Subcommand}; -use client::SproutClient; +use client::BuzzClient; use error::CliError; use nostr::Keys; -/// Run the Sprout CLI from raw arguments (including `argv[0]`). +/// Run the Buzz CLI from raw arguments (including `argv[0]`). /// /// Returns a process exit code (0 = success). /// /// # Example /// /// ```ignore -/// let code = sprout_cli::run_from_args(std::env::args()).await; +/// let code = buzz_cli::run_from_args(std::env::args()).await; /// std::process::exit(code); /// ``` pub async fn run_from_args(args: I) -> i32 @@ -51,15 +51,15 @@ where #[derive(Parser)] #[command( - name = "sprout", - about = "Sprout CLI — interact with a Sprout relay", + name = "buzz", + about = "Buzz CLI — interact with a Buzz relay", long_about = "\ -Sprout CLI — interact with a Sprout relay +Buzz CLI — interact with a Buzz relay Configuration (flags override env vars): - SPROUT_RELAY_URL Relay base URL [default: http://localhost:3000] - SPROUT_PRIVATE_KEY Nostr private key (hex or nsec) [required] - SPROUT_AUTH_TAG NIP-OA auth tag JSON [optional] + BUZZ_RELAY_URL Relay base URL [default: http://localhost:3000] + BUZZ_PRIVATE_KEY Nostr private key (hex or nsec) [required] + BUZZ_AUTH_TAG NIP-OA auth tag JSON [optional] The 'pack' subcommand runs locally and does not require a relay connection. @@ -67,20 +67,16 @@ Exit codes: 0=ok 1=bad input 2=relay/network error 3=auth error 4=other 5=w Errors are JSON on stderr: {\"error\": \"\", \"message\": \"\"}" )] struct Cli { - /// Relay URL (http:// or https://). Overrides SPROUT_RELAY_URL env var. - #[arg( - long, - env = "SPROUT_RELAY_URL", - default_value = "http://localhost:3000" - )] + /// Relay URL (http:// or https://). Overrides BUZZ_RELAY_URL env var. + #[arg(long, env = "BUZZ_RELAY_URL", default_value = "http://localhost:3000")] relay: String, /// Nostr private key (hex or nsec). This is the CLI's identity. - #[arg(long, env = "SPROUT_PRIVATE_KEY")] + #[arg(long, env = "BUZZ_PRIVATE_KEY")] private_key: Option, /// NIP-OA auth tag JSON (owner attestation). Injected into every signed event. - #[arg(long, env = "SPROUT_AUTH_TAG")] + #[arg(long, env = "BUZZ_AUTH_TAG")] auth_tag: Option, /// Output format: 'json' (default, full fields) or 'compact' (reduced fields). @@ -230,10 +226,10 @@ enum Cmd { pub enum MessagesCmd { /// Send a message to a channel #[command( - after_help = "Examples:\n sprout messages send --channel --content \"hello\"\n sprout messages send --channel --content \"@alice check this\"\n echo \"hello from stdin\" | sprout messages send --channel --content -" + after_help = "Examples:\n buzz messages send --channel --content \"hello\"\n buzz messages send --channel --content \"@alice check this\"\n echo \"hello from stdin\" | buzz messages send --channel --content -" )] Send { - /// Channel UUID (from 'sprout channels list') + /// Channel UUID (from 'buzz channels list') #[arg(long)] channel: String, /// Message text — supports @mentions and markdown. Use '-' to read from stdin. @@ -308,7 +304,7 @@ pub enum MessagesCmd { }, /// Retrieve messages from a channel #[command( - after_help = "Examples:\n sprout messages get --channel \n sprout messages get --channel --limit 50 --kinds 1,1984" + after_help = "Examples:\n buzz messages get --channel \n buzz messages get --channel --limit 50 --kinds 1,1984" )] Get { /// Channel UUID @@ -370,7 +366,7 @@ pub enum MessagesCmd { pub enum ChannelsCmd { /// List channels visible to the current identity #[command( - after_help = "Examples:\n sprout channels list\n sprout channels list --visibility open" + after_help = "Examples:\n buzz channels list\n buzz channels list --visibility open" )] List { /// Filter by visibility @@ -391,7 +387,7 @@ pub enum ChannelsCmd { }, /// Search channels by human-readable name #[command( - after_help = "Examples:\n sprout channels search --query composer\n sprout channels search --query sprout-chat-composer --exact\n sprout channels search --query design --include-archived" + after_help = "Examples:\n buzz channels search --query composer\n buzz channels search --query buzz-chat-composer --exact\n buzz channels search --query design --include-archived" )] Search { /// Search query (case-insensitive substring of channel name) @@ -409,7 +405,7 @@ pub enum ChannelsCmd { }, /// Create a new channel #[command( - after_help = "Examples:\n sprout channels create --name general --type stream --visibility open\n sprout channels create --name design --type forum --visibility open --description \"Design discussions\"" + after_help = "Examples:\n buzz channels create --name general --type stream --visibility open\n buzz channels create --name design --type forum --visibility open --description \"Design discussions\"" )] Create { /// Channel name @@ -755,7 +751,7 @@ pub enum WorkflowsCmd { }, /// Trigger a workflow run #[command( - after_help = "Examples:\n sprout workflows trigger --workflow \n sprout workflows trigger --workflow --inputs '{\"key\":\"value\"}'" + after_help = "Examples:\n buzz workflows trigger --workflow \n buzz workflows trigger --workflow --inputs '{\"key\":\"value\"}'" )] Trigger { /// Workflow UUID @@ -776,7 +772,7 @@ pub enum WorkflowsCmd { }, /// Approve or deny a workflow step #[command( - after_help = "Examples:\n sprout workflows approve --token \n sprout workflows approve --token --approved false --note \"needs revision\"" + after_help = "Examples:\n buzz workflows approve --token \n buzz workflows approve --token --approved false --note \"needs revision\"" )] Approve { /// The approval token UUID (from the approval request) @@ -905,7 +901,7 @@ pub enum NotesCmd { /// title is carried forward when `--title` is omitted, and `--title ""` /// explicitly clears it. #[command( - after_help = "Examples:\n echo '# Hello' | sprout notes set --name hello --title 'Hello' --content -\n sprout notes set --name hello --tag onboarding --content - < draft.md" + after_help = "Examples:\n echo '# Hello' | buzz notes set --name hello --title 'Hello' --content -\n buzz notes set --name hello --tag onboarding --content - < draft.md" )] Set { /// Slug — becomes the `d` tag. `[a-z0-9._-]{1,80}`. @@ -1040,12 +1036,12 @@ pub enum UploadCmd { // Mem subcommands (NIP-AE) // --------------------------------------------------------------------------- -/// Subcommands for `sprout mem`. +/// Subcommands for `buzz mem`. #[derive(Subcommand)] pub enum MemCmd { /// List non-tombstoned memory entries Ls { - /// Owner pubkey (hex). Overrides SPROUT_AUTH_TAG. + /// Owner pubkey (hex). Overrides BUZZ_AUTH_TAG. #[arg(long)] owner: Option, /// Agent pubkey (hex) to read as this key's owner. @@ -1096,8 +1092,8 @@ pub enum MemCmd { #[arg(long)] patch_file: Option, /// sha256 hex digest (lowercase) of the value the patch was generated - /// against. Hashes the exact UTF-8 bytes returned by `sprout mem get`, - /// not normalized lines. Run `sprout mem hash ` to capture this + /// against. Hashes the exact UTF-8 bytes returned by `buzz mem get`, + /// not normalized lines. Run `buzz mem hash ` to capture this /// before editing. #[arg(long)] base_hash: Option, @@ -1127,7 +1123,7 @@ pub enum MemCmd { // Pack subcommands (local, no relay connection needed) // --------------------------------------------------------------------------- -/// Subcommands for `sprout pack`. +/// Subcommands for `buzz pack`. #[derive(Subcommand)] pub enum PackCmd { /// Validate a persona pack directory @@ -1160,19 +1156,19 @@ async fn run(cli: Cli) -> Result<(), CliError> { // Auth: private key is required for all relay operations. // The keypair IS the identity — no tokens, no other auth. let private_key_str = cli.private_key.ok_or_else(|| { - CliError::Auth("SPROUT_PRIVATE_KEY is required (use --private-key or set env var)".into()) + CliError::Auth("BUZZ_PRIVATE_KEY is required (use --private-key or set env var)".into()) })?; let keys = Keys::parse(&private_key_str) - .map_err(|e| CliError::Key(format!("invalid SPROUT_PRIVATE_KEY: {e}")))?; + .map_err(|e| CliError::Key(format!("invalid BUZZ_PRIVATE_KEY: {e}")))?; // NIP-OA: parse and verify the auth tag if provided. let (auth_tag, auth_tag_json) = match cli.auth_tag { Some(ref json) if !json.is_empty() => { - let tag = sprout_sdk::nip_oa::parse_auth_tag(json) - .map_err(|e| CliError::Auth(format!("SPROUT_AUTH_TAG is malformed: {e}")))?; - sprout_sdk::nip_oa::verify_auth_tag(json, &keys.public_key()).map_err(|e| { + let tag = buzz_sdk::nip_oa::parse_auth_tag(json) + .map_err(|e| CliError::Auth(format!("BUZZ_AUTH_TAG is malformed: {e}")))?; + buzz_sdk::nip_oa::verify_auth_tag(json, &keys.public_key()).map_err(|e| { CliError::Auth(format!( - "SPROUT_AUTH_TAG verification failed for pubkey {}: {e}", + "BUZZ_AUTH_TAG verification failed for pubkey {}: {e}", keys.public_key().to_hex() )) })?; @@ -1181,7 +1177,7 @@ async fn run(cli: Cli) -> Result<(), CliError> { _ => (None, None), }; - let client = SproutClient::new(relay_url, keys, auth_tag, auth_tag_json)?; + let client = BuzzClient::new(relay_url, keys, auth_tag, auth_tag_json)?; match cli.command { Cmd::Messages(sub) => commands::messages::dispatch(sub, &client, &cli.format).await, diff --git a/crates/buzz-cli/src/main.rs b/crates/buzz-cli/src/main.rs new file mode 100644 index 000000000..ff337776b --- /dev/null +++ b/crates/buzz-cli/src/main.rs @@ -0,0 +1,4 @@ +#[tokio::main] +async fn main() { + std::process::exit(buzz_cli::run_from_args(std::env::args()).await); +} diff --git a/crates/sprout-cli/src/validate.rs b/crates/buzz-cli/src/validate.rs similarity index 98% rename from crates/sprout-cli/src/validate.rs rename to crates/buzz-cli/src/validate.rs index 4882e340a..1a73d3770 100644 --- a/crates/sprout-cli/src/validate.rs +++ b/crates/buzz-cli/src/validate.rs @@ -157,9 +157,9 @@ pub fn infer_language(file_path: &str) -> Option { /// Map `SdkError` to the appropriate `CliError` variant. /// /// `InvalidInput` is a user error (exit 1), everything else is internal (exit 4). -pub fn sdk_err(e: sprout_sdk::SdkError) -> CliError { +pub fn sdk_err(e: buzz_sdk::SdkError) -> CliError { match e { - sprout_sdk::SdkError::InvalidInput(msg) => CliError::Usage(msg), + buzz_sdk::SdkError::InvalidInput(msg) => CliError::Usage(msg), other => CliError::Other(other.to_string()), } } @@ -373,7 +373,7 @@ mod tests { } // Note: `extract_at_names`, `extract_at_mentions_with_known`, `merge_mentions`, - // and `normalize_mention_pubkeys` live in `sprout_sdk::mentions` and are tested there. + // and `normalize_mention_pubkeys` live in `buzz_sdk::mentions` and are tested there. // --- parse_event_id --- diff --git a/crates/sprout-core/Cargo.toml b/crates/buzz-core/Cargo.toml similarity index 95% rename from crates/sprout-core/Cargo.toml rename to crates/buzz-core/Cargo.toml index 60eea1c66..489df360d 100644 --- a/crates/sprout-core/Cargo.toml +++ b/crates/buzz-core/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "sprout-core" +name = "buzz-core" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true repository.workspace = true -description = "Core types, event verification, and filter matching for Sprout" +description = "Core types, event verification, and filter matching for Buzz" [features] test-utils = [] diff --git a/crates/sprout-core/src/channel.rs b/crates/buzz-core/src/channel.rs similarity index 98% rename from crates/sprout-core/src/channel.rs rename to crates/buzz-core/src/channel.rs index e2caffc2a..3cd1721ac 100644 --- a/crates/sprout-core/src/channel.rs +++ b/crates/buzz-core/src/channel.rs @@ -1,6 +1,6 @@ //! Channel and membership enums shared across crates. //! -//! These live in `sprout-core` (zero I/O deps) so both the SDK (client-side) +//! These live in `buzz-core` (zero I/O deps) so both the SDK (client-side) //! and the DB layer (server-side) can use the same types without pulling in //! sqlx/tokio. diff --git a/crates/sprout-core/src/engram.rs b/crates/buzz-core/src/engram.rs similarity index 99% rename from crates/sprout-core/src/engram.rs rename to crates/buzz-core/src/engram.rs index dda710fff..e6a39291b 100644 --- a/crates/sprout-core/src/engram.rs +++ b/crates/buzz-core/src/engram.rs @@ -4,7 +4,7 @@ //! not talk to relays or filesystems. Callers wire it to a transport and a //! key source. //! -//! Shared by `sprout-cli` (`sprout mem …`) and `sprout-acp` (core injection +//! Shared by `buzz-cli` (`buzz mem …`) and `buzz-acp` (core injection //! at session creation). use hmac::digest::KeyInit; @@ -598,7 +598,7 @@ pub fn monotonic_created_at(now: u64, prior_head: Option) -> u64 { } } -/// Wire representation for `sprout mem ls`: one entry per non-tombstone +/// Wire representation for `buzz mem ls`: one entry per non-tombstone /// memory slug (`core` is excluded by the listing procedure). #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Listing { diff --git a/crates/sprout-core/src/error.rs b/crates/buzz-core/src/error.rs similarity index 100% rename from crates/sprout-core/src/error.rs rename to crates/buzz-core/src/error.rs diff --git a/crates/sprout-core/src/event.rs b/crates/buzz-core/src/event.rs similarity index 97% rename from crates/sprout-core/src/event.rs rename to crates/buzz-core/src/event.rs index e95496d1b..def6221dc 100644 --- a/crates/sprout-core/src/event.rs +++ b/crates/buzz-core/src/event.rs @@ -56,7 +56,7 @@ mod tests { fn make_event() -> nostr::Event { let keys = Keys::generate(); - EventBuilder::new(Kind::TextNote, "hello sprout") + EventBuilder::new(Kind::TextNote, "hello buzz") .tags([]) .sign_with_keys(&keys) .expect("sign") diff --git a/crates/sprout-core/src/filter.rs b/crates/buzz-core/src/filter.rs similarity index 100% rename from crates/sprout-core/src/filter.rs rename to crates/buzz-core/src/filter.rs diff --git a/crates/sprout-core/src/git_perms.rs b/crates/buzz-core/src/git_perms.rs similarity index 96% rename from crates/sprout-core/src/git_perms.rs rename to crates/buzz-core/src/git_perms.rs index 865462e36..a8b3da1a3 100644 --- a/crates/sprout-core/src/git_perms.rs +++ b/crates/buzz-core/src/git_perms.rs @@ -1,7 +1,7 @@ //! Git permission types — ref patterns, protection rules, and policy evaluation inputs. //! -//! This module defines the core data types for the Sprout git permission system. -//! The permission model: channel role = repo role; `sprout-protect` tags on +//! This module defines the core data types for the Buzz git permission system. +//! The permission model: channel role = repo role; `buzz-protect` tags on //! kind:30617 add constraints that apply to everyone (including the owner). //! //! # Architecture @@ -17,7 +17,7 @@ use std::fmt; // ── Limits (DoS prevention for untrusted kind:30617 input) ─────────────────── -/// Maximum number of `sprout-protect` tags per repo. +/// Maximum number of `buzz-protect` tags per repo. pub const MAX_PROTECTION_RULES: usize = 50; /// Maximum character length of a ref pattern. pub const MAX_PATTERN_LENGTH: usize = 256; @@ -244,9 +244,9 @@ pub struct RefUpdate { // ── Protection Rules ───────────────────────────────────────────────────────── -/// A single protection rule parsed from a `sprout-protect` tag on kind:30617. +/// A single protection rule parsed from a `buzz-protect` tag on kind:30617. /// -/// Format: `["sprout-protect", "", "", ...]` +/// Format: `["buzz-protect", "", "", ...]` /// Multiple rules per tag are allowed. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ProtectionRule { @@ -267,7 +267,7 @@ pub struct ProtectionRule { pub require_patch: bool, } -/// Errors from parsing a `sprout-protect` tag. +/// Errors from parsing a `buzz-protect` tag. #[derive(Debug, Clone, PartialEq, Eq)] pub enum RuleParseError { /// Tag has fewer than 2 values (need at least pattern + one rule). @@ -285,7 +285,7 @@ pub enum RuleParseError { impl fmt::Display for RuleParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::TooFewValues => write!(f, "sprout-protect tag needs pattern + at least one rule"), + Self::TooFewValues => write!(f, "buzz-protect tag needs pattern + at least one rule"), Self::TooManyRules => write!(f, "exceeds max {MAX_PROTECTION_RULES} rules per repo"), Self::InvalidPattern(e) => write!(f, "invalid pattern: {e}"), Self::UnknownRule(r) => write!(f, "unknown rule: {r:?}"), @@ -296,18 +296,18 @@ impl fmt::Display for RuleParseError { impl std::error::Error for RuleParseError {} -/// Parse a single `sprout-protect` tag into a `ProtectionRule`. +/// Parse a single `buzz-protect` tag into a `ProtectionRule`. /// -/// Tag format: `["sprout-protect", "", "", "", ...]` -/// The first element ("sprout-protect") should already be stripped — pass +/// Tag format: `["buzz-protect", "", "", "", ...]` +/// The first element ("buzz-protect") should already be stripped — pass /// the remaining values starting with the pattern. -/// Parse a single `sprout-protect` tag (simple API, discards unknown rules). +/// Parse a single `buzz-protect` tag (simple API, discards unknown rules). pub fn parse_protection_tag(values: &[&str]) -> Result { let (rule, _unknowns) = parse_protection_tag_with_warnings(values)?; Ok(rule) } -/// Parse a single `sprout-protect` tag, returning unknown rules for logging. +/// Parse a single `buzz-protect` tag, returning unknown rules for logging. pub fn parse_protection_tag_with_warnings( values: &[&str], ) -> Result<(ProtectionRule, Vec), RuleParseError> { @@ -378,9 +378,9 @@ pub struct ParsedProtection { pub unknown_rules: Vec, } -/// Parse all `sprout-protect` tags from a kind:30617 event's tag list. +/// Parse all `buzz-protect` tags from a kind:30617 event's tag list. /// -/// Returns an error if any `sprout-protect` tag is structurally malformed. +/// Returns an error if any `buzz-protect` tag is structurally malformed. /// Unknown rule strings are skipped but reported in `ParsedProtection::unknown_rules` /// so callers can log warnings (helps catch typos while maintaining forward-compat). /// Enforces the per-repo rule count limit. @@ -389,7 +389,7 @@ pub fn parse_protection_tags(tags: &[Vec]) -> Result= MAX_PROTECTION_RULES { @@ -409,7 +409,7 @@ pub fn parse_protection_tags(tags: &[Vec]) -> Result MemberRole { let is_branch = ref_name.starts_with("refs/heads/"); let is_tag = ref_name.starts_with("refs/tags/"); diff --git a/crates/sprout-core/src/kind.rs b/crates/buzz-core/src/kind.rs similarity index 97% rename from crates/sprout-core/src/kind.rs rename to crates/buzz-core/src/kind.rs index c3812852e..381cf8c9d 100644 --- a/crates/sprout-core/src/kind.rs +++ b/crates/buzz-core/src/kind.rs @@ -1,6 +1,6 @@ -//! Sprout V2 kind number registry. +//! Buzz V2 kind number registry. //! -//! This module is the authoritative source for Sprout kind numbers. +//! This module is the authoritative source for Buzz kind numbers. //! All constants are `u32` — NIP-01 specifies kind as an unsigned integer, //! and u32 covers the full range without truncation. @@ -50,7 +50,7 @@ pub const KIND_BOOKMARK_SET: u32 = 30003; /// `required_scope_for_kind`), and the generic NIP-33 replace path keeps only the /// latest per `(pubkey, d_tag)`. pub const KIND_EMOJI_SET: u32 = 30030; -/// NIP-01: Channel metadata (replaceable). Not used by Sprout today. +/// NIP-01: Channel metadata (replaceable). Not used by Buzz today. pub const KIND_CHANNEL_METADATA: u32 = 41; /// NIP-09: Event deletion request. pub const KIND_DELETION: u32 = 5; @@ -80,7 +80,7 @@ pub const KIND_BLOSSOM_AUTH: u32 = 24242; /// NIP-98: HTTP auth event (used in nip98.rs, not stored). pub const KIND_HTTP_AUTH: u32 = 27235; -// NEW: Sprout command kinds (Pure Nostr plan) +// NEW: Buzz command kinds (Pure Nostr plan) /// Agent metadata + owner reference (replaceable, agent-authored). pub const KIND_AGENT_PROFILE: u32 = 10100; @@ -155,7 +155,7 @@ pub const KIND_NIP29_GROUP_ROLES: u32 = 39003; /// Workflow definition (parameterized replaceable, d=workflow_uuid). pub const KIND_WORKFLOW_DEF: u32 = 30620; -/// Mesh-LLM relay status (relay-signed, parameterized replaceable, d=sprout-relay-mesh). +/// Mesh-LLM relay status (relay-signed, parameterized replaceable, d=buzz-relay-mesh). /// /// Published only by the relay. Carries a sanitized, member-readable projection /// of mesh status, including EndpointAddr dial pointers for serving nodes. @@ -247,7 +247,7 @@ pub const KIND_DM_HIDE: u32 = 41012; pub const KIND_DM_CREATED: u32 = 41001; // Agent job protocol (43000–43999) -// Not using NIP-90 kinds (5000–6999) — Sprout requires auth chains (depth ≤ 3, breadth ≤ 10). +// Not using NIP-90 kinds (5000–6999) — Buzz requires auth chains (depth ≤ 3, breadth ≤ 10). /// An agent job was requested. pub const KIND_JOB_REQUEST: u32 = 43001; /// An agent accepted a job request. @@ -509,7 +509,7 @@ pub const fn is_identity_archive_request_kind(kind: u32) -> bool { matches!(kind, KIND_IA_ARCHIVE_REQUEST | KIND_IA_UNARCHIVE_REQUEST) } -/// Returns `true` if `kind` is a Sprout command kind that requires transactional execution. +/// Returns `true` if `kind` is a Buzz command kind that requires transactional execution. pub const fn is_command_kind(kind: u32) -> bool { matches!( kind, @@ -542,7 +542,7 @@ pub fn event_kind_u32(event: &nostr::Event) -> u32 { } /// Extract the kind from a nostr Event as i32 (for Postgres INT columns). -/// Safe: all Sprout kinds fit in i32 (max 65535 < i32::MAX). +/// Safe: all Buzz kinds fit in i32 (max 65535 < i32::MAX). pub fn event_kind_i32(event: &nostr::Event) -> i32 { event.kind.as_u16() as i32 } @@ -563,7 +563,7 @@ const _: () = assert!( && KIND_GIT_REPO_STATE <= PARAM_REPLACEABLE_KIND_MAX ); -// Compile-time: all Sprout kind constants fit in nostr's u16-backed Kind. +// Compile-time: all Buzz kind constants fit in nostr's u16-backed Kind. const _: () = assert!(KIND_AUTH <= u16::MAX as u32); const _: () = assert!(KIND_CANVAS <= u16::MAX as u32); const _: () = assert!(KIND_HUDDLE_GUIDELINES <= u16::MAX as u32); diff --git a/crates/sprout-core/src/lib.rs b/crates/buzz-core/src/lib.rs similarity index 91% rename from crates/sprout-core/src/lib.rs rename to crates/buzz-core/src/lib.rs index b9e0d220f..8ee3d0315 100644 --- a/crates/sprout-core/src/lib.rs +++ b/crates/buzz-core/src/lib.rs @@ -1,9 +1,9 @@ #![deny(unsafe_code)] #![warn(missing_docs)] -//! `sprout-core` — zero-I/O foundation types for the Sprout relay. +//! `buzz-core` — zero-I/O foundation types for the Buzz relay. //! //! Provides [`StoredEvent`], filter matching, kind constants, and event -//! verification. All other Sprout crates depend on this one. +//! verification. All other Buzz crates depend on this one. /// Channel and membership enums shared across crates. pub mod channel; @@ -18,7 +18,7 @@ pub mod event; pub mod filter; /// Git permission types — ref patterns, protection rules, policy evaluation. pub mod git_perms; -/// Sprout kind number registry — custom event type constants. +/// Buzz kind number registry — custom event type constants. pub mod kind; /// Network utilities — SSRF-safe IP classification. pub mod network; diff --git a/crates/sprout-core/src/network.rs b/crates/buzz-core/src/network.rs similarity index 99% rename from crates/sprout-core/src/network.rs rename to crates/buzz-core/src/network.rs index 4e773af1b..1e52d4c10 100644 --- a/crates/sprout-core/src/network.rs +++ b/crates/buzz-core/src/network.rs @@ -1,4 +1,4 @@ -//! Network utility functions for Sprout. +//! Network utility functions for Buzz. //! //! Provides shared helpers used across crates for SSRF protection and //! IP address classification. diff --git a/crates/sprout-core/src/observer.rs b/crates/buzz-core/src/observer.rs similarity index 98% rename from crates/sprout-core/src/observer.rs rename to crates/buzz-core/src/observer.rs index 67a3713b6..f2b981188 100644 --- a/crates/sprout-core/src/observer.rs +++ b/crates/buzz-core/src/observer.rs @@ -1,7 +1,7 @@ //! Agent observer frame helpers. //! //! Observer frames are transient, owner-scoped agent telemetry/control messages. -//! They use a Sprout ephemeral event kind and carry NIP-44 encrypted JSON in the +//! They use a Buzz ephemeral event kind and carry NIP-44 encrypted JSON in the //! event content so relays can route frames without reading ACP internals. use nostr::{nips::nip44, Event, Keys, PublicKey}; diff --git a/crates/sprout-core/src/pairing/NIP-AB.md b/crates/buzz-core/src/pairing/NIP-AB.md similarity index 99% rename from crates/sprout-core/src/pairing/NIP-AB.md rename to crates/buzz-core/src/pairing/NIP-AB.md index 1f92a9f58..fc8f69245 100644 --- a/crates/sprout-core/src/pairing/NIP-AB.md +++ b/crates/buzz-core/src/pairing/NIP-AB.md @@ -672,7 +672,7 @@ Those behaviors remain normative in this document and in the Rust implementation Run the proof with: ```bash -tamarin-prover --prove crates/sprout-core/src/pairing/NIP-AB.spthy +tamarin-prover --prove crates/buzz-core/src/pairing/NIP-AB.spthy ``` ## Cryptographic Primitives @@ -746,7 +746,7 @@ transcript_hash = HKDF-SHA256(IKM=transcript, salt=session_secret, info="nostr-p d662818ff8911fc60a2d025f8b8b4756107104e85888dd202d28db5ca2cf28d3 ``` -Implementations MUST validate against these vectors. They can be reproduced with `sprout-pair test-vectors`. +Implementations MUST validate against these vectors. They can be reproduced with `buzz-pair test-vectors`. A future external vector file (`nip-ab.vectors.json`) with a sha256 checksum committed in this document is planned. When published, it will include categorized intermediate-value vectors for each derivation step and negative/invalid test cases. The sha256 checksum will be the canonical commitment; implementations MUST verify against the checksum before using the file. diff --git a/crates/sprout-core/src/pairing/NIP-AB.spthy b/crates/buzz-core/src/pairing/NIP-AB.spthy similarity index 100% rename from crates/sprout-core/src/pairing/NIP-AB.spthy rename to crates/buzz-core/src/pairing/NIP-AB.spthy diff --git a/crates/sprout-core/src/pairing/crypto.rs b/crates/buzz-core/src/pairing/crypto.rs similarity index 99% rename from crates/sprout-core/src/pairing/crypto.rs rename to crates/buzz-core/src/pairing/crypto.rs index a006bc8a6..ccb91b9b0 100644 --- a/crates/sprout-core/src/pairing/crypto.rs +++ b/crates/buzz-core/src/pairing/crypto.rs @@ -113,7 +113,7 @@ pub fn derive_transcript_hash( /// /// # Examples /// ``` -/// use sprout_core::pairing::crypto::format_sas; +/// use buzz_core::pairing::crypto::format_sas; /// assert_eq!(format_sas(291), "000291"); /// assert_eq!(format_sas(47291), "047291"); /// assert_eq!(format_sas(999999), "999999"); diff --git a/crates/sprout-core/src/pairing/mod.rs b/crates/buzz-core/src/pairing/mod.rs similarity index 100% rename from crates/sprout-core/src/pairing/mod.rs rename to crates/buzz-core/src/pairing/mod.rs diff --git a/crates/sprout-core/src/pairing/qr.rs b/crates/buzz-core/src/pairing/qr.rs similarity index 99% rename from crates/sprout-core/src/pairing/qr.rs rename to crates/buzz-core/src/pairing/qr.rs index 0975adf58..b56c65af9 100644 --- a/crates/sprout-core/src/pairing/qr.rs +++ b/crates/buzz-core/src/pairing/qr.rs @@ -67,7 +67,7 @@ impl Drop for QrPayload { /// # Example /// /// ``` -/// use sprout_core::pairing::qr::{QrPayload, encode_qr}; +/// use buzz_core::pairing::qr::{QrPayload, encode_qr}; /// use nostr::Keys; /// /// let keys = Keys::generate(); diff --git a/crates/sprout-core/src/pairing/session.rs b/crates/buzz-core/src/pairing/session.rs similarity index 100% rename from crates/sprout-core/src/pairing/session.rs rename to crates/buzz-core/src/pairing/session.rs diff --git a/crates/sprout-core/src/pairing/types.rs b/crates/buzz-core/src/pairing/types.rs similarity index 100% rename from crates/sprout-core/src/pairing/types.rs rename to crates/buzz-core/src/pairing/types.rs diff --git a/crates/sprout-core/src/presence.rs b/crates/buzz-core/src/presence.rs similarity index 100% rename from crates/sprout-core/src/presence.rs rename to crates/buzz-core/src/presence.rs diff --git a/crates/sprout-core/src/verification.rs b/crates/buzz-core/src/verification.rs similarity index 100% rename from crates/sprout-core/src/verification.rs rename to crates/buzz-core/src/verification.rs diff --git a/crates/sprout-db/Cargo.toml b/crates/buzz-db/Cargo.toml similarity index 82% rename from crates/sprout-db/Cargo.toml rename to crates/buzz-db/Cargo.toml index 313159e56..23ea3c9d8 100644 --- a/crates/sprout-db/Cargo.toml +++ b/crates/buzz-db/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "sprout-db" +name = "buzz-db" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true repository.workspace = true -description = "Postgres event store and data access layer for Sprout" +description = "Postgres event store and data access layer for Buzz" [dependencies] -sprout-core = { workspace = true } +buzz-core = { workspace = true } sqlx = { workspace = true } tokio = { workspace = true } serde = { workspace = true } diff --git a/crates/sprout-db/src/api_token.rs b/crates/buzz-db/src/api_token.rs similarity index 100% rename from crates/sprout-db/src/api_token.rs rename to crates/buzz-db/src/api_token.rs diff --git a/crates/sprout-db/src/archived_identities.rs b/crates/buzz-db/src/archived_identities.rs similarity index 100% rename from crates/sprout-db/src/archived_identities.rs rename to crates/buzz-db/src/archived_identities.rs diff --git a/crates/sprout-db/src/channel.rs b/crates/buzz-db/src/channel.rs similarity index 99% rename from crates/sprout-db/src/channel.rs rename to crates/buzz-db/src/channel.rs index 28aea0675..f87646ccf 100644 --- a/crates/sprout-db/src/channel.rs +++ b/crates/buzz-db/src/channel.rs @@ -10,10 +10,10 @@ use uuid::Uuid; use crate::error::{DbError, Result}; -// Re-export the canonical enum definitions from sprout-core. +// Re-export the canonical enum definitions from buzz-core. // These live in core (zero I/O deps) so the SDK can share them // without pulling in sqlx/tokio. -pub use sprout_core::channel::{ChannelType, ChannelVisibility, MemberRole}; +pub use buzz_core::channel::{ChannelType, ChannelVisibility, MemberRole}; /// A channel row as returned from the database. #[derive(Debug, Clone)] @@ -1225,7 +1225,7 @@ mod tests { use crate::user::{ensure_user, set_agent_owner}; use nostr::Keys; - const TEST_DB_URL: &str = "postgres://sprout:sprout_dev@localhost:5432/sprout"; + const TEST_DB_URL: &str = "postgres://buzz:buzz_dev@localhost:5432/buzz"; async fn setup_pool() -> PgPool { PgPool::connect(TEST_DB_URL) diff --git a/crates/sprout-db/src/dm.rs b/crates/buzz-db/src/dm.rs similarity index 100% rename from crates/sprout-db/src/dm.rs rename to crates/buzz-db/src/dm.rs diff --git a/crates/sprout-db/src/error.rs b/crates/buzz-db/src/error.rs similarity index 100% rename from crates/sprout-db/src/error.rs rename to crates/buzz-db/src/error.rs diff --git a/crates/sprout-db/src/event.rs b/crates/buzz-db/src/event.rs similarity index 99% rename from crates/sprout-db/src/event.rs rename to crates/buzz-db/src/event.rs index e897d6035..f1558a99f 100644 --- a/crates/sprout-db/src/event.rs +++ b/crates/buzz-db/src/event.rs @@ -9,8 +9,8 @@ use nostr::Event; use sqlx::{PgPool, QueryBuilder, Row}; use uuid::Uuid; -use sprout_core::kind::{event_kind_i32, is_ephemeral, is_parameterized_replaceable, KIND_AUTH}; -use sprout_core::StoredEvent; +use buzz_core::kind::{event_kind_i32, is_ephemeral, is_parameterized_replaceable, KIND_AUTH}; +use buzz_core::StoredEvent; use crate::error::{DbError, Result}; @@ -118,7 +118,7 @@ pub async fn insert_event( let pubkey_bytes = event.pubkey.to_bytes(); let sig_bytes = event.sig.serialize(); let tags_json = serde_json::to_value(&event.tags)?; - // Cast chain: nostr Kind (u16) → i32 (Postgres INT column). Safe: all Sprout kinds fit in i32. + // Cast chain: nostr Kind (u16) → i32 (Postgres INT column). Safe: all Buzz kinds fit in i32. let kind_i32 = event_kind_i32(event); let created_at_secs = event.created_at.as_secs() as i64; let created_at = DateTime::from_timestamp(created_at_secs, 0) diff --git a/crates/sprout-db/src/feed.rs b/crates/buzz-db/src/feed.rs similarity index 97% rename from crates/sprout-db/src/feed.rs rename to crates/buzz-db/src/feed.rs index f90e95df1..99cb7f9a6 100644 --- a/crates/sprout-db/src/feed.rs +++ b/crates/buzz-db/src/feed.rs @@ -32,12 +32,12 @@ use sqlx::postgres::PgRow; use sqlx::{PgPool, QueryBuilder}; use uuid::Uuid; -use sprout_core::kind::{ +use buzz_core::kind::{ KIND_FORUM_COMMENT, KIND_FORUM_POST, KIND_JOB_PROGRESS, KIND_JOB_REQUEST, KIND_JOB_RESULT, KIND_STREAM_MESSAGE, KIND_STREAM_MESSAGE_V2, KIND_STREAM_REMINDER, KIND_WORKFLOW_APPROVAL_REQUESTED, }; -use sprout_core::StoredEvent; +use buzz_core::StoredEvent; use crate::error::Result; use crate::event::row_to_stored_event; @@ -266,7 +266,7 @@ mod tests { #[test] fn mentions_query_includes_stream_message_kind() { - use sprout_core::kind::{ + use buzz_core::kind::{ KIND_FORUM_COMMENT, KIND_FORUM_POST, KIND_STREAM_MESSAGE, KIND_STREAM_MESSAGE_V2, }; let mention_kinds: &[u32] = &[ @@ -296,7 +296,7 @@ mod tests { #[test] fn needs_action_query_includes_approval_and_reminder_kinds() { - use sprout_core::kind::{KIND_STREAM_REMINDER, KIND_WORKFLOW_APPROVAL_REQUESTED}; + use buzz_core::kind::{KIND_STREAM_REMINDER, KIND_WORKFLOW_APPROVAL_REQUESTED}; let needs_action_kinds: &[u32] = &[KIND_WORKFLOW_APPROVAL_REQUESTED, KIND_STREAM_REMINDER]; assert!( @@ -311,7 +311,7 @@ mod tests { #[test] fn activity_query_includes_agent_job_kinds() { - use sprout_core::kind::{ + use buzz_core::kind::{ KIND_FORUM_POST, KIND_JOB_PROGRESS, KIND_JOB_REQUEST, KIND_JOB_RESULT, KIND_STREAM_MESSAGE, KIND_STREAM_MESSAGE_V2, }; @@ -348,7 +348,7 @@ mod tests { #[test] fn activity_query_excludes_workflow_execution_kinds() { - use sprout_core::kind::{ + use buzz_core::kind::{ KIND_FORUM_POST, KIND_JOB_PROGRESS, KIND_JOB_REQUEST, KIND_JOB_RESULT, KIND_STREAM_MESSAGE, KIND_STREAM_MESSAGE_V2, }; @@ -361,7 +361,7 @@ mod tests { KIND_JOB_RESULT, ]; - use sprout_core::kind::{KIND_WORKFLOW_APPROVAL_DENIED, KIND_WORKFLOW_TRIGGERED}; + use buzz_core::kind::{KIND_WORKFLOW_APPROVAL_DENIED, KIND_WORKFLOW_TRIGGERED}; for kind in KIND_WORKFLOW_TRIGGERED..=KIND_WORKFLOW_APPROVAL_DENIED { assert!( !activity_kinds.contains(&kind), @@ -372,7 +372,7 @@ mod tests { #[test] fn needs_action_kinds_do_not_overlap_with_activity_kinds() { - use sprout_core::kind::{ + use buzz_core::kind::{ KIND_FORUM_POST, KIND_JOB_PROGRESS, KIND_JOB_REQUEST, KIND_JOB_RESULT, KIND_STREAM_MESSAGE, KIND_STREAM_MESSAGE_V2, KIND_STREAM_REMINDER, KIND_WORKFLOW_APPROVAL_REQUESTED, diff --git a/crates/sprout-db/src/lib.rs b/crates/buzz-db/src/lib.rs similarity index 99% rename from crates/sprout-db/src/lib.rs rename to crates/buzz-db/src/lib.rs index 5743dde94..84ffc5c67 100644 --- a/crates/sprout-db/src/lib.rs +++ b/crates/buzz-db/src/lib.rs @@ -1,6 +1,6 @@ #![deny(unsafe_code)] #![warn(missing_docs)] -//! sprout-db — Postgres event store for Sprout. +//! buzz-db — Postgres event store for Buzz. //! //! ## Design invariants //! - AUTH events (kind 22242) are never stored — they carry bearer tokens. @@ -45,7 +45,7 @@ use sqlx::{PgPool, QueryBuilder, Row}; use std::time::Duration; use uuid::Uuid; -use sprout_core::StoredEvent; +use buzz_core::StoredEvent; /// Extract p-tag mentions from an event and insert into the `event_mentions` table. /// @@ -150,7 +150,7 @@ impl Default for DbConfig { /// At 20 main + 5 audit = 25/pod, four relay pods fit within the PG limit. fn default() -> Self { Self { - database_url: "postgres://sprout:sprout_dev@localhost:5432/sprout".to_string(), + database_url: "postgres://buzz:buzz_dev@localhost:5432/buzz".to_string(), max_connections: 20, min_connections: 2, acquire_timeout_secs: 3, @@ -1495,7 +1495,7 @@ impl Db { event: &nostr::Event, channel_id: Option, ) -> Result<(StoredEvent, bool)> { - let kind_i32 = sprout_core::kind::event_kind_i32(event); + let kind_i32 = buzz_core::kind::event_kind_i32(event); let pubkey_bytes = event.pubkey.to_bytes(); let created_at_secs = event.created_at.as_secs() as i64; let created_at = chrono::DateTime::from_timestamp(created_at_secs, 0) @@ -1649,7 +1649,7 @@ impl Db { d_tag: &str, channel_id: Option, ) -> Result<(StoredEvent, bool)> { - let kind_i32 = sprout_core::kind::event_kind_i32(event); + let kind_i32 = buzz_core::kind::event_kind_i32(event); let pubkey_bytes = event.pubkey.to_bytes(); let created_at_secs = event.created_at.as_secs() as i64; let created_at = chrono::DateTime::from_timestamp(created_at_secs, 0) diff --git a/crates/sprout-db/src/partition.rs b/crates/buzz-db/src/partition.rs similarity index 100% rename from crates/sprout-db/src/partition.rs rename to crates/buzz-db/src/partition.rs diff --git a/crates/sprout-db/src/reaction.rs b/crates/buzz-db/src/reaction.rs similarity index 100% rename from crates/sprout-db/src/reaction.rs rename to crates/buzz-db/src/reaction.rs diff --git a/crates/sprout-db/src/relay_members.rs b/crates/buzz-db/src/relay_members.rs similarity index 100% rename from crates/sprout-db/src/relay_members.rs rename to crates/buzz-db/src/relay_members.rs diff --git a/crates/sprout-db/src/thread.rs b/crates/buzz-db/src/thread.rs similarity index 100% rename from crates/sprout-db/src/thread.rs rename to crates/buzz-db/src/thread.rs diff --git a/crates/sprout-db/src/user.rs b/crates/buzz-db/src/user.rs similarity index 99% rename from crates/sprout-db/src/user.rs rename to crates/buzz-db/src/user.rs index 1982ec6a6..d93a4e152 100644 --- a/crates/sprout-db/src/user.rs +++ b/crates/buzz-db/src/user.rs @@ -372,7 +372,7 @@ mod tests { use crate::Db; use nostr::Keys; - const TEST_DB_URL: &str = "postgres://sprout:sprout_dev@localhost:5432/sprout"; + const TEST_DB_URL: &str = "postgres://buzz:buzz_dev@localhost:5432/buzz"; async fn setup_db() -> Db { let pool = PgPool::connect(TEST_DB_URL) diff --git a/crates/sprout-db/src/workflow.rs b/crates/buzz-db/src/workflow.rs similarity index 99% rename from crates/sprout-db/src/workflow.rs rename to crates/buzz-db/src/workflow.rs index 5c0b408fb..993dbf19d 100644 --- a/crates/sprout-db/src/workflow.rs +++ b/crates/buzz-db/src/workflow.rs @@ -27,7 +27,7 @@ pub const LIST_MAX_LIMIT: i64 = 1000; /// SHA-256 hash of a raw approval token. Returns the 32-byte digest. /// /// Approval tokens are stored hashed so that a DB read does not expose -/// the raw token (same pattern as API tokens in sprout-auth). +/// the raw token (same pattern as API tokens in buzz-auth). fn hash_approval_token(token: &str) -> Vec { Sha256::digest(token.as_bytes()).to_vec() } diff --git a/crates/sprout-dev-mcp/Cargo.toml b/crates/buzz-dev-mcp/Cargo.toml similarity index 89% rename from crates/sprout-dev-mcp/Cargo.toml rename to crates/buzz-dev-mcp/Cargo.toml index c935c9f24..dd0872b35 100644 --- a/crates/sprout-dev-mcp/Cargo.toml +++ b/crates/buzz-dev-mcp/Cargo.toml @@ -1,20 +1,20 @@ [package] -name = "sprout-dev-mcp" +name = "buzz-dev-mcp" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true [lib] -name = "sprout_dev_mcp" +name = "buzz_dev_mcp" path = "src/lib.rs" [[bin]] -name = "sprout-dev-mcp" +name = "buzz-dev-mcp" path = "src/main.rs" [dependencies] -sprout-cli = { path = "../sprout-cli" } +buzz-cli = { path = "../buzz-cli" } git-credential-nostr = { path = "../git-credential-nostr" } git-sign-nostr = { path = "../git-sign-nostr" } nostr = { workspace = true } @@ -30,7 +30,7 @@ tempfile = "3" ignore = "0.4.25" tracing = { workspace = true } tracing-subscriber = { workspace = true } -# view_image tool: HTTP fetch (workspace reqwest is already used by sprout-cli; +# view_image tool: HTTP fetch (workspace reqwest is already used by buzz-cli; # adding it here is essentially free), base64 encoding, and decode/resize. reqwest = { workspace = true } base64 = "0.22" diff --git a/crates/sprout-dev-mcp/src/lib.rs b/crates/buzz-dev-mcp/src/lib.rs similarity index 96% rename from crates/sprout-dev-mcp/src/lib.rs rename to crates/buzz-dev-mcp/src/lib.rs index b44b7b6ff..edc9b9fb7 100644 --- a/crates/sprout-dev-mcp/src/lib.rs +++ b/crates/buzz-dev-mcp/src/lib.rs @@ -38,7 +38,7 @@ impl DevMcp { #[tool( name = "shell", - description = "Run a bash command. Ephemeral process per call. Output tail-truncated to ~8KB for the LLM; full output (first 10MB) saved to artifact file. timeout_ms defaults to 120000 (2 min) if omitted; capped at 600000 (10 min). For long-running commands (git push with hooks, cargo build, test suites), use 300000+. On PATH: rg (prefer over grep; flags: -n -i -l -g -C --files), tree (flags: -d ; shows line counts), and sprout (Sprout relay CLI — run sprout --help for commands)." + description = "Run a bash command. Ephemeral process per call. Output tail-truncated to ~8KB for the LLM; full output (first 10MB) saved to artifact file. timeout_ms defaults to 120000 (2 min) if omitted; capped at 600000 (10 min). For long-running commands (git push with hooks, cargo build, test suites), use 300000+. On PATH: rg (prefer over grep; flags: -n -i -l -g -C --files), tree (flags: -d ; shows line counts), and buzz (Buzz relay CLI — run buzz --help for commands)." )] async fn shell( &self, @@ -127,7 +127,7 @@ impl ServerHandler for DevMcp { fn get_info(&self) -> ServerInfo { ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) .with_server_info(rmcp::model::Implementation::new( - "sprout-dev-mcp", + "buzz-dev-mcp", env!("CARGO_PKG_VERSION"), )) .with_instructions(self.state.bootstrap_instructions.clone()) @@ -160,9 +160,9 @@ pub fn run() -> Result<(), Box> { } async fn async_main(cmd: String) -> Result<(), Box> { - // sprout CLI needs tokio (async HTTP client). - if cmd == "sprout" { - std::process::exit(sprout_cli::run_from_args(std::env::args()).await); + // buzz CLI needs tokio (async HTTP client). + if cmd == "buzz" { + std::process::exit(buzz_cli::run_from_args(std::env::args()).await); } // MCP server mode — safe to init tracing now. diff --git a/crates/sprout-dev-mcp/src/main.rs b/crates/buzz-dev-mcp/src/main.rs similarity index 68% rename from crates/sprout-dev-mcp/src/main.rs rename to crates/buzz-dev-mcp/src/main.rs index 919bd8d27..e2883ab69 100644 --- a/crates/sprout-dev-mcp/src/main.rs +++ b/crates/buzz-dev-mcp/src/main.rs @@ -1,3 +1,3 @@ fn main() -> Result<(), Box> { - sprout_dev_mcp::run() + buzz_dev_mcp::run() } diff --git a/crates/sprout-dev-mcp/src/paths.rs b/crates/buzz-dev-mcp/src/paths.rs similarity index 100% rename from crates/sprout-dev-mcp/src/paths.rs rename to crates/buzz-dev-mcp/src/paths.rs diff --git a/crates/sprout-dev-mcp/src/read_file.rs b/crates/buzz-dev-mcp/src/read_file.rs similarity index 100% rename from crates/sprout-dev-mcp/src/read_file.rs rename to crates/buzz-dev-mcp/src/read_file.rs diff --git a/crates/sprout-dev-mcp/src/rg.rs b/crates/buzz-dev-mcp/src/rg.rs similarity index 100% rename from crates/sprout-dev-mcp/src/rg.rs rename to crates/buzz-dev-mcp/src/rg.rs diff --git a/crates/sprout-dev-mcp/src/shell.rs b/crates/buzz-dev-mcp/src/shell.rs similarity index 97% rename from crates/sprout-dev-mcp/src/shell.rs rename to crates/buzz-dev-mcp/src/shell.rs index 7581646fa..2a48d2cfd 100644 --- a/crates/sprout-dev-mcp/src/shell.rs +++ b/crates/buzz-dev-mcp/src/shell.rs @@ -35,7 +35,7 @@ pub struct SharedState { impl SharedState { pub fn new(cwd: PathBuf, shim: Shim) -> std::io::Result { let session_dir = tempfile::Builder::new() - .prefix("sprout-dev-mcp-session-") + .prefix("buzz-dev-mcp-session-") .tempdir()?; let bootstrap_instructions = build_bootstrap(&cwd); Ok(Self { @@ -60,18 +60,17 @@ impl SharedState { fn build_bootstrap(cwd: &Path) -> String { let stack = detect_stack(cwd); - let sprout_hint = if std::env::var("SPROUT_RELAY_URL").is_ok() - && std::env::var("SPROUT_PRIVATE_KEY").is_ok() - { - "\nSprout relay configured. Run `sprout --help` to see available commands.\n" - } else { - "" - }; + let buzz_hint = + if std::env::var("BUZZ_RELAY_URL").is_ok() && std::env::var("BUZZ_PRIVATE_KEY").is_ok() { + "\nBuzz relay configured. Run `buzz --help` to see available commands.\n" + } else { + "" + }; format!( "Working directory: {}\n\ Detected stack: {}\n\ Pass `workdir` per call rather than `cd`.\n\ - {sprout_hint}", + {buzz_hint}", cwd.display(), stack, ) @@ -149,7 +148,7 @@ pub async fn run( cmd.current_dir(&workdir); cmd.env("PATH", &state.shim.path_env); // NOSTR_PRIVATE_KEY is already removed from this process's env (shim.rs). - // SPROUT_PRIVATE_KEY is intentionally inherited — the sprout CLI needs it. + // BUZZ_PRIVATE_KEY is intentionally inherited — the buzz CLI needs it. for (k, v) in &state.shim.git_env { cmd.env(k, v); } diff --git a/crates/sprout-dev-mcp/src/shim.rs b/crates/buzz-dev-mcp/src/shim.rs similarity index 88% rename from crates/sprout-dev-mcp/src/shim.rs rename to crates/buzz-dev-mcp/src/shim.rs index 9c3c0aef2..0ecef5061 100644 --- a/crates/sprout-dev-mcp/src/shim.rs +++ b/crates/buzz-dev-mcp/src/shim.rs @@ -11,8 +11,8 @@ use zeroize::Zeroize; /// builds ephemeral `GIT_CONFIG_*` env vars, then removes the env var /// 3. Prepends the shim dir to PATH /// -/// Shell children receive `path_env`, `git_env`, and `SPROUT_PRIVATE_KEY` (for -/// the sprout CLI). `NOSTR_PRIVATE_KEY` is removed from the process env after +/// Shell children receive `path_env`, `git_env`, and `BUZZ_PRIVATE_KEY` (for +/// the buzz CLI). `NOSTR_PRIVATE_KEY` is removed from the process env after /// the keyfile is written — git helpers read from the keyfile only. /// Cleaned up on drop (TempDir). pub struct Shim { @@ -23,9 +23,7 @@ pub struct Shim { impl Shim { pub fn install() -> std::io::Result { - let dir = tempfile::Builder::new() - .prefix("sprout-dev-mcp-") - .tempdir()?; + let dir = tempfile::Builder::new().prefix("buzz-dev-mcp-").tempdir()?; set_owner_only(dir.path())?; let self_exe = std::env::current_exe()?; @@ -34,7 +32,7 @@ impl Shim { for name in [ "rg", "tree", - "sprout", + "buzz", "git-credential-nostr", "git-sign-nostr", ] { @@ -93,7 +91,7 @@ fn write_keyfile(shim_dir: &Path, raw: &str) -> Option { Ok(k) => k, Err(e) => { eprintln!( - "sprout-dev-mcp: warning: NOSTR_PRIVATE_KEY is set but invalid ({e}); \ + "buzz-dev-mcp: warning: NOSTR_PRIVATE_KEY is set but invalid ({e}); \ git auth/signing will be disabled" ); return None; @@ -108,14 +106,16 @@ fn write_keyfile(shim_dir: &Path, raw: &str) -> Option { let keyfile = shim_dir.join(".nostr-key"); if write_keyfile_atomic(&keyfile, raw.as_bytes()).is_err() { eprintln!( - "sprout-dev-mcp: warning: failed to write nostr keyfile; git auth/signing disabled" + "buzz-dev-mcp: warning: failed to write nostr keyfile; git auth/signing disabled" ); return None; } let keyfile_path = match keyfile.to_str() { Some(s) => s.to_owned(), None => { - eprintln!("sprout-dev-mcp: warning: tempdir path is not valid UTF-8; git auth/signing disabled"); + eprintln!( + "buzz-dev-mcp: warning: tempdir path is not valid UTF-8; git auth/signing disabled" + ); return None; } }; @@ -148,10 +148,10 @@ fn write_keyfile_atomic(path: &Path, data: &[u8]) -> std::io::Result<()> { } /// Derive a NIP-05-style email from the pubkey and relay URL. -/// Format: `@` (e.g., `ab12...cd@relay.sprout.dev`). -/// Falls back to `@sprout` if no relay URL is configured. +/// Format: `@` (e.g., `ab12...cd@relay.buzz.dev`). +/// Falls back to `@buzz` if no relay URL is configured. fn derive_git_email(pubkey_hex: &str) -> String { - let host = std::env::var("SPROUT_RELAY_URL") + let host = std::env::var("BUZZ_RELAY_URL") .ok() .and_then(|url| { // Strip scheme, port, and trailing paths @@ -166,13 +166,13 @@ fn derive_git_email(pubkey_hex: &str) -> String { Some(host_port.split(':').next().unwrap_or(host_port).to_owned()) }) .filter(|h| !h.is_empty() && !h.starts_with("localhost") && !h.starts_with("127.")) - .unwrap_or_else(|| "sprout".to_owned()); + .unwrap_or_else(|| "buzz".to_owned()); format!("{pubkey_hex}@{host}") } /// Build GIT_CONFIG_COUNT/KEY/VALUE env vars for ephemeral nostr git config. /// Composes with any existing GIT_CONFIG_COUNT in the environment. When launched -/// via sprout-agent (which clears env), the base is always 0 — composition only +/// via buzz-agent (which clears env), the base is always 0 — composition only /// matters when dev-mcp is run directly with pre-existing GIT_CONFIG vars. fn build_git_env(info: &KeyInfo) -> Vec<(String, String)> { let email = derive_git_email(&info.pubkey_hex); @@ -180,11 +180,11 @@ fn build_git_env(info: &KeyInfo) -> Vec<(String, String)> { // Identity — npub as display name, NIP-05-style email ("user.name", info.npub.clone()), ("user.email", email), - // Nostr credential helper is additive — it silently declines non-Sprout + // Nostr credential helper is additive — it silently declines non-Buzz // remotes (exits 0, no credential), so git falls through to system // helpers (osxkeychain, store, etc.) for GitHub/GitLab/etc. ("credential.helper", "nostr".into()), - // Required: Sprout relay verifies NIP-98 against the full repo-root URL. + // Required: Buzz relay verifies NIP-98 against the full repo-root URL. // Without useHttpPath, git only passes the host and auth is rejected. ("credential.useHttpPath", "true".into()), ("nostr.keyfile", info.keyfile_path.clone()), diff --git a/crates/sprout-dev-mcp/src/str_replace.rs b/crates/buzz-dev-mcp/src/str_replace.rs similarity index 100% rename from crates/sprout-dev-mcp/src/str_replace.rs rename to crates/buzz-dev-mcp/src/str_replace.rs diff --git a/crates/sprout-dev-mcp/src/todo.rs b/crates/buzz-dev-mcp/src/todo.rs similarity index 100% rename from crates/sprout-dev-mcp/src/todo.rs rename to crates/buzz-dev-mcp/src/todo.rs diff --git a/crates/sprout-dev-mcp/src/tree.rs b/crates/buzz-dev-mcp/src/tree.rs similarity index 100% rename from crates/sprout-dev-mcp/src/tree.rs rename to crates/buzz-dev-mcp/src/tree.rs diff --git a/crates/sprout-dev-mcp/src/view_image.rs b/crates/buzz-dev-mcp/src/view_image.rs similarity index 100% rename from crates/sprout-dev-mcp/src/view_image.rs rename to crates/buzz-dev-mcp/src/view_image.rs diff --git a/crates/sprout-media/Cargo.toml b/crates/buzz-media/Cargo.toml similarity index 93% rename from crates/sprout-media/Cargo.toml rename to crates/buzz-media/Cargo.toml index 6edbb536a..b5bdba5cb 100644 --- a/crates/sprout-media/Cargo.toml +++ b/crates/buzz-media/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "sprout-media" +name = "buzz-media" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true repository.workspace = true -description = "Media storage, validation, and thumbnail generation for Sprout" +description = "Media storage, validation, and thumbnail generation for Buzz" [dependencies] -sprout-core = { workspace = true } +buzz-core = { workspace = true } nostr = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/sprout-media/src/auth.rs b/crates/buzz-media/src/auth.rs similarity index 98% rename from crates/sprout-media/src/auth.rs rename to crates/buzz-media/src/auth.rs index 91b7555e0..7798844b6 100644 --- a/crates/sprout-media/src/auth.rs +++ b/crates/buzz-media/src/auth.rs @@ -147,7 +147,7 @@ mod tests { Tag::parse(["x", sha256]).unwrap(), Tag::parse(["expiration", &exp_str]).unwrap(), ]; - EventBuilder::new(Kind::from(24242), "Upload sprout-media") + EventBuilder::new(Kind::from(24242), "Upload buzz-media") .tags(tags) .sign_with_keys(keys) .unwrap() @@ -241,7 +241,7 @@ mod tests { .unwrap(); // Should fail — server tag present but doesn't match our domain assert!(matches!( - verify_blossom_upload_auth(&event, &sha256, Some("sprout.example.com"), 600), + verify_blossom_upload_auth(&event, &sha256, Some("buzz.example.com"), 600), Err(MediaError::ServerMismatch) )); // Should pass when our domain matches diff --git a/crates/sprout-media/src/config.rs b/crates/buzz-media/src/config.rs similarity index 96% rename from crates/sprout-media/src/config.rs rename to crates/buzz-media/src/config.rs index 89c772764..78b963bcd 100644 --- a/crates/sprout-media/src/config.rs +++ b/crates/buzz-media/src/config.rs @@ -33,7 +33,7 @@ pub struct MediaConfig { pub public_base_url: String, /// Server authority for BUD-11 server tag validation. /// Format: `host` for default ports, `host:port` for non-default ports. - /// Examples: "sprout.example.com", "localhost:3000", "relay.example.com:8080". + /// Examples: "buzz.example.com", "localhost:3000", "relay.example.com:8080". /// If None, auth events carrying `server` tags are rejected (fail-closed). /// Must match the authority the desktop signer derives from the relay URL. pub server_domain: Option, diff --git a/crates/sprout-media/src/error.rs b/crates/buzz-media/src/error.rs similarity index 100% rename from crates/sprout-media/src/error.rs rename to crates/buzz-media/src/error.rs diff --git a/crates/sprout-media/src/lib.rs b/crates/buzz-media/src/lib.rs similarity index 83% rename from crates/sprout-media/src/lib.rs rename to crates/buzz-media/src/lib.rs index c3187d089..f382c21f8 100644 --- a/crates/sprout-media/src/lib.rs +++ b/crates/buzz-media/src/lib.rs @@ -1,6 +1,6 @@ -//! Media storage, validation, and thumbnail generation for Sprout. +//! Media storage, validation, and thumbnail generation for Buzz. //! -//! Library crate — no Axum dependency for handlers. Axum handlers live in `sprout-relay`. +//! Library crate — no Axum dependency for handlers. Axum handlers live in `buzz-relay`. pub mod auth; pub mod config; diff --git a/crates/sprout-media/src/storage.rs b/crates/buzz-media/src/storage.rs similarity index 100% rename from crates/sprout-media/src/storage.rs rename to crates/buzz-media/src/storage.rs diff --git a/crates/sprout-media/src/thumbnail.rs b/crates/buzz-media/src/thumbnail.rs similarity index 100% rename from crates/sprout-media/src/thumbnail.rs rename to crates/buzz-media/src/thumbnail.rs diff --git a/crates/sprout-media/src/types.rs b/crates/buzz-media/src/types.rs similarity index 100% rename from crates/sprout-media/src/types.rs rename to crates/buzz-media/src/types.rs diff --git a/crates/sprout-media/src/upload.rs b/crates/buzz-media/src/upload.rs similarity index 100% rename from crates/sprout-media/src/upload.rs rename to crates/buzz-media/src/upload.rs diff --git a/crates/sprout-media/src/validation.rs b/crates/buzz-media/src/validation.rs similarity index 100% rename from crates/sprout-media/src/validation.rs rename to crates/buzz-media/src/validation.rs diff --git a/crates/sprout-pair-relay/Cargo.toml b/crates/buzz-pair-relay/Cargo.toml similarity index 92% rename from crates/sprout-pair-relay/Cargo.toml rename to crates/buzz-pair-relay/Cargo.toml index 40ccd42df..740f13da1 100644 --- a/crates/sprout-pair-relay/Cargo.toml +++ b/crates/buzz-pair-relay/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "sprout-pair-relay" +name = "buzz-pair-relay" description = "Ephemeral sidecar relay for NIP-AB device pairing handshakes" version.workspace = true edition.workspace = true @@ -8,11 +8,11 @@ license.workspace = true repository.workspace = true [lib] -name = "sprout_pair_relay" +name = "buzz_pair_relay" path = "src/lib.rs" [[bin]] -name = "sprout-pair-relay" +name = "buzz-pair-relay" path = "src/main.rs" [dependencies] diff --git a/crates/sprout-pair-relay/src/lib.rs b/crates/buzz-pair-relay/src/lib.rs similarity index 99% rename from crates/sprout-pair-relay/src/lib.rs rename to crates/buzz-pair-relay/src/lib.rs index 5238b4e68..e6ca76d45 100644 --- a/crates/sprout-pair-relay/src/lib.rs +++ b/crates/buzz-pair-relay/src/lib.rs @@ -1019,7 +1019,7 @@ async fn http_service( pub async fn run_server(listener: TcpListener, relay: Arc) { let addr = listener.local_addr().ok(); if let Some(a) = addr { - eprintln!("sprout-pair-relay listening on {a}"); + eprintln!("buzz-pair-relay listening on {a}"); } loop { let (tcp, _peer) = match listener.accept().await { diff --git a/crates/sprout-pair-relay/src/main.rs b/crates/buzz-pair-relay/src/main.rs similarity index 91% rename from crates/sprout-pair-relay/src/main.rs rename to crates/buzz-pair-relay/src/main.rs index f64c041ed..1e81756e3 100644 --- a/crates/sprout-pair-relay/src/main.rs +++ b/crates/buzz-pair-relay/src/main.rs @@ -1,7 +1,7 @@ use std::net::SocketAddr; use std::sync::Arc; -use sprout_pair_relay::{run_server, Relay}; +use buzz_pair_relay::{run_server, Relay}; use tokio::net::TcpListener; #[tokio::main] diff --git a/crates/sprout-pair-relay/tests/integration.rs b/crates/buzz-pair-relay/tests/integration.rs similarity index 99% rename from crates/sprout-pair-relay/tests/integration.rs rename to crates/buzz-pair-relay/tests/integration.rs index 6c108c952..85b29636f 100644 --- a/crates/sprout-pair-relay/tests/integration.rs +++ b/crates/buzz-pair-relay/tests/integration.rs @@ -1,4 +1,4 @@ -//! Integration tests for sprout-pair-relay. +//! Integration tests for buzz-pair-relay. //! //! Each test spins up a relay on a random port (`:0`), connects one or more //! WebSocket clients, and exercises the observable protocol surface. @@ -11,7 +11,7 @@ use serde_json::{json, Value}; use tokio::net::TcpListener; use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream}; -use sprout_pair_relay::{run_server, Relay}; +use buzz_pair_relay::{run_server, Relay}; // ── Crypto imports (for real event signing) ─────────────────────────────────── diff --git a/crates/sprout-pairing-cli/Cargo.toml b/crates/buzz-pairing-cli/Cargo.toml similarity index 87% rename from crates/sprout-pairing-cli/Cargo.toml rename to crates/buzz-pairing-cli/Cargo.toml index 77a1daf12..58fefe32d 100644 --- a/crates/sprout-pairing-cli/Cargo.toml +++ b/crates/buzz-pairing-cli/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "sprout-pairing-cli" +name = "buzz-pairing-cli" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -8,11 +8,11 @@ repository.workspace = true description = "CLI tool for NIP-AB device pairing interop testing" [[bin]] -name = "sprout-pair" +name = "buzz-pair" path = "src/main.rs" [dependencies] -sprout-core = { workspace = true } +buzz-core = { workspace = true } nostr = { workspace = true } tokio = { workspace = true } tokio-tungstenite = { workspace = true } diff --git a/crates/sprout-pairing-cli/README.md b/crates/buzz-pairing-cli/README.md similarity index 81% rename from crates/sprout-pairing-cli/README.md rename to crates/buzz-pairing-cli/README.md index 0ff147dbb..4f9fd1e53 100644 --- a/crates/sprout-pairing-cli/README.md +++ b/crates/buzz-pairing-cli/README.md @@ -1,17 +1,17 @@ -# sprout-pair +# buzz-pair -CLI tool for testing the [NIP-AB device pairing protocol](../sprout-core/src/pairing/NIP-AB.md) end-to-end. Exercises the full protocol over a live Nostr relay — designed for interop testing and NIP submission, not production use. +CLI tool for testing the [NIP-AB device pairing protocol](../buzz-core/src/pairing/NIP-AB.md) end-to-end. Exercises the full protocol over a live Nostr relay — designed for interop testing and NIP submission, not production use. ## Quick Start ```bash -cargo build --release -p sprout-pairing-cli +cargo build --release -p buzz-pairing-cli # Terminal 1 — source (holds the secret) -./target/release/sprout-pair source --relay wss://relay.damus.io +./target/release/buzz-pair source --relay wss://relay.damus.io # Terminal 2 — target (receives the secret) -./target/release/sprout-pair target --show-secret +./target/release/buzz-pair target --show-secret # paste the QR URI from terminal 1 when prompted ``` @@ -24,7 +24,7 @@ Both sides display a 6-digit SAS code. Confirm they match on each side, and the Acts as the device holding the secret. Generates an ephemeral keypair and session secret, displays a `nostrpair://` QR URI, waits for a target to connect, performs SAS verification, and sends the payload. ``` -sprout-pair source --relay [--nsec ] +buzz-pair source --relay [--nsec ] ``` - `--relay` — WebSocket relay URL (default: `wss://relay.damus.io`) @@ -35,7 +35,7 @@ sprout-pair source --relay [--nsec ] Acts as the receiving device. Reads a `nostrpair://` URI from stdin, connects to the relay encoded in the URI, sends an offer, verifies SAS, and receives the payload. ``` -sprout-pair target [--relay ] [--show-secret] +buzz-pair target [--relay ] [--show-secret] ``` - `--relay` — Override the relay URL from the QR code @@ -46,24 +46,24 @@ sprout-pair target [--relay ] [--show-secret] Prints all derived cryptographic values from the NIP-AB spec's fixed test keys. Useful for verifying implementations against the spec. ``` -sprout-pair test-vectors +buzz-pair test-vectors ``` -## Testing Against a Local Sprout Relay +## Testing Against a Local Buzz Relay -The CLI supports NIP-42 authentication, so it works with Sprout relays out of the box. +The CLI supports NIP-42 authentication, so it works with Buzz relays out of the box. ### Prerequisites - Docker running (for Postgres, Redis, etc.) -- Sprout relay built: `cargo build --release -p sprout-relay` +- Buzz relay built: `cargo build --release -p buzz-relay` ### Start the relay ```bash just setup # Docker services + schema cargo build --release --workspace -screen -dmS relay bash -c "./target/release/sprout-relay 2>&1 | tee /tmp/sprout-relay.log" +screen -dmS relay bash -c "./target/release/buzz-relay 2>&1 | tee /tmp/buzz-relay.log" sleep 3 && curl -s http://localhost:3000/health # → "ok" ``` @@ -91,10 +91,10 @@ This spawns source and target as PTY-driven subprocesses, feeds the QR URI betwe ```bash # Terminal 1 -./target/release/sprout-pair source --relay ws://localhost:3000 +./target/release/buzz-pair source --relay ws://localhost:3000 # Terminal 2 -./target/release/sprout-pair target --show-secret +./target/release/buzz-pair target --show-secret # paste the nostrpair:// URI, confirm SAS on both sides ``` diff --git a/crates/sprout-pairing-cli/src/main.rs b/crates/buzz-pairing-cli/src/main.rs similarity index 97% rename from crates/sprout-pairing-cli/src/main.rs rename to crates/buzz-pairing-cli/src/main.rs index 73b6cb9c6..6f266d07f 100644 --- a/crates/sprout-pairing-cli/src/main.rs +++ b/crates/buzz-pairing-cli/src/main.rs @@ -1,11 +1,11 @@ -//! `sprout-pair` — NIP-AB device pairing interop testing CLI. +//! `buzz-pair` — NIP-AB device pairing interop testing CLI. //! //! # Usage //! //! ```text -//! sprout-pair source --relay wss://relay.example.com [--nsec nsec1...] -//! sprout-pair target [--relay wss://relay.example.com] -//! sprout-pair test-vectors +//! buzz-pair source --relay wss://relay.example.com [--nsec nsec1...] +//! buzz-pair target [--relay wss://relay.example.com] +//! buzz-pair test-vectors //! ``` //! //! The `source` subcommand acts as the secret-holding device; `target` acts @@ -15,17 +15,17 @@ use std::io::{self, BufRead, Write}; use std::time::Duration; -use clap::{Parser, Subcommand}; -use futures_util::{SinkExt, StreamExt}; -use nostr::{Event, EventBuilder, Keys, RelayUrl, SecretKey, ToBech32}; -use sprout_core::kind::KIND_PAIRING; -use sprout_core::pairing::session::PairingSession; -use sprout_core::pairing::{ +use buzz_core::kind::KIND_PAIRING; +use buzz_core::pairing::session::PairingSession; +use buzz_core::pairing::{ crypto::{derive_sas, derive_session_id, derive_transcript_hash, format_sas}, qr::{decode_qr, encode_qr}, types::PayloadType, PairingError, }; +use clap::{Parser, Subcommand}; +use futures_util::{SinkExt, StreamExt}; +use nostr::{Event, EventBuilder, Keys, RelayUrl, SecretKey, ToBech32}; use tokio::time::timeout; use tokio_tungstenite::{connect_async, tungstenite::Message}; use zeroize::Zeroizing; @@ -34,7 +34,7 @@ use zeroize::Zeroizing; #[derive(Parser)] #[command( - name = "sprout-pair", + name = "buzz-pair", about = "NIP-AB device pairing interop testing tool", long_about = "Test the NIP-AB device pairing protocol end-to-end.\n\ Run 'source' on one terminal and 'target' on another." @@ -170,7 +170,7 @@ async fn cmd_source(relay_url: String, nsec: Option) -> Result<(), CliEr if !confirmed { // Send abort and exit. if let Some(abort_event) = - session.abort(sprout_core::pairing::types::AbortReason::SasMismatch)? + session.abort(buzz_core::pairing::types::AbortReason::SasMismatch)? { publish_event(&mut write, &abort_event).await?; } @@ -282,7 +282,7 @@ async fn cmd_target(relay_override: Option, show_secret: bool) -> Result // NIP-AB §Step 3: target MUST send abort with reason // "sas_mismatch" on transcript hash mismatch. if let Ok(Some(abort_event)) = - session.abort(sprout_core::pairing::types::AbortReason::SasMismatch) + session.abort(buzz_core::pairing::types::AbortReason::SasMismatch) { let _ = publish_event(&mut write, &abort_event).await; } @@ -301,7 +301,7 @@ async fn cmd_target(relay_override: Option, show_secret: bool) -> Result let confirmed = read_yes_no()?; if !confirmed { if let Some(abort_event) = - session.abort(sprout_core::pairing::types::AbortReason::SasMismatch)? + session.abort(buzz_core::pairing::types::AbortReason::SasMismatch)? { publish_event(&mut write, &abort_event).await?; } diff --git a/crates/sprout-persona/Cargo.toml b/crates/buzz-persona/Cargo.toml similarity index 72% rename from crates/sprout-persona/Cargo.toml rename to crates/buzz-persona/Cargo.toml index d431aa7ec..884965065 100644 --- a/crates/sprout-persona/Cargo.toml +++ b/crates/buzz-persona/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "sprout-persona" +name = "buzz-persona" version = "0.1.0" edition = "2021" -description = "Parser and loader for Sprout persona pack files (.persona.md)" +description = "Parser and loader for Buzz persona pack files (.persona.md)" license = "Apache-2.0" repository = "https://github.com/block/sprout" diff --git a/crates/sprout-persona/PERSONA_PACK_SPEC.md b/crates/buzz-persona/PERSONA_PACK_SPEC.md similarity index 79% rename from crates/sprout-persona/PERSONA_PACK_SPEC.md rename to crates/buzz-persona/PERSONA_PACK_SPEC.md index f29fa3220..3c5611ba0 100644 --- a/crates/sprout-persona/PERSONA_PACK_SPEC.md +++ b/crates/buzz-persona/PERSONA_PACK_SPEC.md @@ -3,7 +3,7 @@ ## 1. Overview & Goals A **Persona Pack** is a portable, self-contained bundle that defines one or more AI agent personas -for deployment in Sprout. It is a **superset of the [Open Plugin Spec](https://open-plugin-spec.org)** +for deployment in Buzz. It is a **superset of the [Open Plugin Spec](https://open-plugin-spec.org)** — every valid Persona Pack is also a valid OPS package, but not vice versa. A pack contains: personas (identity + system prompt), skills (on-demand instruction sets), MCP @@ -11,17 +11,17 @@ server config, pack-level instructions, lifecycle hooks, and distribution metada ### Design Goals -1. **Portable** — zip file or git repo; no Sprout tooling required to inspect +1. **Portable** — zip file or git repo; no Buzz tooling required to inspect 2. **Composable** — skills and MCP servers shared across agents; per-agent overrides additive 3. **OPS-compatible** — discoverable by any OPS-compatible tool -4. **Harness-honest** — explicit about what the agent runtime does vs. what sprout-acp does +4. **Harness-honest** — explicit about what the agent runtime does vs. what buzz-acp does --- ## 2. Open Plugin Spec Compatibility A Persona Pack is a valid OPS package. The `.plugin/plugin.json` manifest follows the OPS schema, -and Sprout-specific extensions live alongside the OPS fields at the top level. Since the Open +and Buzz-specific extensions live alongside the OPS fields at the top level. Since the Open Plugin Spec defines no model configuration fields, there are no collisions. OPS consumers safely ignore unknown fields. @@ -33,13 +33,13 @@ ignore unknown fields. "id": "com.example.meadow-security-team", "name": "Meadow Security Team", "version": "1.2.0", - "description": "A four-agent security review team for Sprout.", + "description": "A four-agent security review team for Buzz.", "author": "Meadow Engineering", "license": "MIT", "homepage": "https://github.com/example/meadow-security-team", - "keywords": ["security", "code-review", "sprout"], + "keywords": ["security", "code-review", "buzz"], "engines": { - "sprout": ">=0.9.0" + "buzz": ">=0.9.0" }, "personas": [ "agents/pip.persona.md", @@ -88,12 +88,12 @@ none of them override it. - **OPS consumers**: see standard metadata; safely ignore unknown fields including `personas`, `defaults`, `pack_instructions`, `mcp_config`, and `hooks_config`. -- **Sprout**: reads both OPS fields and the Sprout-specific fields; `personas` is authoritative. -- **Version negotiation**: `engines.sprout` specifies minimum required Sprout version; sprout-acp +- **Buzz**: reads both OPS fields and the Buzz-specific fields; `personas` is authoritative. +- **Version negotiation**: `engines.buzz` specifies minimum required Buzz version; buzz-acp rejects packs requiring a newer version. -- **Extension mechanism**: Sprout-specific fields sit at the top level of `plugin.json` alongside +- **Extension mechanism**: Buzz-specific fields sit at the top level of `plugin.json` alongside OPS fields. No OPS core field is overloaded. -- **`defaults`**: ignored entirely by OPS consumers. sprout-acp resolves it at deploy time before +- **`defaults`**: ignored entirely by OPS consumers. buzz-acp resolves it at deploy time before constructing per-persona configurations (see Section 10 and Section 12). --- @@ -122,7 +122,7 @@ my-pack/ ├── instructions.md # Pack-level instructions (injected by harness) ├── pack.lock # Version lock (Phase 1+) ├── README.md # Human-readable description -└── my-pack-1.2.0.sproutpack.sha256 # Checksum (required for zip distribution) +└── my-pack-1.2.0.buzzpack.sha256 # Checksum (required for zip distribution) ``` ### Directory Conventions @@ -175,7 +175,7 @@ mcp_servers: env: SEMGREP_TOKEN: "${SEMGREP_TOKEN}" -# === Behavioral Config (Sprout-specific) === +# === Behavioral Config (Buzz-specific) === subscribe: - "#security-reviews" - "#code-reviews" @@ -202,7 +202,7 @@ You are Lep, a security-focused code reviewer on the Meadow team. | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | ✅ | Machine name / agent ID. Lowercase, no spaces, unique within pack. | -| `display_name` | string | ✅ | Human-readable name shown in Sprout UI. | +| `display_name` | string | ✅ | Human-readable name shown in Buzz UI. | | `avatar` | string | ❌ | Pack-relative path to avatar image. | | `description` | string | ✅ | One-line description. | | `version` | string | ❌ | Semver. Defaults to pack version if omitted. | @@ -230,7 +230,7 @@ files (agent runtimes typically do not read them). ## 5. Two-Layer Prompt Architecture -sprout-acp assembles the agent's context from two distinct prompt layers before sending each +buzz-acp assembles the agent's context from two distinct prompt layers before sending each message. Understanding this layering is essential for persona authors — content that belongs in one layer should not be duplicated in the other. @@ -240,7 +240,7 @@ Each message delivered to the agent runtime includes these sections in order: ``` [Base] - + [System] @@ -255,27 +255,27 @@ Each message delivered to the agent runtime includes these sections in order: [Thread/Conversation Context] -[Sprout event] +[Buzz event] ``` ### The `[Base]` Layer -The `[Base]` layer is compiled into sprout-acp and is **identical for every agent**. It covers: +The `[Base]` layer is compiled into buzz-acp and is **identical for every agent**. It covers: | Content | Purpose | |---------|---------| -| Platform identity | Tells the agent it is running inside Sprout and what that means | +| Platform identity | Tells the agent it is running inside Buzz and what that means | | MCP tool reference | Documents the tools available via the connected MCP servers | | Workspace layout | Describes `$AGENT_CWD`, skill discovery paths, and file conventions | | Message polling | Explains how to check for new messages proactively | -Pack authors do not write or configure the `[Base]` layer — it is maintained by the Sprout team -and updated in sprout-acp releases. +Pack authors do not write or configure the `[Base]` layer — it is maintained by the Buzz team +and updated in buzz-acp releases. -**Disabling or customizing the base layer**: Set `SPROUT_ACP_NO_BASE_PROMPT` to omit the `[Base]` +**Disabling or customizing the base layer**: Set `BUZZ_ACP_NO_BASE_PROMPT` to omit the `[Base]` section entirely. To replace the compiled-in default with custom content, set -`SPROUT_ACP_BASE_PROMPT_FILE` to a file path — sprout-acp reads it at startup and uses it instead. +`BUZZ_ACP_BASE_PROMPT_FILE` to a file path — buzz-acp reads it at startup and uses it instead. ### The `[System]` Layer @@ -300,7 +300,7 @@ What belongs in `[System]`: - How to use MCP tools (covered by `[Base]`) - How to poll for new messages or use the `since` parameter (covered by `[Base]`) - Workspace layout or skill loading mechanics (covered by `[Base]`) -- That the agent is running inside Sprout (covered by `[Base]`) +- That the agent is running inside Buzz (covered by `[Base]`) Focus persona prompts on what makes this agent unique: its role, personality, domain expertise, and team-specific protocols. @@ -321,13 +321,13 @@ the agent how to perform a specific task. The agent runtime discovers skills from these directories relative to the session working directory (`$AGENT_CWD` — see definition below): -> **Note**: `.agents/skills/` is sprout-acp's canonical skill location. The other paths shown +> **Note**: `.agents/skills/` is buzz-acp's canonical skill location. The other paths shown > (`.goose/skills/`, `.claude/skills/`) are agent-runtime-specific and listed for reference only. ``` $AGENT_CWD/.goose/skills//SKILL.md $AGENT_CWD/.claude/skills//SKILL.md -$AGENT_CWD/.agents/skills//SKILL.md ← sprout-acp uses this one +$AGENT_CWD/.agents/skills//SKILL.md ← buzz-acp uses this one ``` > **Note**: `$AGENT_CWD/skills/` is NOT scanned. Skills placed at the pack root `skills/` directory @@ -339,12 +339,12 @@ Throughout this spec, **`$AGENT_CWD`** refers to the `cwd` field in the ACP `New — the working directory passed to the agent runtime when creating a session. The value is delivered via the ACP protocol. However, operators can control this value by setting the `AGENT_CWD` environment variable on the -sprout-acp process. sprout-acp determines what value to pass as `NewSessionRequest.cwd` in this +buzz-acp process. buzz-acp determines what value to pass as `NewSessionRequest.cwd` in this order: -1. The `AGENT_CWD` environment variable on the sprout-acp process, if set. +1. The `AGENT_CWD` environment variable on the buzz-acp process, if set. 2. `std::env::current_dir()` as a fallback. -3. If both fail, sprout-acp logs an error and **refuses to start**. +3. If both fail, buzz-acp logs an error and **refuses to start**. The agent runtime stores this value as `session.working_dir` and uses it for all skill discovery. @@ -386,7 +386,7 @@ with other agents. If you want a skill available to all agents AND explicitly li ### Collision Handling If a skill with the same load key already exists in `$AGENT_CWD/.agents/skills/`, the pack skill -is **not overwritten**. This allows operators to pin custom skill versions. sprout-acp **must log +is **not overwritten**. This allows operators to pin custom skill versions. buzz-acp **must log a warning** when a pack skill is skipped due to a collision: ``` @@ -401,7 +401,7 @@ Skills are not auto-loaded into context. The agent must explicitly load them: load(source: "security-review") ``` -sprout-acp lists available skills in the user message prefix so the agent knows what's available. +buzz-acp lists available skills in the user message prefix so the agent knows what's available. See Section 12 for the full message format. ### Skill File Format @@ -418,7 +418,7 @@ description: "Reviews code for security vulnerabilities using OWASP Top 10 and s ``` Both `name:` and `description:` are **required**. A skill missing either field is silently skipped -by the agent runtime. `sprout pack validate` warns on skill name mismatches but does not yet +by the agent runtime. `buzz pack validate` warns on skill name mismatches but does not yet enforce required metadata fields (see PF-5). --- @@ -427,7 +427,7 @@ enforce required metadata fields (see PF-5). MCP servers provide external tool access (GitHub, Semgrep, databases, etc.). Configuration is defined at two levels: pack-level (shared across all agents) and per-persona (agent-specific). -sprout-acp merges them and passes the result via the ACP protocol — no filesystem placement required. +buzz-acp merges them and passes the result via the ACP protocol — no filesystem placement required. > **Transport Warning**: Only `stdio` and `streamable_http` transports are supported. SSE transport > is rejected by the ACP runtime with the error `"SSE is unsupported, migrate to streamable_http"` and @@ -477,22 +477,22 @@ mcp_servers: > yet implemented. In the current release, `${VAR_NAME}` strings are passed through as literals > to the agent runtime, which may resolve them via its own MCP server configuration handling. -When implemented, all `env` values will be scanned for `${VAR_NAME}`. sprout-acp will resolve +When implemented, all `env` values will be scanned for `${VAR_NAME}`. buzz-acp will resolve from the process environment **before** passing to the agent runtime. Unresolved variables will cause a startup error. ### Delivery -sprout-acp passes the merged config via `NewSessionRequest.mcp_servers`. **No `.mcp.json` is written to the agent's working directory.** +buzz-acp passes the merged config via `NewSessionRequest.mcp_servers`. **No `.mcp.json` is written to the agent's working directory.** --- ## 8. Pack-Level Instructions `instructions.md` contains shared rules, coding standards, and team norms that apply to all agents -in the pack. sprout-acp appends it to the persona prompt in the user message prefix. +in the pack. buzz-acp appends it to the persona prompt in the user message prefix. -sprout-acp appends `instructions.md` to the persona prompt in the user message prefix (see +buzz-acp appends `instructions.md` to the persona prompt in the user message prefix (see Section 12). **No file is written to disk.** **What does NOT work**: `.mdc` rule files (agent runtimes typically don't read them), `rules/` directory (no @@ -510,7 +510,7 @@ contributors only). > **Implementation note**: Hooks are parsed and validated at pack load time but not yet executed. > Hook execution is planned for a future release. -Hooks are shell commands fired by sprout-acp at agent lifecycle points. **Agent runtimes typically have no hook system** +Hooks are shell commands fired by buzz-acp at agent lifecycle points. **Agent runtimes typically have no hook system** — hooks are entirely a harness feature. ### `hooks/hooks.json` @@ -546,7 +546,7 @@ hooks: ### Hook Execution -Hooks run as the sprout-acp user; working directory is `$AGENT_CWD`; agent env vars are available. +Hooks run as the buzz-acp user; working directory is `$AGENT_CWD`; agent env vars are available. Exit codes: `on_start` non-zero → abort startup; `on_stop` non-zero → logged only; `on_message` non-zero → message dropped and error logged. @@ -554,7 +554,7 @@ non-zero → message dropped and error logged. The `on_message` hook receives the incoming message content via **stdin** (UTF-8 text). It is a **read-only side-effect hook** — it cannot modify the message. If you need message transformation, -that must be implemented directly in sprout-acp's dispatch loop, not via a hook. +that must be implemented directly in buzz-acp's dispatch loop, not via a hook. - **Timeout**: 5 seconds. Hooks that exceed this are killed (SIGKILL) and the message is dropped. - **Non-zero exit**: Message is dropped and an error is logged. The agent does not see the message. @@ -562,19 +562,19 @@ that must be implemented directly in sprout-acp's dispatch loop, not via a hook. ### `on_stop` Crash Caveat -`on_stop` fires on normal exit and on handled errors. It **will not fire** if sprout-acp crashes +`on_stop` fires on normal exit and on handled errors. It **will not fire** if buzz-acp crashes (SIGSEGV, OOM, etc.). For critical cleanup (lock files, external resource release), use a systemd/supervisor cleanup unit or a process supervisor that runs cleanup unconditionally. -**Hooks are NOT agent runtime features.** They are implemented entirely in sprout-acp. Bypassing -sprout-acp means no hooks fire. +**Hooks are NOT agent runtime features.** They are implemented entirely in buzz-acp. Bypassing +buzz-acp means no hooks fire. --- ## 10. Behavioral Configuration The behavioral config fields in a persona's frontmatter control how the agent participates in -Sprout conversations. These are all Sprout-specific — the agent runtime has no awareness of them. They sit +Buzz conversations. These are all Buzz-specific — the agent runtime has no awareness of them. They sit at the top level of the frontmatter alongside identity fields like `name` and `description`. ### Pack Defaults @@ -631,28 +631,28 @@ Result: ### Precedence Model -In this spec, **"deploy time"** means when sprout-acp loads the pack and constructs per-persona -session configurations — typically at sprout-acp process startup. For git-based packs, this occurs -each time sprout-acp starts and reads the installed pack directory. +In this spec, **"deploy time"** means when buzz-acp loads the pack and constructs per-persona +session configurations — typically at buzz-acp process startup. For git-based packs, this occurs +each time buzz-acp starts and reads the installed pack directory. -When sprout-acp resolves the effective configuration for a persona, it applies this order (highest +When buzz-acp resolves the effective configuration for a persona, it applies this order (highest wins): ``` 1. Operator env vars — e.g. GOOSE_MODEL, GOOSE_PROVIDER (agent-runtime-specific) already set in the parent process environment -2. Desktop UI per-agent — overrides set in the Sprout desktop app per-agent settings +2. Desktop UI per-agent — overrides set in the Buzz desktop app per-agent settings 3. Per-persona frontmatter — behavioral config fields set directly in the persona's frontmatter 4. Pack-level defaults — the `defaults` object in plugin.json -5. Built-in defaults — sprout-acp's hardcoded fallback values +5. Built-in defaults — buzz-acp's hardcoded fallback values ``` -sprout-acp resolves levels 3–5 at deploy time (when the pack is loaded and sessions are +buzz-acp resolves levels 3–5 at deploy time (when the pack is loaded and sessions are constructed). Levels 1–2 are applied at runtime and are outside the pack's control. -**Level 1 — Operator env vars**: If the operator has already set env vars for model, provider, temperature, or context limit in the parent process environment, sprout-acp MUST NOT override them with pack/persona values. sprout-acp only injects env vars for fields that are NOT already set in the parent environment. This ensures operators can always override pack configuration. +**Level 1 — Operator env vars**: If the operator has already set env vars for model, provider, temperature, or context limit in the parent process environment, buzz-acp MUST NOT override them with pack/persona values. buzz-acp only injects env vars for fields that are NOT already set in the parent environment. This ensures operators can always override pack configuration. -**Implementation**: when constructing the child process environment, sprout-acp checks +**Implementation**: when constructing the child process environment, buzz-acp checks `std::env::var(key)` for each env var. If the parent already has it set, skip injection. If not, inject the resolved pack/persona value. @@ -764,15 +764,15 @@ apply to both. | `triggers.keywords` | string[] | `[]` | Any strings | Respond when message contains any keyword (case-insensitive). | | `triggers.all_messages` | bool | `false` | `true` / `false` | Respond to every message in subscribed channels. | | `model` | string | none (agent runtime uses operator default) | `"provider:model-id"` format | Model to use. Split on first `:` for provider + model env vars. | -| `temperature` | float | `0.7` | Provider-dependent (typically 0.0–2.0). sprout-acp passes through without range validation; `sprout pack validate` checks type only (must be a number), not range. | Passed as env var to agent runtime. | +| `temperature` | float | `0.7` | Provider-dependent (typically 0.0–2.0). buzz-acp passes through without range validation; `buzz pack validate` checks type only (must be a number), not range. | Passed as env var to agent runtime. | | `max_context_tokens` | int | none (provider default) | Positive integer | Passed as env var to agent runtime. | | `thread_replies` | bool | `true` | `true` / `false` | Reply in-thread when the triggering message is in a thread. | | `broadcast_replies` | bool | `false` | `true` / `false` | Also surface thread replies to the main channel. | -**Unknown keys** in `defaults` (in `plugin.json`) are **validation warnings** in `sprout pack +**Unknown keys** in `defaults` (in `plugin.json`) are **validation warnings** in `buzz pack validate` — this catches typos like `temprature` at validate time. Unknown keys in persona frontmatter are **hard errors** (via `deny_unknown_fields` in the YAML parser). At deploy time, -sprout-acp logs a `WARN` and ignores unknown manifest keys, remaining fail-soft: +buzz-acp logs a `WARN` and ignores unknown manifest keys, remaining fail-soft: ``` WARN: Unknown key "temprature" in defaults (plugin.json); ignoring @@ -805,18 +805,18 @@ broadcast_replies: false ### Channel Name `#` Convention -The `#` prefix in `subscribe` entries is a **display convention only**. Channel names in the Sprout -relay are stored and queried **without** the `#` prefix. sprout-acp strips the leading `#` before +The `#` prefix in `subscribe` entries is a **display convention only**. Channel names in the Buzz +relay are stored and queried **without** the `#` prefix. buzz-acp strips the leading `#` before making any relay API calls. `"#security-reviews"` and `"security-reviews"` are equivalent in this field. ### Env Var Mapping -sprout-acp resolves pack defaults and per-persona overrides (precedence levels 3–5) into a single +buzz-acp resolves pack defaults and per-persona overrides (precedence levels 3–5) into a single effective configuration per persona **before** injecting environment variables into the child process. The env vars set reflect the fully-resolved values — not the raw persona frontmatter. -sprout-acp translates persona behavioral config fields to agent configuration via environment +buzz-acp translates persona behavioral config fields to agent configuration via environment variables injected into the child process at spawn time: | Persona field | Env var(s) | Notes | @@ -825,20 +825,20 @@ variables injected into the child process at spawn time: | `temperature: 0.3` | `GOOSE_TEMPERATURE=0.3` | Read by agent runtime at startup | | `max_context_tokens: 128000` | `GOOSE_CONTEXT_LIMIT=128000` | Read by agent runtime at startup | -If `model` is omitted from both the persona frontmatter and `defaults`, sprout-acp does not set +If `model` is omitted from both the persona frontmatter and `defaults`, buzz-acp does not set `GOOSE_PROVIDER` or `GOOSE_MODEL`, and the agent runtime uses its configured operator default. -> **Implementation note**: `AcpClient::spawn` accepts per-persona env vars via the `extra_env` parameter. sprout-acp checks `std::env::var(key)` before injecting each var — if the parent environment already has the key set, injection is skipped (operator precedence, level 1). +> **Implementation note**: `AcpClient::spawn` accepts per-persona env vars via the `extra_env` parameter. buzz-acp checks `std::env::var(key)` before injecting each var — if the parent environment already has the key set, injection is skipped (operator precedence, level 1). See the Canonical Behavioral Config Field Schema table above for the full field reference. > **Built-in defaults note**: The "Built-in Default" column in the Canonical Behavioral Config -> Field Schema table lists sprout-acp's built-in fallbacks (precedence level 5). If `defaults` is +> Field Schema table lists buzz-acp's built-in fallbacks (precedence level 5). If `defaults` is > present in `plugin.json`, those values take precedence over the built-in defaults (level 4 > > level 5). The built-in defaults only apply when neither the persona nor the pack defaults specify > a value. -All fields are consumed entirely by sprout-acp. None are passed to the agent runtime directly — they are projected as env vars or used by the harness's subscription/dispatch logic. +All fields are consumed entirely by buzz-acp. None are passed to the agent runtime directly — they are projected as env vars or used by the harness's subscription/dispatch logic. --- @@ -846,19 +846,19 @@ All fields are consumed entirely by sprout-acp. None are passed to the agent run ### Phase 1: Zip File -A pack is distributed as a `.sproutpack` file (zip archive): +A pack is distributed as a `.buzzpack` file (zip archive): ```bash -sprout pack validate ./my-pack -sprout pack ./my-pack --output my-pack-1.2.0.sproutpack -sprout install ./my-pack-1.2.0.sproutpack -sprout install https://example.com/releases/my-pack-1.2.0.sproutpack +buzz pack validate ./my-pack +buzz pack ./my-pack --output my-pack-1.2.0.buzzpack +buzz install ./my-pack-1.2.0.buzzpack +buzz install https://example.com/releases/my-pack-1.2.0.buzzpack ``` #### Pack Integrity (Required) -Zip packs **must** ship with `-.sproutpack.sha256` containing `sha256sum` -output (` `). sprout-acp **must** verify before installation and refuse on +Zip packs **must** ship with `-.buzzpack.sha256` containing `sha256sum` +output (` `). buzz-acp **must** verify before installation and refuse on mismatch. For HTTP installs, the checksum file is fetched from the same base URL. #### `pack.lock` for Phase 1 @@ -868,7 +868,7 @@ Phase 1 installs record the installed pack in `pack.lock` alongside the pack dir ```json { "com.example.meadow-security-team": { - "source": "https://example.com/releases/my-pack-1.2.0.sproutpack", + "source": "https://example.com/releases/my-pack-1.2.0.buzzpack", "sha256": "a3f1c2d4e5b6...", "version": "1.2.0", "installed_at": "2026-04-10T11:00:00Z" @@ -879,9 +879,9 @@ Phase 1 installs record the installed pack in `pack.lock` alongside the pack dir ### Phase 2: Git Repository ```bash -sprout install github:example/meadow-security-team -sprout install github:example/meadow-security-team@v1.2.0 -sprout install git+https://gitlab.example.com/team/pack.git +buzz install github:example/meadow-security-team +buzz install github:example/meadow-security-team@v1.2.0 +buzz install git+https://gitlab.example.com/team/pack.git ``` `pack.lock` for git installs records the resolved commit SHA: @@ -899,17 +899,17 @@ sprout install git+https://gitlab.example.com/team/pack.git ### Phase 3: App Store UI -A Sprout-hosted registry and in-app browser for discovering and installing packs. API-compatible +A Buzz-hosted registry and in-app browser for discovering and installing packs. API-compatible with OPS registries. Details TBD. ### Installed Pack Location -Installed packs live at `~/.sprout/packs//`. sprout-acp reads packs from this location +Installed packs live at `~/.buzz/packs//`. buzz-acp reads packs from this location at agent startup. ### Desktop App Import -The Sprout desktop app can import persona packs via the Import button: +The Buzz desktop app can import persona packs via the Import button: - **My Agents → Import**: Accepts `.persona.md` files (individual personas) or `.zip` files (persona packs detected by `.plugin/plugin.json`). Pack zips are resolved in a temp directory; @@ -930,16 +930,16 @@ How each pack component reaches the running agent: | Component | Delivery Method | Mechanism | Filesystem Write? | |-----------|----------------|-----------|-------------------| -| Skills | Copy at deploy time (planned) | sprout-acp will copy `skills/` → `$AGENT_CWD/.agents/skills/` | ✅ Yes (only one) | +| Skills | Copy at deploy time (planned) | buzz-acp will copy `skills/` → `$AGENT_CWD/.agents/skills/` | ✅ Yes (only one) | | MCP servers | ACP protocol | `NewSessionRequest.mcp_servers` | ❌ No | -| Persona prompt | User message prefix | `[System]` block prepended to user message text by sprout-acp | ❌ No | +| Persona prompt | User message prefix | `[System]` block prepended to user message text by buzz-acp | ❌ No | | Pack instructions | User message prefix | Appended to `[System]` block in user message text | ❌ No | -| Lifecycle hooks | Harness internal | sprout-acp fires shell commands directly | ❌ No | +| Lifecycle hooks | Harness internal | buzz-acp fires shell commands directly | ❌ No | | Model/provider | Child process env vars | Agent-runtime-specific env vars (e.g. `GOOSE_PROVIDER`, `GOOSE_MODEL`) | ❌ No | -| Behavioral config | Harness internal | sprout-acp subscription + dispatch logic | ❌ No | -| Pack defaults (`defaults`) | Harness internal | Resolved at deploy time by sprout-acp into per-persona effective config; never passed to the agent runtime directly | ❌ No | +| Behavioral config | Harness internal | buzz-acp subscription + dispatch logic | ❌ No | +| Pack defaults (`defaults`) | Harness internal | Resolved at deploy time by buzz-acp into per-persona effective config; never passed to the agent runtime directly | ❌ No | -> **Pack defaults are resolved at deploy time**, not at runtime. When sprout-acp loads a pack and +> **Pack defaults are resolved at deploy time**, not at runtime. When buzz-acp loads a pack and > constructs per-persona session configurations, it merges the `defaults` object with each persona's > frontmatter behavioral config fields (per the precedence model in Section 10) and stores the > resulting effective configuration. The `defaults` object itself is not forwarded to the agent runtime or @@ -947,8 +947,8 @@ How each pack component reaches the running agent: ### The `[System]` Block — Current Implementation -sprout-acp's `format_prompt()` in `queue.rs` prepends a `[System]` block to the **user message -text** before sending it to the agent runtime. This is a **sprout-acp feature, not an agent runtime +buzz-acp's `format_prompt()` in `queue.rs` prepends a `[System]` block to the **user message +text** before sending it to the agent runtime. This is a **buzz-acp feature, not an agent runtime feature**. The agent sees the `[System]` prefix as part of the user message content — it is NOT injected into the agent's actual system prompt. @@ -996,23 +996,23 @@ The `[System]` prefix re-sends the full persona prompt on every turn. True syste Never embed secrets in pack files. Use `${VAR_NAME}` references in all `env` blocks. Currently, `${VAR_NAME}` strings are passed through as literals to the agent runtime (see Section 7). When -harness-side interpolation is implemented, sprout-acp will resolve them from the process +harness-side interpolation is implemented, buzz-acp will resolve them from the process environment at startup and refuse to start if any are unresolved. Inject secrets via your deployment mechanism (systemd env files, Vault, Kubernetes secrets, etc.). ### Pack Integrity -- **Phase 1 (zip)**: Packs **must** ship with `-.sproutpack.sha256` containing - `sha256sum` output (` `). sprout-acp **must** verify before installation +- **Phase 1 (zip)**: Packs **must** ship with `-.buzzpack.sha256` containing + `sha256sum` output (` `). buzz-acp **must** verify before installation and refuse on mismatch. -- **Phase 2 (git)**: `pack.lock` pins the resolved commit SHA; sprout-acp verifies on install. +- **Phase 2 (git)**: `pack.lock` pins the resolved commit SHA; buzz-acp verifies on install. - **Phase 3 (registry)**: Registry signatures TBD. ### Hook Execution -Hooks run with sprout-acp's privileges — significant attack surface. Only install packs from -trusted sources. Review all hook commands before installing. Consider sandboxing sprout-acp -(container, restricted user) for untrusted packs. sprout-acp should display hook commands before +Hooks run with buzz-acp's privileges — significant attack surface. Only install packs from +trusted sources. Review all hook commands before installing. Consider sandboxing buzz-acp +(container, restricted user) for untrusted packs. buzz-acp should display hook commands before first execution (Phase 2 feature). ### MCP Server and Skill Trust @@ -1025,28 +1025,28 @@ both with the same caution as any untrusted prompt content. ## 14. Migration Path -### From V6 (sprout-namespaced) Format +### From V6 (buzz-namespaced) Format Field mapping from V6 `.persona.md` to current `.persona.md`: | V6 location | Current location | |---|---| -| `sprout.model` | `model` (top-level frontmatter) | -| `sprout.temperature` | `temperature` (top-level frontmatter) | -| `sprout.max_context_tokens` | `max_context_tokens` (top-level frontmatter) | -| `sprout.subscribe` | `subscribe` (top-level frontmatter) | -| `sprout.respond_to` | `triggers` (top-level frontmatter) | -| `sprout.thread_replies` | `thread_replies` (top-level frontmatter) | -| `sprout.broadcast_replies` | `broadcast_replies` (top-level frontmatter) | -| `plugin.json` → `sprout.defaults` | `plugin.json` → `defaults` (top-level) | -| `plugin.json` → `sprout.personas` | `plugin.json` → `personas` (top-level) | -| `plugin.json` → `sprout.pack_instructions` | `plugin.json` → `pack_instructions` (top-level) | -| `plugin.json` → `sprout.mcp_config` | `plugin.json` → `mcp_config` (top-level) | -| `plugin.json` → `sprout.hooks_config` | `plugin.json` → `hooks_config` (top-level) | +| `buzz.model` | `model` (top-level frontmatter) | +| `buzz.temperature` | `temperature` (top-level frontmatter) | +| `buzz.max_context_tokens` | `max_context_tokens` (top-level frontmatter) | +| `buzz.subscribe` | `subscribe` (top-level frontmatter) | +| `buzz.respond_to` | `triggers` (top-level frontmatter) | +| `buzz.thread_replies` | `thread_replies` (top-level frontmatter) | +| `buzz.broadcast_replies` | `broadcast_replies` (top-level frontmatter) | +| `plugin.json` → `buzz.defaults` | `plugin.json` → `defaults` (top-level) | +| `plugin.json` → `buzz.personas` | `plugin.json` → `personas` (top-level) | +| `plugin.json` → `buzz.pack_instructions` | `plugin.json` → `pack_instructions` (top-level) | +| `plugin.json` → `buzz.mcp_config` | `plugin.json` → `mcp_config` (top-level) | +| `plugin.json` → `buzz.hooks_config` | `plugin.json` → `hooks_config` (top-level) | **V6 persona frontmatter** (before): ```yaml -sprout: +buzz: model: "anthropic:claude-sonnet-4-20250514" temperature: 0.3 subscribe: @@ -1063,7 +1063,7 @@ subscribe: **V6 `plugin.json`** (before): ```json -"sprout": { +"buzz": { "personas": ["agents/pip.persona.md"], "defaults": { "model": "anthropic:claude-sonnet-4-20250514" } } @@ -1093,11 +1093,11 @@ Field mapping from flat JSON (`personas/lep.json`) to `.persona.md`: 2. For each persona JSON → create `agents/.persona.md` using the mapping above 3. Move skills to `skills//SKILL.md`; ensure each has `name:` and `description:` frontmatter 4. Create `instructions.md` from any shared prompt content -5. Run `sprout pack validate ./my-pack` +5. Run `buzz pack validate ./my-pack` ### Backward Compatibility -The V6 namespaced `sprout:` block format is not supported. Only the current flat top-level fields format is accepted. The `respond_to` key is accepted as a legacy alias for `triggers` in both persona frontmatter and `plugin.json` defaults. +The V6 namespaced `buzz:` block format is not supported. Only the current flat top-level fields format is accepted. The `respond_to` key is accepted as a legacy alias for `triggers` in both persona frontmatter and `plugin.json` defaults. --- @@ -1106,13 +1106,13 @@ The V6 namespaced `sprout:` block format is not supported. Only the current flat ### Unresolved 1. **`session/set_model` as env var alternative**: The ACP runtime implements `on_set_model()` (ACP - unstable feature). sprout-acp could call `session/set_model` after `session/new` to set the + unstable feature). buzz-acp could call `session/set_model` after `session/new` to set the model per-session without env var injection. This avoids the `AcpClient::spawn` limitation for model (but not provider, temperature, or context limit). Deferred pending stability of the ACP unstable feature. 2. **`CONTEXT_FILE_NAMES` env var**: The goose agent runtime supports this env var to control which filenames are - scanned for hints. Should sprout-acp set this to include pack-specific filenames? Deferred + scanned for hints. Should buzz-acp set this to include pack-specific filenames? Deferred pending use case. 3. **Skill versioning**: Skills are identified by load key only. If two packs provide a skill with @@ -1127,7 +1127,7 @@ The V6 namespaced `sprout:` block format is not supported. Only the current flat ### Future Work -`sprout pack init` scaffolding; hot reload of skills/instructions; skill marketplace; pack dependencies; agent-to-agent handoff within a pack. +`buzz pack init` scaffolding; hot reload of skills/instructions; skill marketplace; pack dependencies; agent-to-agent handoff within a pack. --- @@ -1138,11 +1138,11 @@ Features required by this spec but not yet implemented. | ID | What | Where | |----|------|-------| | PF-1 | True system prompt injection via the ACP protocol's `on_new_session()`. Current `[System]` prefix re-sends persona prompt on every turn; true injection fires once at session creation. | ACP server `on_new_session()` | -| PF-2 | `sprout pack validate` CLI: **Implemented.** Schema-validates `plugin.json`; checks `.persona.md` required identity fields; validates behavioral config fields; warns on unknown keys and skill name mismatches. Remaining: verify `skills:` and `hooks:` paths exist; error on `SKILL.md` missing `name:` or `description:`. | `sprout-cli` / `sprout-admin` | -| PF-3 | Skill collision warning: emit `WARN` when a pack skill is skipped because a skill with the same load key already exists in `.agents/skills/`. | sprout-acp skill copy logic | -| PF-4 | `$AGENT_CWD` resolution: determine `NewSessionRequest.cwd` from (1) `AGENT_CWD` env var, (2) `std::env::current_dir()`, (3) error and refuse to start. | sprout-acp startup / session init | -| PF-5 | Skill parse failure warning: emit `WARN` when `parse_skill_content` returns `None` (missing `name:`, missing `description:`, or malformed frontmatter). Currently the agent runtime silently skips. sprout-acp should pre-validate during skill copy. | sprout-acp skill copy logic | -| PF-6 | Per-subprocess env var injection: **Implemented.** `AcpClient::spawn` accepts `extra_env: &[(String, String)]` injected via `Command::env()`. sprout-acp checks `std::env::var(key)` before injecting — operator env vars take precedence (level 1). | `sprout-acp/src/acp.rs` `AcpClient::spawn()` | +| PF-2 | `buzz pack validate` CLI: **Implemented.** Schema-validates `plugin.json`; checks `.persona.md` required identity fields; validates behavioral config fields; warns on unknown keys and skill name mismatches. Remaining: verify `skills:` and `hooks:` paths exist; error on `SKILL.md` missing `name:` or `description:`. | `buzz-cli` / `buzz-admin` | +| PF-3 | Skill collision warning: emit `WARN` when a pack skill is skipped because a skill with the same load key already exists in `.agents/skills/`. | buzz-acp skill copy logic | +| PF-4 | `$AGENT_CWD` resolution: determine `NewSessionRequest.cwd` from (1) `AGENT_CWD` env var, (2) `std::env::current_dir()`, (3) error and refuse to start. | buzz-acp startup / session init | +| PF-5 | Skill parse failure warning: emit `WARN` when `parse_skill_content` returns `None` (missing `name:`, missing `description:`, or malformed frontmatter). Currently the agent runtime silently skips. buzz-acp should pre-validate during skill copy. | buzz-acp skill copy logic | +| PF-6 | Per-subprocess env var injection: **Implemented.** `AcpClient::spawn` accepts `extra_env: &[(String, String)]` injected via `Command::env()`. buzz-acp checks `std::env::var(key)` before injecting — operator env vars take precedence (level 1). | `buzz-acp/src/acp.rs` `AcpClient::spawn()` | --- diff --git a/crates/sprout-persona/src/lib.rs b/crates/buzz-persona/src/lib.rs similarity index 100% rename from crates/sprout-persona/src/lib.rs rename to crates/buzz-persona/src/lib.rs diff --git a/crates/sprout-persona/src/manifest.rs b/crates/buzz-persona/src/manifest.rs similarity index 95% rename from crates/sprout-persona/src/manifest.rs rename to crates/buzz-persona/src/manifest.rs index 77a3d4dc4..376dffc05 100644 --- a/crates/sprout-persona/src/manifest.rs +++ b/crates/buzz-persona/src/manifest.rs @@ -1,7 +1,7 @@ //! Pack manifest types and `plugin.json` parser. //! //! Every persona pack ships a `.plugin/plugin.json` that describes the pack -//! (OPS metadata) and tells Sprout where to find personas, hooks, and MCP +//! (OPS metadata) and tells Buzz where to find personas, hooks, and MCP //! config. //! //! ```json @@ -39,9 +39,9 @@ pub enum ManifestError { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub struct Engines { - /// Semver range the Sprout runtime must satisfy (e.g. `">=0.9.0"`). - #[serde(skip_serializing_if = "Option::is_none")] - pub sprout: Option, + /// Semver range the Buzz runtime must satisfy (e.g. `">=0.9.0"`). + #[serde(skip_serializing_if = "Option::is_none", alias = "buzz")] + pub buzz: Option, } /// Pack-wide behavioral defaults. @@ -107,7 +107,7 @@ pub struct PackManifest { #[serde(skip_serializing_if = "Option::is_none")] pub engines: Option, - // ── Sprout extensions ───────────────────────────────────────────────── + // ── Buzz extensions ───────────────────────────────────────────────── /// Paths to `.persona.md` files (pack-relative). #[serde(default)] pub personas: Vec, @@ -137,7 +137,7 @@ pub struct PackManifest { /// Intentionally permissive (no `deny_unknown_fields`): `plugin.json` is an /// OPS superset and may carry fields from other tools (e.g. `ops_category`, /// `marketplace_tags`). Unknown fields are silently ignored here; the -/// validator issues advisory warnings for Sprout-unknown keys. +/// validator issues advisory warnings for Buzz-unknown keys. #[derive(Debug, Deserialize)] #[serde(rename_all = "snake_case")] struct RawManifest { @@ -240,7 +240,7 @@ mod tests { "license": "MIT", "homepage": "https://example.com", "keywords": ["ai", "bot"], - "engines": {"sprout": ">=0.9.0"}, + "engines": {"buzz": ">=0.9.0"}, "personas": ["personas/a.persona.md", "personas/b.persona.md"], "pack_instructions": "instructions.md", "mcp_config": ".mcp.json", @@ -254,7 +254,7 @@ mod tests { let m = parse_manifest(json).unwrap(); assert_eq!(m.id, "full-pack"); assert_eq!(m.keywords, vec!["ai", "bot"]); - assert_eq!(m.engines.unwrap().sprout.as_deref(), Some(">=0.9.0")); + assert_eq!(m.engines.unwrap().buzz.as_deref(), Some(">=0.9.0")); assert_eq!(m.personas.len(), 2); assert_eq!(m.pack_instructions.as_deref(), Some("instructions.md")); let d = m.defaults.unwrap(); diff --git a/crates/sprout-persona/src/merge.rs b/crates/buzz-persona/src/merge.rs similarity index 99% rename from crates/sprout-persona/src/merge.rs rename to crates/buzz-persona/src/merge.rs index 037e9148d..e9444b469 100644 --- a/crates/sprout-persona/src/merge.rs +++ b/crates/buzz-persona/src/merge.rs @@ -322,14 +322,14 @@ mod tests { let persona = json!({ "triggers": { "mentions": true, - "keywords": ["help", "sprout"], + "keywords": ["help", "buzz"], "all_messages": false, } }); let resolved = resolve_persona_config(&persona, None); let t = resolved.triggers.unwrap(); assert!(t.mentions); - assert_eq!(t.keywords, vec!["help", "sprout"]); + assert_eq!(t.keywords, vec!["help", "buzz"]); assert!(!t.all_messages); } diff --git a/crates/sprout-persona/src/pack.rs b/crates/buzz-persona/src/pack.rs similarity index 99% rename from crates/sprout-persona/src/pack.rs rename to crates/buzz-persona/src/pack.rs index 2fc9aacec..24ad9614c 100644 --- a/crates/sprout-persona/src/pack.rs +++ b/crates/buzz-persona/src/pack.rs @@ -113,7 +113,7 @@ pub struct PackManifestData { pub pack_instructions: Option, pub mcp_config: Option, // hooks_config is intentionally omitted: hooks are a runtime concern loaded - // separately by sprout-acp, not a pack-parsing concern. + // separately by buzz-acp, not a pack-parsing concern. /// Raw defaults block. pub defaults: Option, } diff --git a/crates/sprout-persona/src/persona.rs b/crates/buzz-persona/src/persona.rs similarity index 100% rename from crates/sprout-persona/src/persona.rs rename to crates/buzz-persona/src/persona.rs diff --git a/crates/sprout-persona/src/resolve.rs b/crates/buzz-persona/src/resolve.rs similarity index 97% rename from crates/sprout-persona/src/resolve.rs rename to crates/buzz-persona/src/resolve.rs index 47782a59a..03794e89c 100644 --- a/crates/sprout-persona/src/resolve.rs +++ b/crates/buzz-persona/src/resolve.rs @@ -389,10 +389,10 @@ fn runtime_env_vars(persona: &LoadedPersona) -> Vec<(String, String)> { let (provider, model_id) = split_model(model_str); match runtime { - Some("sprout-agent") => { - vars.push(("SPROUT_AGENT_MODEL".to_owned(), model_id.to_owned())); + Some("buzz-agent") => { + vars.push(("BUZZ_AGENT_MODEL".to_owned(), model_id.to_owned())); if let Some(p) = provider { - vars.push(("SPROUT_AGENT_PROVIDER".to_owned(), p.to_owned())); + vars.push(("BUZZ_AGENT_PROVIDER".to_owned(), p.to_owned())); } } _ => { @@ -627,13 +627,13 @@ mod tests { } #[test] - fn runtime_env_vars_sprout_agent_emits_sprout_agent_vars() { + fn runtime_env_vars_buzz_agent_emits_buzz_agent_vars() { let mut lp = stub_persona(Some("databricks:goose-claude-4-6-opus"), None, None); - lp.runtime = Some("sprout-agent".to_owned()); + lp.runtime = Some("buzz-agent".to_owned()); let vars = runtime_env_vars(&lp); let map: HashMap<&str, &str> = vars.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect(); - assert_eq!(map["SPROUT_AGENT_MODEL"], "goose-claude-4-6-opus"); - assert_eq!(map["SPROUT_AGENT_PROVIDER"], "databricks"); + assert_eq!(map["BUZZ_AGENT_MODEL"], "goose-claude-4-6-opus"); + assert_eq!(map["BUZZ_AGENT_PROVIDER"], "databricks"); assert!(!map.contains_key("GOOSE_MODEL")); assert!(!map.contains_key("GOOSE_PROVIDER")); } @@ -646,7 +646,7 @@ mod tests { let map: HashMap<&str, &str> = vars.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect(); assert_eq!(map["GOOSE_MODEL"], "goose-claude-4-6-opus"); assert_eq!(map["GOOSE_PROVIDER"], "databricks"); - assert!(!map.contains_key("SPROUT_AGENT_MODEL")); + assert!(!map.contains_key("BUZZ_AGENT_MODEL")); } #[test] @@ -659,13 +659,13 @@ mod tests { } #[test] - fn runtime_env_vars_sprout_agent_model_without_provider() { + fn runtime_env_vars_buzz_agent_model_without_provider() { let mut lp = stub_persona(Some("gpt-4o"), None, None); - lp.runtime = Some("sprout-agent".to_owned()); + lp.runtime = Some("buzz-agent".to_owned()); let vars = runtime_env_vars(&lp); let map: HashMap<&str, &str> = vars.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect(); - assert_eq!(map["SPROUT_AGENT_MODEL"], "gpt-4o"); - assert!(!map.contains_key("SPROUT_AGENT_PROVIDER")); + assert_eq!(map["BUZZ_AGENT_MODEL"], "gpt-4o"); + assert!(!map.contains_key("BUZZ_AGENT_PROVIDER")); } // ── Full pipeline (resolve_pack via filesystem) ─────────────────────── diff --git a/crates/sprout-persona/src/validate.rs b/crates/buzz-persona/src/validate.rs similarity index 99% rename from crates/sprout-persona/src/validate.rs rename to crates/buzz-persona/src/validate.rs index 14c52696f..3bc45a2c0 100644 --- a/crates/sprout-persona/src/validate.rs +++ b/crates/buzz-persona/src/validate.rs @@ -1,4 +1,4 @@ -//! Pack validation (`sprout pack validate`). +//! Pack validation (`buzz pack validate`). //! //! Architecture: the validator delegates all structural checks to `load_pack()`. //! If loading succeeds, the pack is structurally valid by definition — no @@ -599,7 +599,7 @@ mod tests { "homepage": "https://example.com", "repository": "https://github.com/example/pack", "keywords": ["test"], - "engines": { "sprout": ">=0.9.0" } + "engines": { "buzz": ">=0.9.0" } }"#, ) .unwrap(); diff --git a/crates/sprout-persona/tests/e2e_env_flow.rs b/crates/buzz-persona/tests/e2e_env_flow.rs similarity index 82% rename from crates/sprout-persona/tests/e2e_env_flow.rs rename to crates/buzz-persona/tests/e2e_env_flow.rs index 3246a1b86..de9895fd7 100644 --- a/crates/sprout-persona/tests/e2e_env_flow.rs +++ b/crates/buzz-persona/tests/e2e_env_flow.rs @@ -2,7 +2,7 @@ //! //! These tests exercise the full pack-resolve pipeline and verify that: //! - Goose personas emit GOOSE_PROVIDER, GOOSE_MODEL, GOOSE_TEMPERATURE -//! - Sprout-agent personas emit SPROUT_AGENT_MODEL, SPROUT_AGENT_PROVIDER +//! - Buzz-agent personas emit BUZZ_AGENT_MODEL, BUZZ_AGENT_PROVIDER //! - The import filter strips derived provider/model keys but preserves knobs //! - Multi-runtime packs produce correct per-persona env var prefixes //! - Models without a provider prefix emit only the model key (no provider) @@ -10,15 +10,15 @@ use std::collections::BTreeMap; use std::fs; -use sprout_persona::resolve::resolve_pack; +use buzz_persona::resolve::resolve_pack; // ── Import filter (replicates desktop crate logic) ─────────────────────────── const DERIVED_PROVIDER_MODEL_ENV_KEYS: &[&str] = &[ "GOOSE_MODEL", "GOOSE_PROVIDER", - "SPROUT_AGENT_MODEL", - "SPROUT_AGENT_PROVIDER", + "BUZZ_AGENT_MODEL", + "BUZZ_AGENT_PROVIDER", ]; fn filter_derived(env_vars: Vec<(String, String)>) -> BTreeMap { @@ -94,10 +94,10 @@ You are a test bot. ); } -// ── Test 2: Sprout-agent persona emits SPROUT_AGENT_* vars ─────────────────── +// ── Test 2: Buzz-agent persona emits BUZZ_AGENT_* vars ─────────────────── #[test] -fn resolve_pack_sprout_agent_persona_emits_sprout_agent_vars() { +fn resolve_pack_buzz_agent_persona_emits_buzz_agent_vars() { let tmp = tempfile::tempdir().unwrap(); let root = tmp.path(); @@ -122,7 +122,7 @@ fn resolve_pack_sprout_agent_persona_emits_sprout_agent_vars() { name: "bot" display_name: "Bot" description: "Test bot" -runtime: "sprout-agent" +runtime: "buzz-agent" model: "openai:gpt-4o" --- You are a test bot. @@ -140,24 +140,24 @@ You are a test bot. .collect(); assert_eq!( - env.get("SPROUT_AGENT_MODEL"), + env.get("BUZZ_AGENT_MODEL"), Some(&"gpt-4o"), - "should emit SPROUT_AGENT_MODEL=gpt-4o" + "should emit BUZZ_AGENT_MODEL=gpt-4o" ); assert_eq!( - env.get("SPROUT_AGENT_PROVIDER"), + env.get("BUZZ_AGENT_PROVIDER"), Some(&"openai"), - "should emit SPROUT_AGENT_PROVIDER=openai" + "should emit BUZZ_AGENT_PROVIDER=openai" ); // Must NOT contain GOOSE_* keys assert!( !env.contains_key("GOOSE_MODEL"), - "sprout-agent runtime must not emit GOOSE_MODEL" + "buzz-agent runtime must not emit GOOSE_MODEL" ); assert!( !env.contains_key("GOOSE_PROVIDER"), - "sprout-agent runtime must not emit GOOSE_PROVIDER" + "buzz-agent runtime must not emit GOOSE_PROVIDER" ); } @@ -239,7 +239,7 @@ fn full_pipeline_two_runtimes_different_env_vars() { "version": "1.0.0", "personas": [ "agents/goose-bot.persona.md", - "agents/sprout-bot.persona.md" + "agents/buzz-bot.persona.md" ], "defaults": {} }"#, @@ -260,17 +260,17 @@ You are a goose bot. ) .unwrap(); - // Sprout-agent persona + // Buzz-agent persona fs::write( - root.join("agents/sprout-bot.persona.md"), + root.join("agents/buzz-bot.persona.md"), r#"--- -name: "sprout-bot" -display_name: "Sprout Bot" -description: "A sprout-agent runtime bot" -runtime: "sprout-agent" +name: "buzz-bot" +display_name: "Buzz Bot" +description: "A buzz-agent runtime bot" +runtime: "buzz-agent" model: "openai:gpt-4o" --- -You are a sprout bot. +You are a buzz bot. "#, ) .unwrap(); @@ -283,11 +283,11 @@ You are a sprout bot. .iter() .find(|p| p.name == "goose-bot") .expect("goose-bot should exist"); - let sprout = pack + let buzz = pack .personas .iter() - .find(|p| p.name == "sprout-bot") - .expect("sprout-bot should exist"); + .find(|p| p.name == "buzz-bot") + .expect("buzz-bot should exist"); // Goose persona gets GOOSE_* env vars let goose_env: std::collections::HashMap<_, _> = goose @@ -301,29 +301,29 @@ You are a sprout bot. Some(&"claude-sonnet-4-20250514") ); assert!( - !goose_env.contains_key("SPROUT_AGENT_MODEL"), - "goose persona must not emit SPROUT_AGENT_MODEL" + !goose_env.contains_key("BUZZ_AGENT_MODEL"), + "goose persona must not emit BUZZ_AGENT_MODEL" ); assert!( - !goose_env.contains_key("SPROUT_AGENT_PROVIDER"), - "goose persona must not emit SPROUT_AGENT_PROVIDER" + !goose_env.contains_key("BUZZ_AGENT_PROVIDER"), + "goose persona must not emit BUZZ_AGENT_PROVIDER" ); - // Sprout-agent persona gets SPROUT_AGENT_* env vars - let sprout_env: std::collections::HashMap<_, _> = sprout + // Buzz-agent persona gets BUZZ_AGENT_* env vars + let buzz_env: std::collections::HashMap<_, _> = buzz .runtime_env_vars .iter() .map(|(k, v)| (k.as_str(), v.as_str())) .collect(); - assert_eq!(sprout_env.get("SPROUT_AGENT_MODEL"), Some(&"gpt-4o")); - assert_eq!(sprout_env.get("SPROUT_AGENT_PROVIDER"), Some(&"openai")); + assert_eq!(buzz_env.get("BUZZ_AGENT_MODEL"), Some(&"gpt-4o")); + assert_eq!(buzz_env.get("BUZZ_AGENT_PROVIDER"), Some(&"openai")); assert!( - !sprout_env.contains_key("GOOSE_MODEL"), - "sprout-agent persona must not emit GOOSE_MODEL" + !buzz_env.contains_key("GOOSE_MODEL"), + "buzz-agent persona must not emit GOOSE_MODEL" ); assert!( - !sprout_env.contains_key("GOOSE_PROVIDER"), - "sprout-agent persona must not emit GOOSE_PROVIDER" + !buzz_env.contains_key("GOOSE_PROVIDER"), + "buzz-agent persona must not emit GOOSE_PROVIDER" ); } diff --git a/crates/sprout-persona/tests/integration.rs b/crates/buzz-persona/tests/integration.rs similarity index 99% rename from crates/sprout-persona/tests/integration.rs rename to crates/buzz-persona/tests/integration.rs index 83f356e69..bea755993 100644 --- a/crates/sprout-persona/tests/integration.rs +++ b/crates/buzz-persona/tests/integration.rs @@ -1,4 +1,4 @@ -//! Integration tests for the sprout-persona crate. +//! Integration tests for the buzz-persona crate. //! //! These exercise the full pipeline: build a pack on disk → load it → //! parse personas → resolve defaults → merge config → validate. @@ -7,10 +7,10 @@ use std::fs; use std::path::Path; -use sprout_persona::pack; -use sprout_persona::persona; -use sprout_persona::resolve; -use sprout_persona::validate; +use buzz_persona::pack; +use buzz_persona::persona; +use buzz_persona::resolve; +use buzz_persona::validate; // ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/crates/sprout-proxy/Cargo.toml b/crates/buzz-proxy/Cargo.toml similarity index 87% rename from crates/sprout-proxy/Cargo.toml rename to crates/buzz-proxy/Cargo.toml index 770076acc..9b0ee4768 100644 --- a/crates/sprout-proxy/Cargo.toml +++ b/crates/buzz-proxy/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "sprout-proxy" +name = "buzz-proxy" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true repository.workspace = true -description = "Nostr client compatibility proxy for Sprout" +description = "Nostr client compatibility proxy for Buzz" [dependencies] -sprout-core = { workspace = true } +buzz-core = { workspace = true } nostr = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } @@ -32,5 +32,5 @@ tower-http = { workspace = true } tracing-subscriber = { workspace = true } [[bin]] -name = "sprout-proxy" +name = "buzz-proxy" path = "src/main.rs" diff --git a/crates/sprout-proxy/README.md b/crates/buzz-proxy/README.md similarity index 73% rename from crates/sprout-proxy/README.md rename to crates/buzz-proxy/README.md index 7ef7844f6..d98dbfa3e 100644 --- a/crates/sprout-proxy/README.md +++ b/crates/buzz-proxy/README.md @@ -1,9 +1,9 @@ -# sprout-proxy +# buzz-proxy -NIP-28 compatibility proxy for [Sprout](../../VISION.md). Lets standard Nostr clients (Coracle, Amethyst, nak, etc.) connect to a Sprout relay using familiar NIP-28 channel events. +NIP-28 compatibility proxy for [Buzz](../../VISION.md). Lets standard Nostr clients (Coracle, Amethyst, nak, etc.) connect to a Buzz relay using familiar NIP-28 channel events. ``` -Client (NIP-28) ←→ sprout-proxy :4869 ←→ sprout-relay :3000 +Client (NIP-28) ←→ buzz-proxy :4869 ←→ buzz-relay :3000 ``` **Supported NIPs:** NIP-01, NIP-11, NIP-28, NIP-42 @@ -22,15 +22,15 @@ just relay # starts Docker services + migrations automatically ### 2. Mint a proxy API token -The token's `--pubkey` must match the public key derived from `SPROUT_PROXY_SERVER_KEY`. +The token's `--pubkey` must match the public key derived from `BUZZ_PROXY_SERVER_KEY`. ```bash # Derive the public key from your server key -nak key public +nak key public # Mint the token with that pubkey -cargo run -p sprout-admin -- mint-token \ - --name "sprout-proxy" \ +cargo run -p buzz-admin -- mint-token \ + --name "buzz-proxy" \ --scopes "proxy:submit,channels:read,messages:read" \ --pubkey ``` @@ -38,12 +38,12 @@ cargo run -p sprout-admin -- mint-token \ ### 3. Configure environment ```bash -export SPROUT_UPSTREAM_URL=ws://localhost:3000 -export SPROUT_PROXY_BIND_ADDR=0.0.0.0:4869 -export SPROUT_PROXY_SERVER_KEY= -export SPROUT_PROXY_SALT=$(openssl rand -hex 32) -export SPROUT_PROXY_API_TOKEN= -export SPROUT_PROXY_ADMIN_SECRET= +export BUZZ_UPSTREAM_URL=ws://localhost:3000 +export BUZZ_PROXY_BIND_ADDR=0.0.0.0:4869 +export BUZZ_PROXY_SERVER_KEY= +export BUZZ_PROXY_SALT=$(openssl rand -hex 32) +export BUZZ_PROXY_API_TOKEN= +export BUZZ_PROXY_ADMIN_SECRET= ``` > Put these in `.env` at the repo root — `just` loads it automatically. @@ -61,7 +61,7 @@ Register a guest by their Nostr public key. They can then connect with any NIP-4 ```bash curl -X POST http://localhost:4869/admin/guests \ -H "Content-Type: application/json" \ - -H "Authorization: Bearer " \ + -H "Authorization: Bearer " \ -d '{"pubkey":"","channels":","}' ``` @@ -80,7 +80,7 @@ The client handles NIP-42 authentication automatically. No token in the URL. ```bash curl -X POST http://localhost:4869/admin/invite \ -H "Content-Type: application/json" \ - -H "Authorization: Bearer " \ + -H "Authorization: Bearer " \ -d '{"channels":"","hours":24,"max_uses":10}' ``` @@ -96,13 +96,13 @@ ws://localhost:4869?token= | Variable | Required | Default | Description | |----------|:--------:|---------|-------------| -| `SPROUT_UPSTREAM_URL` | ✅ | — | WebSocket URL of the Sprout relay | -| `SPROUT_PROXY_SERVER_KEY` | ✅ | — | Hex nsec for the proxy server keypair | -| `SPROUT_PROXY_SALT` | ✅ | — | Hex 32-byte salt for shadow key derivation (keep stable) | -| `SPROUT_PROXY_API_TOKEN` | ✅ | — | Sprout API token with `proxy:submit`, `channels:read`, and `messages:read` scopes | -| `SPROUT_PROXY_BIND_ADDR` | ❌ | `0.0.0.0:4869` | Listen address | -| `SPROUT_PROXY_ADMIN_SECRET` | ❌ | — | Bearer secret for `POST /admin/invite` (unset = dev mode, no auth) | -| `RUST_LOG` | ❌ | `sprout_proxy=info` | Log level | +| `BUZZ_UPSTREAM_URL` | ✅ | — | WebSocket URL of the Buzz relay | +| `BUZZ_PROXY_SERVER_KEY` | ✅ | — | Hex nsec for the proxy server keypair | +| `BUZZ_PROXY_SALT` | ✅ | — | Hex 32-byte salt for shadow key derivation (keep stable) | +| `BUZZ_PROXY_API_TOKEN` | ✅ | — | Buzz API token with `proxy:submit`, `channels:read`, and `messages:read` scopes | +| `BUZZ_PROXY_BIND_ADDR` | ❌ | `0.0.0.0:4869` | Listen address | +| `BUZZ_PROXY_ADMIN_SECRET` | ❌ | — | Bearer secret for `POST /admin/invite` (unset = dev mode, no auth) | +| `RUST_LOG` | ❌ | `buzz_proxy=info` | Log level | --- @@ -114,7 +114,7 @@ ws://localhost:4869?token= ### `POST /admin/invite` -Create an invite token. Requires `Authorization: Bearer ` (if secret is set). +Create an invite token. Requires `Authorization: Bearer ` (if secret is set). **Request:** ```json @@ -128,7 +128,7 @@ Create an invite token. Requires `Authorization: Bearer ", + "token": "buzz_invite_", "channels": ["", ""], "expires_at": "2026-03-12T22:00:00Z", "max_uses": 10 @@ -194,7 +194,7 @@ nak event -k 42 -c "Hello!" --tag "e=" --sec "ws://local | `"error: invite token not found"` | Token expired, used up, or proxy restarted | Create new token via `POST /admin/invite` | | `"auth-required: authentication timeout"` | Client didn't respond to AUTH challenge within 30s | Use a NIP-42-capable client | | `"error: channel not found"` | Channel created after proxy started | Restart proxy to refresh channel map | -| Connection drops immediately | Relay not running or wrong `SPROUT_UPSTREAM_URL` | Check `just relay` is running | +| Connection drops immediately | Relay not running or wrong `BUZZ_UPSTREAM_URL` | Check `just relay` is running | | No messages appearing | Wrong kind:40 event ID in subscription | Re-query kind:40 to get correct event ID | | Startup fails: "failed to initialize channel map" | Can't reach relay REST API | Check relay health and API token scopes | diff --git a/crates/sprout-proxy/src/channel_map.rs b/crates/buzz-proxy/src/channel_map.rs similarity index 99% rename from crates/sprout-proxy/src/channel_map.rs rename to crates/buzz-proxy/src/channel_map.rs index 0068a4d7d..83e9de49a 100644 --- a/crates/sprout-proxy/src/channel_map.rs +++ b/crates/buzz-proxy/src/channel_map.rs @@ -37,7 +37,7 @@ pub struct ChannelDto { /// All relevant information about a mapped channel. #[derive(Debug, Clone)] pub struct ChannelInfo { - /// The Sprout-internal UUID. + /// The Buzz-internal UUID. pub uuid: Uuid, /// Human-readable name. pub name: String, @@ -169,7 +169,7 @@ impl ChannelMap { // ─── Lookups ───────────────────────────────────────────────────────────── - /// Look up channel info by Sprout UUID. + /// Look up channel info by Buzz UUID. pub fn lookup_by_uuid(&self, uuid: &Uuid) -> Option { self.by_uuid.get(uuid).map(|r| r.clone()) } diff --git a/crates/sprout-proxy/src/error.rs b/crates/buzz-proxy/src/error.rs similarity index 100% rename from crates/sprout-proxy/src/error.rs rename to crates/buzz-proxy/src/error.rs diff --git a/crates/sprout-proxy/src/guest_store.rs b/crates/buzz-proxy/src/guest_store.rs similarity index 100% rename from crates/sprout-proxy/src/guest_store.rs rename to crates/buzz-proxy/src/guest_store.rs diff --git a/crates/sprout-proxy/src/invite.rs b/crates/buzz-proxy/src/invite.rs similarity index 100% rename from crates/sprout-proxy/src/invite.rs rename to crates/buzz-proxy/src/invite.rs diff --git a/crates/sprout-proxy/src/invite_store.rs b/crates/buzz-proxy/src/invite_store.rs similarity index 100% rename from crates/sprout-proxy/src/invite_store.rs rename to crates/buzz-proxy/src/invite_store.rs diff --git a/crates/sprout-proxy/src/kind_translator.rs b/crates/buzz-proxy/src/kind_translator.rs similarity index 74% rename from crates/sprout-proxy/src/kind_translator.rs rename to crates/buzz-proxy/src/kind_translator.rs index aa0000646..18dd901cf 100644 --- a/crates/sprout-proxy/src/kind_translator.rs +++ b/crates/buzz-proxy/src/kind_translator.rs @@ -1,4 +1,4 @@ -//! Kind translation between standard Nostr kinds and Sprout custom kinds. +//! Kind translation between standard Nostr kinds and Buzz custom kinds. //! //! # ⚠️ Architectural limitation //! @@ -6,17 +6,17 @@ //! Nostr event ID is `SHA-256([0, pubkey, created_at, kind, tags, content])`, so //! any kind mutation produces a different ID and a broken Schnorr signature. //! -//! This translator is intentionally designed for **Sprout-internal use only**, +//! This translator is intentionally designed for **Buzz-internal use only**, //! where events are re-signed by the proxy's shadow keypair after translation. //! It must never be used in a standard Nostr interop path where signature //! verification is expected to pass. -use sprout_core::kind::{ +use buzz_core::kind::{ KIND_DM_CREATED, KIND_NIP29_DELETE_EVENT, KIND_STREAM_MESSAGE, KIND_STREAM_MESSAGE_EDIT, KIND_STREAM_MESSAGE_V2, }; -/// Translates Nostr event kinds between standard and Sprout-internal values. +/// Translates Nostr event kinds between standard and Buzz-internal values. pub struct KindTranslator; impl KindTranslator { @@ -25,27 +25,27 @@ impl KindTranslator { Self } - /// Translate a standard Nostr kind to the equivalent Sprout kind. + /// Translate a standard Nostr kind to the equivalent Buzz kind. /// Unknown kinds pass through unchanged. /// /// # ⚠️ Lossy mapping — round-tripping is NOT lossless /// - /// Multiple standard Nostr kinds collapse onto the same Sprout kind. - /// This is intentional: Sprout's internal kind space is smaller than the + /// Multiple standard Nostr kinds collapse onto the same Buzz kind. + /// This is intentional: Buzz's internal kind space is smaller than the /// full Nostr kind space, and the proxy re-signs events anyway (see module /// doc), so the original kind is not preserved. /// - /// **Do not use `to_standard(to_sprout(k))` expecting to recover `k`.** + /// **Do not use `to_standard(to_buzz(k))` expecting to recover `k`.** /// The round-trip is only lossless for kinds that have a 1-to-1 mapping. /// - /// | Standard kind(s) | Sprout kind | Lossy? | + /// | Standard kind(s) | Buzz kind | Lossy? | /// |------------------------|---------------------------|--------| /// | 1, 40, 42 | `KIND_STREAM_MESSAGE` | ✅ yes | /// | 41, 44 | `KIND_STREAM_MESSAGE_EDIT`| ✅ yes | /// | 4 | `KIND_DM_CREATED` | no | /// | 43 | `KIND_NIP29_DELETE_EVENT` | no | /// | anything else | unchanged (pass-through) | no | - pub fn to_sprout(&self, standard_kind: u32) -> u32 { + pub fn to_buzz(&self, standard_kind: u32) -> u32 { match standard_kind { 1 => KIND_STREAM_MESSAGE, 4 => KIND_DM_CREATED, @@ -58,11 +58,11 @@ impl KindTranslator { } } - /// Translate a Sprout kind back to the canonical standard Nostr kind. + /// Translate a Buzz kind back to the canonical standard Nostr kind. /// Unknown kinds pass through unchanged. /// - /// Returns the **canonical** standard kind for each Sprout kind. Because - /// `to_sprout` is lossy (multiple standard kinds map to one Sprout kind), + /// Returns the **canonical** standard kind for each Buzz kind. Because + /// `to_buzz` is lossy (multiple standard kinds map to one Buzz kind), /// this function always returns the primary/canonical standard kind — it /// cannot recover the original kind if it was one of the secondary mappings. /// @@ -70,7 +70,7 @@ impl KindTranslator { /// channel message), not `1` or `40`, even if the event was originally /// kind 1 or 40. /// - /// | Sprout kind | Standard kind | Notes | + /// | Buzz kind | Standard kind | Notes | /// |----------------------------|---------------|---------------------------| /// | `KIND_STREAM_MESSAGE` | 42 | NIP-28 channel message | /// | `KIND_STREAM_MESSAGE_V2` | 42 | Rich format → plain 42 | @@ -78,8 +78,8 @@ impl KindTranslator { /// | `KIND_DM_CREATED` | 4 | Encrypted DM | /// | `KIND_NIP29_DELETE_EVENT` | 43 | NIP-29 delete | /// | anything else | unchanged | pass-through | - pub fn to_standard(&self, sprout_kind: u32) -> u32 { - match sprout_kind { + pub fn to_standard(&self, buzz_kind: u32) -> u32 { + match buzz_kind { k if k == KIND_STREAM_MESSAGE => 42, // NIP-28 channel message (was 1) k if k == KIND_STREAM_MESSAGE_V2 => 42, // Rich format → plain kind:42 k if k == KIND_STREAM_MESSAGE_EDIT => 41, @@ -91,7 +91,7 @@ impl KindTranslator { /// Returns `true` if `kind` has a non-identity mapping in either direction. pub fn is_translatable(&self, kind: u32) -> bool { - self.to_sprout(kind) != kind || self.to_standard(kind) != kind + self.to_buzz(kind) != kind || self.to_standard(kind) != kind } } @@ -104,21 +104,21 @@ impl Default for KindTranslator { #[cfg(test)] mod tests { use super::*; - use sprout_core::kind::{ + use buzz_core::kind::{ KIND_DM_CREATED, KIND_STREAM_MESSAGE, KIND_STREAM_MESSAGE_EDIT, KIND_STREAM_MESSAGE_V2, }; #[test] - fn standard_to_sprout() { + fn standard_to_buzz() { let t = KindTranslator::new(); - assert_eq!(t.to_sprout(1), KIND_STREAM_MESSAGE); - assert_eq!(t.to_sprout(4), KIND_DM_CREATED); - assert_eq!(t.to_sprout(40), KIND_STREAM_MESSAGE); - assert_eq!(t.to_sprout(41), KIND_STREAM_MESSAGE_EDIT); + assert_eq!(t.to_buzz(1), KIND_STREAM_MESSAGE); + assert_eq!(t.to_buzz(4), KIND_DM_CREATED); + assert_eq!(t.to_buzz(40), KIND_STREAM_MESSAGE); + assert_eq!(t.to_buzz(41), KIND_STREAM_MESSAGE_EDIT); } #[test] - fn sprout_to_standard() { + fn buzz_to_standard() { let t = KindTranslator::new(); assert_eq!(t.to_standard(KIND_STREAM_MESSAGE), 42); assert_eq!(t.to_standard(KIND_STREAM_MESSAGE_V2), 42); @@ -130,14 +130,14 @@ mod tests { fn stream_message_v2_round_trip() { let t = KindTranslator::new(); // kind:42 → KIND_STREAM_MESSAGE (lossy collapse), then back → 42 - assert_eq!(t.to_standard(t.to_sprout(42)), 42); + assert_eq!(t.to_standard(t.to_buzz(42)), 42); } #[test] fn unknown_kinds_pass_through() { let t = KindTranslator::new(); - assert_eq!(t.to_sprout(9999), 9999); - assert_eq!(t.to_sprout(0), 0); + assert_eq!(t.to_buzz(9999), 9999); + assert_eq!(t.to_buzz(0), 0); assert_eq!(t.to_standard(12345), 12345); assert_eq!(t.to_standard(0), 0); } diff --git a/crates/sprout-proxy/src/lib.rs b/crates/buzz-proxy/src/lib.rs similarity index 76% rename from crates/sprout-proxy/src/lib.rs rename to crates/buzz-proxy/src/lib.rs index d8535e266..2d62bd712 100644 --- a/crates/sprout-proxy/src/lib.rs +++ b/crates/buzz-proxy/src/lib.rs @@ -1,8 +1,8 @@ #![deny(unsafe_code)] #![warn(missing_docs)] -//! `sprout-proxy` — Guest relay proxy for Nostr client compatibility. +//! `buzz-proxy` — Guest relay proxy for Nostr client compatibility. //! -//! Translates standard Nostr kinds ↔ Sprout custom kinds, derives deterministic +//! Translates standard Nostr kinds ↔ Buzz custom kinds, derives deterministic //! shadow keypairs for external users, and authenticates guests via invite tokens. /// Bidirectional UUID ↔ NIP-28 kind:40 event ID mapping. @@ -15,13 +15,13 @@ pub mod guest_store; pub mod invite; /// Thread-safe invite token registry. pub mod invite_store; -/// Kind translation between standard Nostr and Sprout-internal kinds. +/// Kind translation between standard Nostr and Buzz-internal kinds. pub mod kind_translator; /// External-facing NIP-01 WebSocket server for standard Nostr clients. pub mod server; /// Deterministic shadow keypair derivation and caching. pub mod shadow_keys; -/// Event translation between Sprout internal format and NIP-28 standard format. +/// Event translation between Buzz internal format and NIP-28 standard format. pub mod translate; /// Upstream relay WebSocket client with NIP-42 auth and reconnect. pub mod upstream; diff --git a/crates/sprout-proxy/src/main.rs b/crates/buzz-proxy/src/main.rs similarity index 85% rename from crates/sprout-proxy/src/main.rs rename to crates/buzz-proxy/src/main.rs index 99d67965a..aaeb24a30 100644 --- a/crates/sprout-proxy/src/main.rs +++ b/crates/buzz-proxy/src/main.rs @@ -1,4 +1,4 @@ -//! sprout-proxy binary — NIP-28 guest relay proxy for standard Nostr clients. +//! buzz-proxy binary — NIP-28 guest relay proxy for standard Nostr clients. use std::sync::Arc; @@ -6,13 +6,13 @@ use nostr::prelude::*; use tokio::sync::{broadcast, mpsc}; use tracing::{error, info}; -use sprout_proxy::channel_map::ChannelMap; -use sprout_proxy::guest_store::GuestStore; -use sprout_proxy::invite_store::InviteStore; -use sprout_proxy::server::{self, ProxyState}; -use sprout_proxy::shadow_keys::ShadowKeyManager; -use sprout_proxy::translate::Translator; -use sprout_proxy::upstream::{UpstreamClient, UpstreamEvent}; +use buzz_proxy::channel_map::ChannelMap; +use buzz_proxy::guest_store::GuestStore; +use buzz_proxy::invite_store::InviteStore; +use buzz_proxy::server::{self, ProxyState}; +use buzz_proxy::shadow_keys::ShadowKeyManager; +use buzz_proxy::translate::Translator; +use buzz_proxy::upstream::{UpstreamClient, UpstreamEvent}; // ── Env helpers ─────────────────────────────────────────────────────────────── @@ -31,26 +31,26 @@ fn env_or(name: &str, default: &str) -> String { #[tokio::main] async fn main() { - // Init tracing — respects RUST_LOG; falls back to info for sprout_proxy and tower_http. + // Init tracing — respects RUST_LOG; falls back to info for buzz_proxy and tower_http. tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| "sprout_proxy=info,tower_http=info".into()), + .unwrap_or_else(|_| "buzz_proxy=info,tower_http=info".into()), ) .init(); // ── Parse env ───────────────────────────────────────────────────────────── - let upstream_url = env_required("SPROUT_UPSTREAM_URL"); - let bind_addr = env_or("SPROUT_PROXY_BIND_ADDR", "0.0.0.0:4869"); - let server_key_hex = env_required("SPROUT_PROXY_SERVER_KEY"); - let salt_hex = env_required("SPROUT_PROXY_SALT"); - let api_token = env_required("SPROUT_PROXY_API_TOKEN"); - let relay_pubkey = env_required("SPROUT_RELAY_PUBKEY").to_lowercase(); + let upstream_url = env_required("BUZZ_UPSTREAM_URL"); + let bind_addr = env_or("BUZZ_PROXY_BIND_ADDR", "0.0.0.0:4869"); + let server_key_hex = env_required("BUZZ_PROXY_SERVER_KEY"); + let salt_hex = env_required("BUZZ_PROXY_SALT"); + let api_token = env_required("BUZZ_PROXY_API_TOKEN"); + let relay_pubkey = env_required("BUZZ_RELAY_PUBKEY").to_lowercase(); // Validate relay pubkey is well-formed 64-char hex at startup. // Input is lowercased above, so mixed-case is accepted. if relay_pubkey.len() != 64 || !relay_pubkey.chars().all(|c| c.is_ascii_hexdigit()) { - eprintln!("error: SPROUT_RELAY_PUBKEY must be a 64-character hex string (32 bytes)"); + eprintln!("error: BUZZ_RELAY_PUBKEY must be a 64-character hex string (32 bytes)"); std::process::exit(1); } info!(relay_pubkey = %relay_pubkey, "relay pubkey configured for attribution trust"); @@ -58,7 +58,7 @@ async fn main() { // ── Parse server keypair ────────────────────────────────────────────────── let server_secret = SecretKey::from_hex(&server_key_hex).unwrap_or_else(|e| { - eprintln!("error: invalid SPROUT_PROXY_SERVER_KEY: {e}"); + eprintln!("error: invalid BUZZ_PROXY_SERVER_KEY: {e}"); std::process::exit(1); }); let server_keys = Keys::new(server_secret); @@ -67,7 +67,7 @@ async fn main() { // ── Parse salt ──────────────────────────────────────────────────────────── let salt = hex::decode(&salt_hex).unwrap_or_else(|e| { - eprintln!("error: invalid SPROUT_PROXY_SALT (must be hex): {e}"); + eprintln!("error: invalid BUZZ_PROXY_SALT (must be hex): {e}"); std::process::exit(1); }); @@ -164,11 +164,11 @@ async fn main() { // ── Read admin secret from env (optional) ───────────────────────────────── - let admin_secret = std::env::var("SPROUT_PROXY_ADMIN_SECRET").ok(); + let admin_secret = std::env::var("BUZZ_PROXY_ADMIN_SECRET").ok(); if admin_secret.is_some() { - info!("admin endpoint protected by SPROUT_PROXY_ADMIN_SECRET"); + info!("admin endpoint protected by BUZZ_PROXY_ADMIN_SECRET"); } else { - info!("admin endpoint running unauthenticated (dev mode) — set SPROUT_PROXY_ADMIN_SECRET to secure it"); + info!("admin endpoint running unauthenticated (dev mode) — set BUZZ_PROXY_ADMIN_SECRET to secure it"); } // ── Build proxy state ───────────────────────────────────────────────────── @@ -176,7 +176,7 @@ async fn main() { // Relay URL for NIP-42 relay tag validation. Prefer explicit env var // (e.g. "wss://proxy.example.com") over the derived bind address fallback. let relay_url = - std::env::var("SPROUT_PROXY_RELAY_URL").unwrap_or_else(|_| format!("ws://{}", bind_addr)); + std::env::var("BUZZ_PROXY_RELAY_URL").unwrap_or_else(|_| format!("ws://{}", bind_addr)); let state = ProxyState { channel_map: channel_map.clone(), @@ -195,7 +195,7 @@ async fn main() { // ── Bind listener ───────────────────────────────────────────────────────── - info!("sprout-proxy starting on {bind_addr} → upstream {upstream_url}"); + info!("buzz-proxy starting on {bind_addr} → upstream {upstream_url}"); let listener = tokio::net::TcpListener::bind(&bind_addr) .await @@ -217,7 +217,7 @@ async fn main() { } } - info!("sprout-proxy shut down"); + info!("buzz-proxy shut down"); } // ── Graceful shutdown ───────────────────────────────────────────────────────── diff --git a/crates/sprout-proxy/src/server.rs b/crates/buzz-proxy/src/server.rs similarity index 99% rename from crates/sprout-proxy/src/server.rs rename to crates/buzz-proxy/src/server.rs index 7bae3c42c..fa42dbd23 100644 --- a/crates/sprout-proxy/src/server.rs +++ b/crates/buzz-proxy/src/server.rs @@ -40,7 +40,7 @@ pub struct ProxyState { pub guest_store: Arc, /// In-memory invite token registry (temporary access via bearer token). pub invite_store: Arc, - /// Event translator: NIP-28 ↔ Sprout internal format. + /// Event translator: NIP-28 ↔ Buzz internal format. pub translator: Arc, /// Upstream relay client — used to send events, REQs, and CLOSEs. pub upstream: Arc, @@ -74,7 +74,7 @@ pub struct WsParams { /// - `DELETE /admin/guests` — Revoke a guest pubkey /// - `GET /admin/guests` — List all registered guests /// -/// All `/admin/*` routes are protected by `SPROUT_PROXY_ADMIN_SECRET` if set. +/// All `/admin/*` routes are protected by `BUZZ_PROXY_ADMIN_SECRET` if set. pub fn router(state: ProxyState) -> Router { Router::new() .route("/", get(root_handler)) @@ -94,7 +94,7 @@ pub fn router(state: ProxyState) -> Router { /// /// Uses `axum::extract::Request` to manually attempt the WS upgrade so that /// plain HTTP GET requests (NIP-11 clients, browser visits) are not rejected -/// by the extractor. Mirrors the pattern used in `sprout-relay/src/router.rs`. +/// by the extractor. Mirrors the pattern used in `buzz-relay/src/router.rs`. async fn root_handler( State(state): State, headers: HeaderMap, @@ -124,10 +124,10 @@ async fn root_handler( fn nip11_response() -> impl IntoResponse { let nip11 = serde_json::json!({ - "name": "sprout-proxy", - "description": "Sprout NIP-28 guest proxy for standard Nostr clients", + "name": "buzz-proxy", + "description": "Buzz NIP-28 guest proxy for standard Nostr clients", "supported_nips": [1, 11, 28, 42], - "software": "sprout-proxy", + "software": "buzz-proxy", "version": env!("CARGO_PKG_VERSION"), "limitation": { "auth_required": true @@ -858,7 +858,7 @@ async fn create_invite( .into_response(); } - let token_str = format!("sprout_invite_{}", Uuid::new_v4().simple()); + let token_str = format!("buzz_invite_{}", Uuid::new_v4().simple()); let expires_at = chrono::Utc::now() + chrono::Duration::hours(req.hours as i64); let token = crate::InviteToken::new(&token_str, channel_ids.clone(), expires_at, req.max_uses); @@ -1056,10 +1056,10 @@ mod tests { shadow_keys, channel_map.clone(), "http://localhost:3000", - "sprout_test", + "buzz_test", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", )); - let upstream = Arc::new(UpstreamClient::new("ws://localhost:3000", "sprout_test")); + let upstream = Arc::new(UpstreamClient::new("ws://localhost:3000", "buzz_test")); ProxyState { channel_map, guest_store, diff --git a/crates/sprout-proxy/src/shadow_keys.rs b/crates/buzz-proxy/src/shadow_keys.rs similarity index 100% rename from crates/sprout-proxy/src/shadow_keys.rs rename to crates/buzz-proxy/src/shadow_keys.rs diff --git a/crates/sprout-proxy/src/translate.rs b/crates/buzz-proxy/src/translate.rs similarity index 94% rename from crates/sprout-proxy/src/translate.rs rename to crates/buzz-proxy/src/translate.rs index 6756782da..7dd0834c7 100644 --- a/crates/sprout-proxy/src/translate.rs +++ b/crates/buzz-proxy/src/translate.rs @@ -1,15 +1,15 @@ -//! Event translation between Sprout internal format and NIP-28 standard format. +//! Event translation between Buzz internal format and NIP-28 standard format. //! //! # Overview //! -//! The proxy sits between standard Nostr clients (NIP-28) and the Sprout relay. -//! Sprout stores messages as kind:9 events with `#h ` channel tags. +//! The proxy sits between standard Nostr clients (NIP-28) and the Buzz relay. +//! Buzz stores messages as kind:9 events with `#h ` channel tags. //! NIP-28 clients expect kind:42 events with `#e ` channel tags. //! //! This module handles bidirectional translation: //! -//! - **Outbound** (Sprout → client): kind:9 + `#h(uuid)` → kind:42 + `#e(event_id)` -//! - **Inbound** (client → Sprout): kind:42 + `#e(event_id)` → kind:9 + `#h(uuid)` +//! - **Outbound** (Buzz → client): kind:9 + `#h(uuid)` → kind:42 + `#e(event_id)` +//! - **Inbound** (client → Buzz): kind:42 + `#e(event_id)` → kind:9 + `#h(uuid)` //! //! All translated events are re-signed with deterministic shadow keys so that //! each external user maps to a consistent shadow pubkey across sessions. @@ -17,12 +17,12 @@ use std::sync::Arc; use std::time::Duration; -use moka::sync::Cache; -use nostr::prelude::*; -use sprout_core::kind::{ +use buzz_core::kind::{ event_kind_u32, KIND_DELETION, KIND_REACTION, KIND_STREAM_MESSAGE, KIND_STREAM_MESSAGE_EDIT, KIND_STREAM_MESSAGE_V2, }; +use moka::sync::Cache; +use nostr::prelude::*; use uuid::Uuid; use crate::channel_map::{ChannelInfo, ChannelMap}; @@ -32,7 +32,7 @@ use crate::ProxyError; // ─── Translator ────────────────────────────────────────────────────────────── -/// Translates events and filters between Sprout internal format and NIP-28 +/// Translates events and filters between Buzz internal format and NIP-28 /// standard format. /// /// All translated events are re-signed with shadow keys to preserve per-user @@ -89,7 +89,7 @@ impl Translator { } } -// ─── Outbound translation (Sprout → NIP-28 client) ─────────────────────────── +// ─── Outbound translation (Buzz → NIP-28 client) ─────────────────────────── impl Translator { fn cache_event_mapping(&self, internal_event_id: &str, external_event_id: &str) { @@ -360,7 +360,7 @@ impl Translator { Ok(Some(translated)) } - /// Translate a Sprout event to NIP-28 format for delivery to external clients. + /// Translate a Buzz event to NIP-28 format for delivery to external clients. /// /// Returns `Ok(Some(event))` on success, `Ok(None)` if the event should be /// silently dropped (unknown kind, no channel tag, etc.), or an error if the @@ -394,10 +394,10 @@ impl Translator { } } -// ─── Inbound translation (NIP-28 client → Sprout) ──────────────────────────── +// ─── Inbound translation (NIP-28 client → Buzz) ──────────────────────────── impl Translator { - /// Translate a NIP-28 event from an external client into Sprout format. + /// Translate a NIP-28 event from an external client into Buzz format. /// /// # Translation rules /// @@ -496,16 +496,16 @@ impl Translator { } // Translate kind number. - let sprout_kind = self.kind_translator.to_sprout(kind_u32); + let buzz_kind = self.kind_translator.to_buzz(kind_u32); // Re-sign with the shadow key for the external user. let shadow_keys = self.shadow_keys.get_or_create(external_pubkey)?; - // SAFETY: sprout_kind is derived from KindTranslator which maps to known Sprout kinds (9, 40002, 40003) that fit in u16 + // SAFETY: buzz_kind is derived from KindTranslator which maps to known Buzz kinds (9, 40002, 40003) that fit in u16 let translated = EventBuilder::new( Kind::Custom( - u16::try_from(sprout_kind) - .expect("SAFETY: sprout kind values (9, 40002, 40003) always fit in u16"), + u16::try_from(buzz_kind) + .expect("SAFETY: buzz kind values (9, 40002, 40003) always fit in u16"), ), &event.content, ) @@ -665,13 +665,13 @@ impl Translator { // ─── Filter translation ─────────────────────────────────────────────────────── impl Translator { - /// Translate a NIP-28 REQ filter to Sprout format. + /// Translate a NIP-28 REQ filter to Buzz format. /// /// - kind:42 / kind:1 → kind:9 /// - Injects `#h` tag filters from `allowed_channels` so the client can /// only receive events from channels they have access to. /// - /// The returned filter is ready to be forwarded to the upstream Sprout relay. + /// The returned filter is ready to be forwarded to the upstream Buzz relay. pub fn translate_filter_inbound(&self, filter: &Filter, allowed_channels: &[Uuid]) -> Filter { // Start with a clone and rebuild the kinds set. let mut f = filter.clone(); @@ -681,11 +681,10 @@ impl Translator { .iter() .map(|k| { let k_u32 = k.as_u16() as u32; - let sprout_k = self.kind_translator.to_sprout(k_u32); - // SAFETY: sprout kind values (9, 40002, 40003) always fit in u16 + let buzz_k = self.kind_translator.to_buzz(k_u32); + // SAFETY: buzz kind values (9, 40002, 40003) always fit in u16 Kind::Custom( - u16::try_from(sprout_k) - .expect("SAFETY: sprout kind values always fit in u16"), + u16::try_from(buzz_k).expect("SAFETY: buzz kind values always fit in u16"), ) }) .collect(); @@ -695,12 +694,12 @@ impl Translator { // Check for client-supplied #e channel filters and translate to #h UUIDs. // - // NIP-28 clients filter by channel using `#e `. Sprout + // NIP-28 clients filter by channel using `#e `. Buzz // uses `#h ` instead. If the client specified `#e` values, resolve // them to UUIDs (intersected with allowed_channels) and use those for the // `#h` injection. The `#e` filter must be removed from the translated // filter — if both `#e` and `#h` were present, the relay would AND them, - // and since Sprout events carry `#h` but not `#e`, zero events would match. + // and since Buzz events carry `#h` but not `#e`, zero events would match. let e_tag_key = SingleLetterTag::lowercase(Alphabet::E); let had_e_filter = f.generic_tags.contains_key(&e_tag_key); let client_channel_uuids: Vec = @@ -715,7 +714,7 @@ impl Translator { Vec::new() }; - // Remove the #e tag filter — Sprout uses #h, not #e. + // Remove the #e tag filter — Buzz uses #h, not #e. f.generic_tags.remove(&e_tag_key); // Inject #h tag constraints from the allowed channel list. @@ -765,7 +764,7 @@ fn extract_plain_text(content: &str) -> String { mod tests { use super::*; use crate::channel_map::{ChannelDto, ChannelMap}; - use sprout_core::kind::KIND_STREAM_MESSAGE; + use buzz_core::kind::KIND_STREAM_MESSAGE; // ── Test fixtures ──────────────────────────────────────────────────────── @@ -798,7 +797,7 @@ mod tests { shadow_mgr, channel_map.clone(), "http://localhost:3000", - "sprout_test", + "buzz_test", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", ); @@ -825,14 +824,13 @@ mod tests { // Build a synthetic kind:9 event with an #h tag. let h_tag = Tag::parse(["h", TEST_UUID]).unwrap(); - let sprout_event = - EventBuilder::new(Kind::Custom(KIND_STREAM_MESSAGE as u16), "hello world") - .tags([h_tag]) - .sign_with_keys(&author_keys) - .unwrap(); + let buzz_event = EventBuilder::new(Kind::Custom(KIND_STREAM_MESSAGE as u16), "hello world") + .tags([h_tag]) + .sign_with_keys(&author_keys) + .unwrap(); let result = translator - .translate_outbound(&sprout_event, &allowed()) + .translate_outbound(&buzz_event, &allowed()) .await .expect("outbound translation must not error"); @@ -927,13 +925,13 @@ mod tests { let author_keys = Keys::generate(); let h_tag = Tag::parse(["h", TEST_UUID]).unwrap(); - let sprout_event = EventBuilder::new(Kind::Custom(KIND_STREAM_MESSAGE as u16), "secret") + let buzz_event = EventBuilder::new(Kind::Custom(KIND_STREAM_MESSAGE as u16), "secret") .tags([h_tag]) .sign_with_keys(&author_keys) .unwrap(); let result = translator - .translate_outbound(&sprout_event, &no_channels()) + .translate_outbound(&buzz_event, &no_channels()) .await; assert!( @@ -976,14 +974,13 @@ mod tests { let v2_content = r#"{"text":"hello v2","attachments":[]}"#; let h_tag = Tag::parse(["h", TEST_UUID]).unwrap(); - let sprout_event = - EventBuilder::new(Kind::Custom(KIND_STREAM_MESSAGE_V2 as u16), v2_content) - .tags([h_tag]) - .sign_with_keys(&author_keys) - .unwrap(); + let buzz_event = EventBuilder::new(Kind::Custom(KIND_STREAM_MESSAGE_V2 as u16), v2_content) + .tags([h_tag]) + .sign_with_keys(&author_keys) + .unwrap(); let translated = translator - .translate_outbound(&sprout_event, &allowed()) + .translate_outbound(&buzz_event, &allowed()) .await .unwrap() .expect("should produce translated event"); @@ -1045,7 +1042,7 @@ mod tests { // The #e filter must be removed from the translated filter. assert!( !translated.generic_tags.contains_key(&e_tag_key), - "#e tag filter must be removed from translated filter (would cause zero matches on Sprout relay)" + "#e tag filter must be removed from translated filter (would cause zero matches on Buzz relay)" ); // The #h filter must be present and contain the channel UUID. @@ -1179,7 +1176,7 @@ mod tests { let channel_h_tag = Tag::parse(["h", TEST_UUID]).unwrap(); let other_h_tag = Tag::parse(["h", "some-other-value"]).unwrap(); - let sprout_event = EventBuilder::new( + let buzz_event = EventBuilder::new( Kind::Custom(KIND_STREAM_MESSAGE as u16), "message with extra h tag", ) @@ -1188,7 +1185,7 @@ mod tests { .unwrap(); let result = translator - .translate_outbound(&sprout_event, &allowed()) + .translate_outbound(&buzz_event, &allowed()) .await .expect("outbound translation must not error"); @@ -1248,7 +1245,7 @@ mod tests { #[test] fn inbound_translates_edit_message() { - use sprout_core::kind::KIND_STREAM_MESSAGE_EDIT; + use buzz_core::kind::KIND_STREAM_MESSAGE_EDIT; let (translator, kind40_event_id) = make_translator(); let external_keys = Keys::generate(); @@ -1309,13 +1306,13 @@ mod tests { #[tokio::test] async fn outbound_translates_edit_message() { - use sprout_core::kind::KIND_STREAM_MESSAGE_EDIT; + use buzz_core::kind::KIND_STREAM_MESSAGE_EDIT; let (translator, _) = make_translator(); let author_keys = Keys::generate(); let h_tag = Tag::parse(["h", TEST_UUID]).unwrap(); - let sprout_event = EventBuilder::new( + let buzz_event = EventBuilder::new( Kind::Custom(KIND_STREAM_MESSAGE_EDIT as u16), "edited content", ) @@ -1324,7 +1321,7 @@ mod tests { .unwrap(); let result = translator - .translate_outbound(&sprout_event, &allowed()) + .translate_outbound(&buzz_event, &allowed()) .await .expect("outbound translation of edit must not error"); @@ -1400,7 +1397,7 @@ mod tests { let (translator, _) = make_translator(); let author_keys = Keys::generate(); - // A kind:9999 event (unknown Sprout kind) should be dropped. + // A kind:9999 event (unknown Buzz kind) should be dropped. let h_tag = Tag::parse(["h", TEST_UUID]).unwrap(); let event = EventBuilder::new(Kind::Custom(9999), "internal stuff") .tags([h_tag]) diff --git a/crates/sprout-proxy/src/upstream.rs b/crates/buzz-proxy/src/upstream.rs similarity index 97% rename from crates/sprout-proxy/src/upstream.rs rename to crates/buzz-proxy/src/upstream.rs index c55a44c63..ba0e6acbb 100644 --- a/crates/sprout-proxy/src/upstream.rs +++ b/crates/buzz-proxy/src/upstream.rs @@ -1,6 +1,6 @@ -//! Upstream relay client — single persistent WebSocket connection to the Sprout relay. +//! Upstream relay client — single persistent WebSocket connection to the Buzz relay. //! -//! Maintains one authenticated connection to the upstream Sprout relay, authenticated +//! Maintains one authenticated connection to the upstream Buzz relay, authenticated //! via a `proxy:submit` API token. Handles NIP-42 auth automatically and reconnects //! with exponential backoff on disconnect. @@ -47,18 +47,18 @@ struct Inner { } /// Manages a single persistent, authenticated WebSocket connection to the upstream -/// Sprout relay. +/// Buzz relay. /// /// Clone-friendly: all state is behind an `Arc`. /// /// # Usage /// /// ```no_run -/// # use sprout_proxy::upstream::UpstreamClient; +/// # use buzz_proxy::upstream::UpstreamClient; /// # use tokio::sync::mpsc; /// # async fn example() { /// let (inbound_tx, mut inbound_rx) = mpsc::channel(256); -/// let client = UpstreamClient::new("ws://localhost:3000", "sprout_mytoken123"); +/// let client = UpstreamClient::new("ws://localhost:3000", "buzz_mytoken123"); /// /// // Spawn the connection loop in the background. /// let client2 = client.clone(); @@ -79,8 +79,8 @@ pub struct UpstreamClient { impl UpstreamClient { /// Create a new [`UpstreamClient`]. /// - /// `relay_url` — WebSocket URL of the upstream Sprout relay (e.g. `ws://localhost:3000`). - /// `api_token` — A `sprout_*` API token with the `proxy:submit` scope. + /// `relay_url` — WebSocket URL of the upstream Buzz relay (e.g. `ws://localhost:3000`). + /// `api_token` — A `buzz_*` API token with the `proxy:submit` scope. pub fn new(relay_url: impl Into, api_token: impl Into) -> Self { Self::with_keys(relay_url, api_token, Keys::generate()) } @@ -443,7 +443,7 @@ mod tests { #[test] fn new_client_starts_disconnected() { - let client = UpstreamClient::new("ws://localhost:3000", "sprout_test"); + let client = UpstreamClient::new("ws://localhost:3000", "buzz_test"); assert!(!client.is_connected()); } @@ -451,7 +451,7 @@ mod tests { fn send_methods_queue_correctly_formatted_json() { let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async { - let client = UpstreamClient::new("ws://localhost:3000", "sprout_test"); + let client = UpstreamClient::new("ws://localhost:3000", "buzz_test"); // Queue an EVENT message. let keys = Keys::generate(); @@ -502,7 +502,7 @@ mod tests { rt.block_on(async { let inner = Inner { relay_url: "ws://localhost:3000".into(), - api_token: "sprout_mytoken".into(), + api_token: "buzz_mytoken".into(), auth_keys: Keys::generate(), connected: RwLock::new(false), auth_event_id: tokio::sync::Mutex::new(None), @@ -522,7 +522,7 @@ mod tests { msg.contains("test-challenge-abc"), "expected challenge in: {msg}" ); - assert!(msg.contains("sprout_mytoken"), "expected token in: {msg}"); + assert!(msg.contains("buzz_mytoken"), "expected token in: {msg}"); assert!( msg.contains("ws://localhost:3000"), "expected relay url in: {msg}" @@ -536,7 +536,7 @@ mod tests { rt.block_on(async { let inner = Inner { relay_url: "ws://localhost:3000".into(), - api_token: "sprout_mytoken".into(), + api_token: "buzz_mytoken".into(), auth_keys: Keys::generate(), connected: RwLock::new(false), auth_event_id: tokio::sync::Mutex::new(None), @@ -572,7 +572,7 @@ mod tests { fn send_req_tracks_subscription() { let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async { - let client = UpstreamClient::new("ws://localhost:3000", "sprout_test"); + let client = UpstreamClient::new("ws://localhost:3000", "buzz_test"); assert_eq!(client.inner.active_subs.len(), 0); diff --git a/crates/sprout-pubsub/Cargo.toml b/crates/buzz-pubsub/Cargo.toml similarity index 84% rename from crates/sprout-pubsub/Cargo.toml rename to crates/buzz-pubsub/Cargo.toml index 56329b20b..2ee2d6b47 100644 --- a/crates/sprout-pubsub/Cargo.toml +++ b/crates/buzz-pubsub/Cargo.toml @@ -1,15 +1,15 @@ [package] -name = "sprout-pubsub" +name = "buzz-pubsub" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true repository.workspace = true -description = "Redis pub/sub fan-out, presence, and typing indicators for Sprout" +description = "Redis pub/sub fan-out, presence, and typing indicators for Buzz" [dependencies] -sprout-core = { workspace = true } -sprout-auth = { workspace = true } +buzz-core = { workspace = true } +buzz-auth = { workspace = true } redis = { workspace = true } deadpool-redis = { workspace = true } tokio = { workspace = true } diff --git a/crates/sprout-pubsub/src/error.rs b/crates/buzz-pubsub/src/error.rs similarity index 100% rename from crates/sprout-pubsub/src/error.rs rename to crates/buzz-pubsub/src/error.rs diff --git a/crates/sprout-pubsub/src/lib.rs b/crates/buzz-pubsub/src/lib.rs similarity index 97% rename from crates/sprout-pubsub/src/lib.rs rename to crates/buzz-pubsub/src/lib.rs index 670b7a3db..cefd12fe7 100644 --- a/crates/sprout-pubsub/src/lib.rs +++ b/crates/buzz-pubsub/src/lib.rs @@ -1,16 +1,16 @@ #![deny(unsafe_code)] #![warn(missing_docs)] -//! `sprout-pubsub` — Redis pub/sub fan-out, presence tracking, and typing indicators. +//! `buzz-pubsub` — Redis pub/sub fan-out, presence tracking, and typing indicators. //! //! # Architecture //! //! ```text -//! sprout-relay process +//! buzz-relay process //! │ //! ├── deadpool-redis pool → PUBLISH, SET, ZADD, etc. //! │ //! └── dedicated redis::aio::PubSub connection (NOT from pool) -//! └── PSUBSCRIBE sprout:channel:* +//! └── PSUBSCRIBE buzz:channel:* //! └── run_subscriber() → broadcast::channel(4096) → N WS receivers //! ``` //! @@ -66,7 +66,7 @@ impl PubSubConfig { } } -/// Central pub/sub manager for a Sprout relay instance. +/// Central pub/sub manager for a Buzz relay instance. pub struct PubSubManager { pool: deadpool_redis::Pool, /// Redis URL used by the reconnect loop to re-establish pub/sub connections. diff --git a/crates/sprout-pubsub/src/presence.rs b/crates/buzz-pubsub/src/presence.rs similarity index 95% rename from crates/sprout-pubsub/src/presence.rs rename to crates/buzz-pubsub/src/presence.rs index 9bb9b6f22..65567b557 100644 --- a/crates/sprout-pubsub/src/presence.rs +++ b/crates/buzz-pubsub/src/presence.rs @@ -1,6 +1,6 @@ //! Presence tracking — online/away status with TTL. //! -//! Stored as `SET sprout:presence:{pubkey_hex} "online" EX 90`. +//! Stored as `SET buzz:presence:{pubkey_hex} "online" EX 90`. //! TTL is 3x the 30s heartbeat interval so a single missed heartbeat doesn't //! cause presence flap. Clean disconnect deletes immediately. @@ -15,7 +15,7 @@ pub const PRESENCE_TTL_SECS: u64 = 90; /// Returns the Redis key for the presence entry of `pubkey`. pub fn presence_key(pubkey: &PublicKey) -> String { - format!("sprout:presence:{}", pubkey.to_hex()) + format!("buzz:presence:{}", pubkey.to_hex()) } /// Sets presence status for `pubkey` with a [`PRESENCE_TTL_SECS`]-second TTL. @@ -88,8 +88,8 @@ mod tests { fn test_presence_key_format() { let pubkey = make_pubkey(); let key = presence_key(&pubkey); - assert!(key.starts_with("sprout:presence:")); - let hex_part = key.strip_prefix("sprout:presence:").unwrap(); + assert!(key.starts_with("buzz:presence:")); + let hex_part = key.strip_prefix("buzz:presence:").unwrap(); assert_eq!(hex_part.len(), 64); assert!(hex_part.chars().all(|c| c.is_ascii_hexdigit())); } diff --git a/crates/sprout-pubsub/src/publisher.rs b/crates/buzz-pubsub/src/publisher.rs similarity index 94% rename from crates/sprout-pubsub/src/publisher.rs rename to crates/buzz-pubsub/src/publisher.rs index 60b711f2c..ea5d15166 100644 --- a/crates/sprout-pubsub/src/publisher.rs +++ b/crates/buzz-pubsub/src/publisher.rs @@ -8,7 +8,7 @@ use crate::error::PubSubError; /// Returns the Redis pub/sub channel key for `channel_id`. pub fn channel_key(channel_id: Uuid) -> String { - format!("sprout:channel:{}", channel_id) + format!("buzz:channel:{}", channel_id) } /// Returns the number of subscribers that received the message. diff --git a/crates/sprout-pubsub/src/rate_limiter.rs b/crates/buzz-pubsub/src/rate_limiter.rs similarity index 90% rename from crates/sprout-pubsub/src/rate_limiter.rs rename to crates/buzz-pubsub/src/rate_limiter.rs index 9b8148781..e466893aa 100644 --- a/crates/sprout-pubsub/src/rate_limiter.rs +++ b/crates/buzz-pubsub/src/rate_limiter.rs @@ -1,6 +1,6 @@ //! Redis-backed rate limiter using atomic Lua script (INCR + EXPIRE). //! -//! Implements the [`RateLimiter`] trait from `sprout-auth`. +//! Implements the [`RateLimiter`] trait from `buzz-auth`. //! Uses a single Lua script to atomically INCR and conditionally EXPIRE, //! eliminating the crash window where a key could exist without a TTL. //! @@ -9,12 +9,12 @@ use std::net::IpAddr; -use nostr::PublicKey; -use redis::Script; -use sprout_auth::{ +use buzz_auth::{ error::AuthError, rate_limit::{LimitType, RateLimitResult, RateLimiter}, }; +use nostr::PublicKey; +use redis::Script; /// Atomically INCR the key, set EXPIRE on first call, and return (count, ttl). /// @@ -79,8 +79,8 @@ async fn run_rate_limit( /// Redis-backed rate limiter using fixed-window counters. /// -/// Each key is `sprout:ratelimit::` (pubkey) or -/// `sprout:ratelimit:ip::conn` (IP). The counter and its TTL are managed +/// Each key is `buzz:ratelimit::` (pubkey) or +/// `buzz:ratelimit:ip::conn` (IP). The counter and its TTL are managed /// atomically via a Lua script to prevent keys from persisting without expiry. pub struct RedisRateLimiter { pool: deadpool_redis::Pool, @@ -101,7 +101,7 @@ impl RateLimiter for RedisRateLimiter { window_secs: u64, limit: u64, ) -> Result { - let key = sprout_auth::rate_limit::rate_limit_key(pubkey, &limit_type); + let key = buzz_auth::rate_limit::rate_limit_key(pubkey, &limit_type); run_rate_limit(&self.pool, &key, window_secs, limit).await } @@ -111,7 +111,7 @@ impl RateLimiter for RedisRateLimiter { window_secs: u64, limit: u64, ) -> Result { - let key = sprout_auth::rate_limit::ip_rate_limit_key(ip); + let key = buzz_auth::rate_limit::ip_rate_limit_key(ip); run_rate_limit(&self.pool, &key, window_secs, limit).await } } diff --git a/crates/sprout-pubsub/src/subscriber.rs b/crates/buzz-pubsub/src/subscriber.rs similarity index 94% rename from crates/sprout-pubsub/src/subscriber.rs rename to crates/buzz-pubsub/src/subscriber.rs index 3031df6dc..120d9538e 100644 --- a/crates/sprout-pubsub/src/subscriber.rs +++ b/crates/buzz-pubsub/src/subscriber.rs @@ -12,7 +12,7 @@ const BACKOFF_INITIAL_SECS: u64 = 1; /// Maximum reconnect backoff (30 seconds). const BACKOFF_MAX_SECS: u64 = 30; -/// Pattern-subscribes to `sprout:channel:*` and forwards events to broadcast. +/// Pattern-subscribes to `buzz:channel:*` and forwards events to broadcast. /// /// Runs a reconnect loop with exponential backoff (1s → 2s → 4s → … → 30s max). /// Logs `error!` on disconnect and `info!` on successful reconnect. @@ -53,9 +53,9 @@ async fn connect_and_subscribe( let client = redis::Client::open(redis_url)?; let mut conn = client.get_async_pubsub().await?; - conn.psubscribe("sprout:channel:*").await?; + conn.psubscribe("buzz:channel:*").await?; - tracing::info!("Redis pub/sub subscriber connected — listening on sprout:channel:*"); + tracing::info!("Redis pub/sub subscriber connected — listening on buzz:channel:*"); // Note: backoff is NOT reset here on connect. It resets in the outer loop // only after this function returns Ok(()) — i.e., after the connection ran @@ -74,7 +74,7 @@ async fn connect_and_subscribe( let channel_name = msg.get_channel_name(); let channel_id = channel_name - .strip_prefix("sprout:channel:") + .strip_prefix("buzz:channel:") .and_then(|s| Uuid::parse_str(s).ok()); let channel_id = match channel_id { diff --git a/crates/sprout-relay/Cargo.toml b/crates/buzz-relay/Cargo.toml similarity index 74% rename from crates/sprout-relay/Cargo.toml rename to crates/buzz-relay/Cargo.toml index b03c7d3ad..e28389080 100644 --- a/crates/sprout-relay/Cargo.toml +++ b/crates/buzz-relay/Cargo.toml @@ -1,28 +1,28 @@ [package] -name = "sprout-relay" -default-run = "sprout-relay" +name = "buzz-relay" +default-run = "buzz-relay" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true repository.workspace = true -description = "WebSocket relay server for the Sprout communications platform" +description = "WebSocket relay server for the Buzz communications platform" [[bin]] -name = "sprout-relay" +name = "buzz-relay" path = "src/main.rs" [[bin]] -name = "sprout-reindex-kind0" +name = "buzz-reindex-kind0" path = "src/bin/reindex_kind0.rs" [dependencies] -sprout-core = { workspace = true } -sprout-db = { workspace = true } -sprout-auth = { workspace = true } -sprout-pubsub = { workspace = true } -sprout-audit = { workspace = true } -sprout-search = { workspace = true } +buzz-core = { workspace = true } +buzz-db = { workspace = true } +buzz-auth = { workspace = true } +buzz-pubsub = { workspace = true } +buzz-audit = { workspace = true } +buzz-search = { workspace = true } axum = { workspace = true } tokio = { workspace = true } tokio-util = { workspace = true } @@ -43,9 +43,9 @@ deadpool-redis = { workspace = true } redis = { workspace = true } sqlx = { workspace = true } base64 = "0.22" -sprout-sdk = { workspace = true } -sprout-workflow = { workspace = true, features = ["reqwest"] } -sprout-media = { workspace = true } +buzz-sdk = { workspace = true } +buzz-workflow = { workspace = true, features = ["reqwest"] } +buzz-media = { workspace = true } s3 = { version = "0.37", package = "rust-s3", default-features = false, features = ["tokio-rustls-tls", "fail-on-err", "tags"] } tempfile = "3" bytes = "1" @@ -62,11 +62,11 @@ metrics = { workspace = true } metrics-exporter-prometheus = { workspace = true } [features] -dev = ["sprout-auth/dev"] +dev = ["buzz-auth/dev"] [dev-dependencies] mesh-llm-sdk = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82", package = "mesh-llm-sdk", default-features = false, features = ["client", "serving"] } mesh-llm-host-runtime = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82", package = "mesh-llm-host-runtime", default-features = false, features = ["dynamic-native-runtime"] } -sprout-core = { workspace = true, features = ["test-utils"] } -sprout-auth = { workspace = true, features = ["dev"] } +buzz-core = { workspace = true, features = ["test-utils"] } +buzz-auth = { workspace = true, features = ["dev"] } reqwest = { workspace = true } diff --git a/crates/sprout-relay/examples/mesh_serve_client_smoke.rs b/crates/buzz-relay/examples/mesh_serve_client_smoke.rs similarity index 97% rename from crates/sprout-relay/examples/mesh_serve_client_smoke.rs rename to crates/buzz-relay/examples/mesh_serve_client_smoke.rs index 96f2ee146..42721cd26 100644 --- a/crates/sprout-relay/examples/mesh_serve_client_smoke.rs +++ b/crates/buzz-relay/examples/mesh_serve_client_smoke.rs @@ -1,6 +1,6 @@ //! Local mesh serve→client→inference smoke test. //! -//! Proves the full path Sprout's "Share compute" + "Run on relay mesh" pair +//! Proves the full path Buzz's "Share compute" + "Run on relay mesh" pair //! relies on, on a single box and without a relay or Nostr discovery: //! //! 1. Start a SERVE node hosting a GGUF (the `serve::start` path desktop @@ -19,11 +19,11 @@ //! //! Usage: //! # default model is a ~100MB instruct model, downloaded on first run: -//! cargo run -p sprout-relay --example mesh_serve_client_smoke +//! cargo run -p buzz-relay --example mesh_serve_client_smoke //! //! # or point at any local .gguf / hf model ref (e.g. the on-hardware 35B): //! MESH_SMOKE_MODEL=/path/to/model.gguf \ -//! cargo run -p sprout-relay --example mesh_serve_client_smoke +//! cargo run -p buzz-relay --example mesh_serve_client_smoke use std::time::Duration; use mesh_llm_sdk::{client, serve, MeshDiscoveryMode}; diff --git a/crates/sprout-relay/examples/mesh_serve_smoke.rs b/crates/buzz-relay/examples/mesh_serve_smoke.rs similarity index 97% rename from crates/sprout-relay/examples/mesh_serve_smoke.rs rename to crates/buzz-relay/examples/mesh_serve_smoke.rs index 7ff2ee4a7..cd65c9088 100644 --- a/crates/sprout-relay/examples/mesh_serve_smoke.rs +++ b/crates/buzz-relay/examples/mesh_serve_smoke.rs @@ -1,13 +1,13 @@ //! Local mesh-serve inference smoke test. //! -//! Serves a GGUF model through the same `mesh_llm_sdk::serve` path Sprout +//! Serves a GGUF model through the same `mesh_llm_sdk::serve` path Buzz //! desktop uses in "Share compute" mode, then drives one chat completion //! against the node's local OpenAI-compatible endpoint. No mesh publish, no //! auto-join, no Nostr discovery — pure single-node serve-and-self-consume, //! which is exactly the loopback variant we can prove on one box. //! //! Usage: -//! cargo run -p sprout-relay --example mesh_serve_smoke -- +//! cargo run -p buzz-relay --example mesh_serve_smoke -- use std::time::Duration; use mesh_llm_sdk::{serve, MeshDiscoveryMode}; diff --git a/crates/sprout-relay/src/api/bridge.rs b/crates/buzz-relay/src/api/bridge.rs similarity index 94% rename from crates/sprout-relay/src/api/bridge.rs rename to crates/buzz-relay/src/api/bridge.rs index 30a901f1e..342d0e341 100644 --- a/crates/sprout-relay/src/api/bridge.rs +++ b/crates/buzz-relay/src/api/bridge.rs @@ -50,7 +50,7 @@ fn verify_bridge_auth( .map_err(|_| api_error(StatusCode::UNAUTHORIZED, "invalid NIP-98 event JSON"))?; let event_id_bytes = event.id.to_bytes(); - let pubkey = sprout_auth::verify_nip98_event(&event_json, url, method, body) + let pubkey = buzz_auth::verify_nip98_event(&event_json, url, method, body) .map_err(|e| api_error(StatusCode::UNAUTHORIZED, &format!("NIP-98: {e}")))?; return Ok((pubkey, event_id_bytes)); @@ -155,7 +155,7 @@ fn extract_feed_types(raw: &Value) -> Option> { } } -fn event_in_accessible_channel(se: &sprout_core::StoredEvent, accessible: &[uuid::Uuid]) -> bool { +fn event_in_accessible_channel(se: &buzz_core::StoredEvent, accessible: &[uuid::Uuid]) -> bool { match se.channel_id { Some(ch_id) => accessible.contains(&ch_id), None => true, @@ -194,9 +194,9 @@ pub async fn submit_event( // them to the mesh handlers — the HTTP twin of the WS door's special-casing // in handlers::event. Membership was enforced above; the handlers re-check // it fail-closed. - let kind_u32 = sprout_core::kind::event_kind_u32(&event); - if kind_u32 == sprout_core::kind::KIND_MESH_STATUS_REPORT - || kind_u32 == sprout_core::kind::KIND_MESH_CONNECT_REQUEST + let kind_u32 = buzz_core::kind::event_kind_u32(&event); + if kind_u32 == buzz_core::kind::KIND_MESH_STATUS_REPORT + || kind_u32 == buzz_core::kind::KIND_MESH_CONNECT_REQUEST { let event_id = event.id.to_hex(); return match crate::handlers::mesh_signaling::handle_mesh_event_http( @@ -215,7 +215,7 @@ pub async fn submit_event( let auth = IngestAuth::Http { pubkey, - scopes: sprout_auth::Scope::all_known(), // Pure Nostr: full scopes, channel access via membership + scopes: buzz_auth::Scope::all_known(), // Pure Nostr: full scopes, channel access via membership auth_method: crate::handlers::ingest::HttpAuthMethod::Nip98, }; @@ -461,12 +461,12 @@ pub async fn query_events( if !event_in_accessible_channel(&se, &accessible_channels) { continue; } - if !sprout_core::filter::filters_match(std::slice::from_ref(filter), &se) { + if !buzz_core::filter::filters_match(std::slice::from_ref(filter), &se) { continue; } // Result-level read auth: never hand a viewer-private snapshot // (kind:30622) to anyone but its owner, even via kindless `ids`. - if !sprout_core::filter::reader_authorized_for_event( + if !buzz_core::filter::reader_authorized_for_event( &se.event, &authed_pubkey_hex, ) { @@ -561,8 +561,7 @@ pub async fn count_events( match state.db.query_events(&q).await { Ok(stored_events) => { for se in stored_events { - if sprout_core::filter::filters_match(std::slice::from_ref(filter), &se) - { + if buzz_core::filter::filters_match(std::slice::from_ref(filter), &se) { total += 1; } } @@ -595,8 +594,7 @@ pub async fn count_events( match state.db.query_events(&query).await { Ok(stored_events) => { for se in stored_events { - if sprout_core::filter::filters_match(std::slice::from_ref(filter), &se) - { + if buzz_core::filter::filters_match(std::slice::from_ref(filter), &se) { total += 1; } } @@ -628,11 +626,11 @@ pub async fn count_events( /// outside that set are rejected regardless of NIP-01 match. fn search_hit_accepted( filter: &nostr::Filter, - stored: &sprout_core::StoredEvent, + stored: &buzz_core::StoredEvent, accessible_channels: &[uuid::Uuid], reader_pubkey_hex: &str, ) -> bool { - if !sprout_core::filter::filters_match(std::slice::from_ref(filter), stored) { + if !buzz_core::filter::filters_match(std::slice::from_ref(filter), stored) { return false; } if let Some(ch_id) = stored.channel_id { @@ -640,7 +638,7 @@ fn search_hit_accepted( return false; } } - if !sprout_core::filter::reader_authorized_for_event(&stored.event, reader_pubkey_hex) { + if !buzz_core::filter::reader_authorized_for_event(&stored.event, reader_pubkey_hex) { return false; } true @@ -717,7 +715,7 @@ async fn handle_bridge_search( let filter_by = filter_parts.join(" && "); - let search_query = sprout_search::SearchQuery { + let search_query = buzz_search::SearchQuery { q: search_text, filter_by: Some(filter_by), sort_by: None, // Typesense default = relevance @@ -751,11 +749,10 @@ async fn handle_bridge_search( .map_err(|e| internal_error(&format!("search fetch error: {e}")))?; // Build lookup map to preserve Typesense relevance ordering. - let event_map: std::collections::HashMap<[u8; 32], &sprout_core::StoredEvent> = - stored_events - .iter() - .map(|ev| (ev.event.id.to_bytes(), ev)) - .collect(); + let event_map: std::collections::HashMap<[u8; 32], &buzz_core::StoredEvent> = stored_events + .iter() + .map(|ev| (ev.event.id.to_bytes(), ev)) + .collect(); for hit_id in &hit_ids { let id_array: [u8; 32] = match hit_id.as_slice().try_into() { @@ -811,10 +808,10 @@ pub async fn workflow_webhook( .await .map_err(|_| not_found("workflow not found"))?; - let def: sprout_workflow::WorkflowDef = serde_json::from_value(workflow.definition.clone()) + let def: buzz_workflow::WorkflowDef = serde_json::from_value(workflow.definition.clone()) .map_err(|e| super::internal_error(&format!("corrupt workflow definition: {e}")))?; - if !matches!(def.trigger, sprout_workflow::TriggerDef::Webhook) { + if !matches!(def.trigger, buzz_workflow::TriggerDef::Webhook) { return Err(api_error( StatusCode::BAD_REQUEST, "workflow does not have a webhook trigger", @@ -856,7 +853,7 @@ pub async fn workflow_webhook( }; // Build trigger context from webhook body fields. - let mut trigger_ctx = sprout_workflow::executor::TriggerContext { + let mut trigger_ctx = buzz_workflow::executor::TriggerContext { channel_id: workflow .channel_id .map(|ch| ch.to_string()) @@ -886,14 +883,14 @@ pub async fn workflow_webhook( let def_value = workflow.definition.clone(); let trigger_ctx_clone = trigger_ctx.clone(); tokio::spawn(async move { - let def: sprout_workflow::WorkflowDef = match serde_json::from_value(def_value) { + let def: buzz_workflow::WorkflowDef = match serde_json::from_value(def_value) { Ok(d) => d, Err(e) => { tracing::error!("webhook: failed to parse definition: {e}"); if let Err(db_err) = db .update_workflow_run( run_id, - sprout_db::workflow::RunStatus::Failed, + buzz_db::workflow::RunStatus::Failed, 0, &serde_json::json!([]), Some(&format!("definition parse error: {e}")), @@ -906,7 +903,7 @@ pub async fn workflow_webhook( } }; - let result = sprout_workflow::executor::execute_from_step( + let result = buzz_workflow::executor::execute_from_step( &engine, run_id, &def, @@ -936,7 +933,7 @@ pub async fn workflow_webhook( /// /// Returns `Some(events)` if handled, `None` to fall through to normal query. async fn synthesize_presence(state: &AppState, filters: &[nostr::Filter]) -> Option> { - use sprout_core::kind::{KIND_PRESENCE_SNAPSHOT, KIND_PRESENCE_UPDATE}; + use buzz_core::kind::{KIND_PRESENCE_SNAPSHOT, KIND_PRESENCE_UPDATE}; // Only intercept if every filter targets kind:20001 or 40902 with authors. let mut all_pubkeys: Vec = Vec::new(); @@ -1004,7 +1001,7 @@ mod tests { use nostr::{Alphabet, EventBuilder, Keys, Kind, SingleLetterTag, Tag}; /// Build a kind:30174 engram envelope authored by `agent`, tagged with `owner`. - fn engram_envelope(agent: &Keys, owner_hex: &str) -> sprout_core::StoredEvent { + fn engram_envelope(agent: &Keys, owner_hex: &str) -> buzz_core::StoredEvent { let d_tag = Tag::custom( nostr::TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::D)), ["abcd1234"], @@ -1017,7 +1014,7 @@ mod tests { .tags([d_tag, p_tag]) .sign_with_keys(agent) .expect("sign engram"); - sprout_core::StoredEvent::new(ev, None) + buzz_core::StoredEvent::new(ev, None) } /// Regression test for the NIP-AE `/query` search leak (PR #593 review). @@ -1231,7 +1228,7 @@ mod tests { let ev = EventBuilder::new(Kind::Custom(1), "test") .sign_with_keys(&keys) .unwrap(); - let se = sprout_core::StoredEvent::new(ev, None); + let se = buzz_core::StoredEvent::new(ev, None); assert!(event_in_accessible_channel(&se, &[])); } @@ -1242,7 +1239,7 @@ mod tests { .sign_with_keys(&keys) .unwrap(); let ch = uuid::Uuid::new_v4(); - let mut se = sprout_core::StoredEvent::new(ev, None); + let mut se = buzz_core::StoredEvent::new(ev, None); se.channel_id = Some(ch); assert!(event_in_accessible_channel(&se, &[ch])); } @@ -1255,7 +1252,7 @@ mod tests { .unwrap(); let ch = uuid::Uuid::new_v4(); let other = uuid::Uuid::new_v4(); - let mut se = sprout_core::StoredEvent::new(ev, None); + let mut se = buzz_core::StoredEvent::new(ev, None); se.channel_id = Some(ch); assert!(!event_in_accessible_channel(&se, &[other])); } @@ -1279,14 +1276,11 @@ mod tests { nostr::TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::P)), [&viewer], ); - let ev = EventBuilder::new( - Kind::Custom(sprout_core::kind::KIND_DM_VISIBILITY as u16), - "", - ) - .tags([d_tag, p_tag]) - .sign_with_keys(&relay) - .expect("sign snapshot"); - let stored = sprout_core::StoredEvent::new(ev.clone(), None); + let ev = EventBuilder::new(Kind::Custom(buzz_core::kind::KIND_DM_VISIBILITY as u16), "") + .tags([d_tag, p_tag]) + .sign_with_keys(&relay) + .expect("sign snapshot"); + let stored = buzz_core::StoredEvent::new(ev.clone(), None); // Kindless filter — the exact bypass shape: no #p, just the id. let filter = nostr::Filter::new().id(ev.id); diff --git a/crates/sprout-relay/src/api/events.rs b/crates/buzz-relay/src/api/events.rs similarity index 100% rename from crates/sprout-relay/src/api/events.rs rename to crates/buzz-relay/src/api/events.rs diff --git a/crates/sprout-relay/src/api/git/cas_publish.rs b/crates/buzz-relay/src/api/git/cas_publish.rs similarity index 100% rename from crates/sprout-relay/src/api/git/cas_publish.rs rename to crates/buzz-relay/src/api/git/cas_publish.rs diff --git a/crates/sprout-relay/src/api/git/hook.rs b/crates/buzz-relay/src/api/git/hook.rs similarity index 85% rename from crates/sprout-relay/src/api/git/hook.rs rename to crates/buzz-relay/src/api/git/hook.rs index 2bc1133e6..8b0136661 100644 --- a/crates/sprout-relay/src/api/git/hook.rs +++ b/crates/buzz-relay/src/api/git/hook.rs @@ -20,16 +20,16 @@ use tracing::{error, info}; /// The pre-receive hook script content. /// /// Environment variables set by the relay before spawning git receive-pack: -/// - `SPROUT_HOOK_URL` — internal policy endpoint (http://127.0.0.1:{port}/internal/git/policy) -/// - `SPROUT_HOOK_SECRET` — per-push HMAC secret -/// - `SPROUT_REPO_ID` — repo identifier (d-tag) -/// - `SPROUT_PUSHER_PUBKEY` — authenticated pusher's hex pubkey +/// - `BUZZ_HOOK_URL` — internal policy endpoint (http://127.0.0.1:{port}/internal/git/policy) +/// - `BUZZ_HOOK_SECRET` — per-push HMAC secret +/// - `BUZZ_REPO_ID` — repo identifier (d-tag) +/// - `BUZZ_PUSHER_PUBKEY` — authenticated pusher's hex pubkey /// /// Git sets automatically (quarantine): /// - `GIT_OBJECT_DIRECTORY` — quarantine object store /// - `GIT_ALTERNATE_OBJECT_DIRECTORIES` — includes the real object store const PRE_RECEIVE_HOOK: &str = r#"#!/usr/bin/env bash -# Sprout pre-receive hook — FAIL-CLOSED +# Buzz pre-receive hook — FAIL-CLOSED # ANY error, timeout, or non-200 response → reject the push. set -eo pipefail @@ -40,11 +40,11 @@ export LC_ALL=C ZERO="0000000000000000000000000000000000000000" # Fail-closed: required env vars must be set by the relay. -: "${SPROUT_REPO_ID:?error: SPROUT_REPO_ID not set}" -: "${SPROUT_REPO_OWNER:?error: SPROUT_REPO_OWNER not set}" -: "${SPROUT_PUSHER_PUBKEY:?error: SPROUT_PUSHER_PUBKEY not set}" -: "${SPROUT_HOOK_URL:?error: SPROUT_HOOK_URL not set}" -: "${SPROUT_HOOK_SECRET:?error: SPROUT_HOOK_SECRET not set}" +: "${BUZZ_REPO_ID:?error: BUZZ_REPO_ID not set}" +: "${BUZZ_REPO_OWNER:?error: BUZZ_REPO_OWNER not set}" +: "${BUZZ_PUSHER_PUBKEY:?error: BUZZ_PUSHER_PUBKEY not set}" +: "${BUZZ_HOOK_URL:?error: BUZZ_HOOK_URL not set}" +: "${BUZZ_HOOK_SECRET:?error: BUZZ_HOOK_SECRET not set}" WORK_DIR=$(mktemp -d) || { echo "error: cannot create temp dir" >&2; exit 1; } REFS_FILE="$WORK_DIR/refs" @@ -97,8 +97,8 @@ TIMESTAMP=$(date +%s) # Structurally unambiguous HMAC format (matches Rust's compute_hmac): # len(repo_id):repo_id | repo_owner | pusher | (old_oid + new_oid + len(ref):ref + is_anc)* | timestamp -REPO_ID_LEN=${#SPROUT_REPO_ID} -HMAC_INPUT="${REPO_ID_LEN}:${SPROUT_REPO_ID}|${SPROUT_REPO_OWNER}|${SPROUT_PUSHER_PUBKEY}|" +REPO_ID_LEN=${#BUZZ_REPO_ID} +HMAC_INPUT="${REPO_ID_LEN}:${BUZZ_REPO_ID}|${BUZZ_REPO_OWNER}|${BUZZ_PUSHER_PUBKEY}|" # Sort by ref_name (field 1) — matches Rust's sort_by(|a, b| a.ref_name.cmp(&b.ref_name)) if [ -f "$HMAC_FILE" ]; then sort "$HMAC_FILE" | while IFS=' ' read ref_name old_oid new_oid is_anc; do @@ -110,7 +110,7 @@ if [ -f "$HMAC_FILE" ]; then fi HMAC_INPUT="${HMAC_INPUT}|${TIMESTAMP}" -SIGNATURE=$(printf '%s' "$HMAC_INPUT" | openssl dgst -sha256 -hmac "$SPROUT_HOOK_SECRET" -hex 2>/dev/null | sed 's/.*= //') +SIGNATURE=$(printf '%s' "$HMAC_INPUT" | openssl dgst -sha256 -hmac "$BUZZ_HOOK_SECRET" -hex 2>/dev/null | sed 's/.*= //') if [ -z "$SIGNATURE" ]; then echo "error: failed to compute HMAC signature" >&2 exit 1 @@ -119,8 +119,8 @@ fi # Phase 3: POST to policy endpoint — FAIL-CLOSED. # repo_id is free-form (user-chosen d-tag) — must be escaped for JSON safety. # repo_owner and pusher_pubkey are validated 64-char lowercase hex — no escaping needed. -SAFE_REPO_ID=$(printf '%s' "$SPROUT_REPO_ID" | sed 's/\\/\\\\/g; s/"/\\"/g') -BODY="{\"repo_id\":\"${SAFE_REPO_ID}\",\"repo_owner\":\"${SPROUT_REPO_OWNER}\",\"pusher_pubkey\":\"${SPROUT_PUSHER_PUBKEY}\",\"ref_updates\":[${REFS}],\"timestamp\":${TIMESTAMP},\"signature\":\"${SIGNATURE}\"}" +SAFE_REPO_ID=$(printf '%s' "$BUZZ_REPO_ID" | sed 's/\\/\\\\/g; s/"/\\"/g') +BODY="{\"repo_id\":\"${SAFE_REPO_ID}\",\"repo_owner\":\"${BUZZ_REPO_OWNER}\",\"pusher_pubkey\":\"${BUZZ_PUSHER_PUBKEY}\",\"ref_updates\":[${REFS}],\"timestamp\":${TIMESTAMP},\"signature\":\"${SIGNATURE}\"}" HTTP_CODE=$(curl --silent --max-time 10 \ -o "$RESP_FILE" \ @@ -128,7 +128,7 @@ HTTP_CODE=$(curl --silent --max-time 10 \ -X POST \ -H "Content-Type: application/json" \ -d "$BODY" \ - "$SPROUT_HOOK_URL" 2>/dev/null) || { + "$BUZZ_HOOK_URL" 2>/dev/null) || { echo "error: push authorization failed (network error reaching policy service)" >&2 exit 1 } diff --git a/crates/sprout-relay/src/api/git/hydrate.rs b/crates/buzz-relay/src/api/git/hydrate.rs similarity index 99% rename from crates/sprout-relay/src/api/git/hydrate.rs rename to crates/buzz-relay/src/api/git/hydrate.rs index bf9eed25d..c705a139f 100644 --- a/crates/sprout-relay/src/api/git/hydrate.rs +++ b/crates/buzz-relay/src/api/git/hydrate.rs @@ -321,19 +321,19 @@ mod tests { // -------- Live MinIO + real git roundtrip ---------------------------------- // // Run manually: - // SPROUT_GIT_S3_PROBE=1 cargo test -p sprout-relay --lib \ + // BUZZ_GIT_S3_PROBE=1 cargo test -p buzz-relay --lib \ // api::git::hydrate::tests::live -- --nocapture --test-threads=1 fn probe_enabled() -> bool { - std::env::var("SPROUT_GIT_S3_PROBE").as_deref() == Ok("1") + std::env::var("BUZZ_GIT_S3_PROBE").as_deref() == Ok("1") } fn store() -> GitStore { GitStore::new( "http://localhost:9000", - "sprout_dev", - "sprout_dev_secret", - "sprout-git", + "buzz_dev", + "buzz_dev_secret", + "buzz-git", ) .expect("connect minio") } diff --git a/crates/sprout-relay/src/api/git/manifest.rs b/crates/buzz-relay/src/api/git/manifest.rs similarity index 99% rename from crates/sprout-relay/src/api/git/manifest.rs rename to crates/buzz-relay/src/api/git/manifest.rs index ed4b093ec..6506789e7 100644 --- a/crates/sprout-relay/src/api/git/manifest.rs +++ b/crates/buzz-relay/src/api/git/manifest.rs @@ -118,7 +118,7 @@ pub fn is_safe_refname(s: &str) -> bool { } /// Hex-OID predicate. Accepts both SHA-1 (40 chars) and SHA-256 (64 chars) — -/// sprout pins SHA-1 today but the predicate is forward-looking. Used +/// buzz pins SHA-1 today but the predicate is forward-looking. Used /// symmetrically by write-side validation and read-side hydration. pub fn is_hex_oid(s: &str) -> bool { (s.len() == 40 || s.len() == 64) && s.chars().all(|c| c.is_ascii_hexdigit()) diff --git a/crates/sprout-relay/src/api/git/manifest_event.rs b/crates/buzz-relay/src/api/git/manifest_event.rs similarity index 98% rename from crates/sprout-relay/src/api/git/manifest_event.rs rename to crates/buzz-relay/src/api/git/manifest_event.rs index 3ed050314..094b156fa 100644 --- a/crates/sprout-relay/src/api/git/manifest_event.rs +++ b/crates/buzz-relay/src/api/git/manifest_event.rs @@ -11,9 +11,9 @@ //! ["HEAD", "ref: refs/heads/"], // symbolic HEAD //! ] //! -//! Sprout extension: a `p` tag carrying the pusher's pubkey (or the repo +//! Buzz extension: a `p` tag carrying the pusher's pubkey (or the repo //! owner on creation events) so subscribers can filter by author of state -//! transition. Not part of NIP-34 but consistent with the rest of sprout's +//! transition. Not part of NIP-34 but consistent with the rest of buzz's //! event-publishing conventions. use std::collections::BTreeMap; @@ -37,7 +37,7 @@ pub struct RefStateInputs<'a> { /// `ref_name -> oid_hex`. Only `refs/heads/*` and `refs/tags/*` will be /// emitted; other ref namespaces are filtered. pub refs: &'a BTreeMap, - /// Pubkey to include in the `p` tag (sprout extension). On push, this is + /// Pubkey to include in the `p` tag (buzz extension). On push, this is /// the pusher's pubkey from the receive-pack hook. On repo-creation, this /// is the kind:30617 author (repo owner). Hex-encoded (64 chars). pub actor_pubkey_hex: &'a str, @@ -106,7 +106,7 @@ pub fn build_ref_state_event( )); } - // p-tag: sprout extension (pusher or owner pubkey). + // p-tag: buzz extension (pusher or owner pubkey). tags.push(Tag::public_key(actor)); let event = EventBuilder::new(Kind::Custom(30618), "") diff --git a/crates/sprout-relay/src/api/git/mod.rs b/crates/buzz-relay/src/api/git/mod.rs similarity index 100% rename from crates/sprout-relay/src/api/git/mod.rs rename to crates/buzz-relay/src/api/git/mod.rs diff --git a/crates/sprout-relay/src/api/git/policy.rs b/crates/buzz-relay/src/api/git/policy.rs similarity index 96% rename from crates/sprout-relay/src/api/git/policy.rs rename to crates/buzz-relay/src/api/git/policy.rs index 9109d0a2d..d57d4b774 100644 --- a/crates/sprout-relay/src/api/git/policy.rs +++ b/crates/buzz-relay/src/api/git/policy.rs @@ -5,9 +5,9 @@ //! //! 1. Validates HMAC signature + 30s TTL (fail-closed) //! 2. Resolves kind:30617 → protection rules -//! 3. Resolves pusher's channel role via sprout-channel binding +//! 3. Resolves pusher's channel role via buzz-channel binding //! 4. Promotes Bot → Member (bots in a channel push as members) -//! 5. Calls `sprout_core::git_perms::evaluate_push()` +//! 5. Calls `buzz_core::git_perms::evaluate_push()` //! 6. Returns 200 (allow) or 403 (deny with reasons) //! //! # Bot Role Model @@ -40,9 +40,9 @@ use tracing::{error, warn}; use uuid::Uuid; -use sprout_core::channel::MemberRole; -use sprout_core::git_perms::{evaluate_push, parse_protection_tags, Denial, RefUpdate, UpdateKind}; -use sprout_db::EventQuery; +use buzz_core::channel::MemberRole; +use buzz_core::git_perms::{evaluate_push, parse_protection_tags, Denial, RefUpdate, UpdateKind}; +use buzz_db::EventQuery; use crate::state::AppState; @@ -279,7 +279,7 @@ pub async fn hook_policy_check( Ok(parsed) => { // Log unknown rules as warnings (helps catch typos). for unknown in &parsed.unknown_rules { - warn!(repo = %req.repo_id, rule = %unknown, "unknown sprout-protect rule (skipped)"); + warn!(repo = %req.repo_id, rule = %unknown, "unknown buzz-protect rule (skipped)"); } parsed.rules } @@ -293,7 +293,7 @@ pub async fn hook_policy_check( // 6. Resolve channel and check archived state (applies to ALL pushers including owner). let channel_id = tags .iter() - .find(|t| t.first().map(|s| s.as_str()) == Some("sprout-channel")) + .find(|t| t.first().map(|s| s.as_str()) == Some("buzz-channel")) .and_then(|t| t.get(1)) .and_then(|id| Uuid::parse_str(id).ok()); @@ -317,7 +317,7 @@ pub async fn hook_policy_check( } else { match channel_id { None => { - warn!(repo = %req.repo_id, "hook callback: no sprout-channel binding"); + warn!(repo = %req.repo_id, "hook callback: no buzz-channel binding"); return (StatusCode::FORBIDDEN, "no channel binding").into_response(); } Some(ch_id) => { @@ -595,10 +595,10 @@ mod tests { let bash_script = format!( r#" export LC_ALL=C -SPROUT_REPO_ID="{repo_id}" -SPROUT_REPO_OWNER="{repo_owner}" -SPROUT_PUSHER_PUBKEY="{pusher}" -SPROUT_HOOK_SECRET="{secret}" +BUZZ_REPO_ID="{repo_id}" +BUZZ_REPO_OWNER="{repo_owner}" +BUZZ_PUSHER_PUBKEY="{pusher}" +BUZZ_HOOK_SECRET="{secret}" TIMESTAMP="{timestamp}" # Simulate the HMAC_FILE with two refs (unsorted, like the hook writes them) @@ -611,8 +611,8 @@ echo "refs/heads/main {old1} {new1} 1" >> "$HMAC_FILE" echo "refs/heads/feature {old2} {new2} 0" >> "$HMAC_FILE" # Build HMAC input — exact logic from hook script -REPO_ID_LEN=${{#SPROUT_REPO_ID}} -HMAC_INPUT="${{REPO_ID_LEN}}:${{SPROUT_REPO_ID}}|${{SPROUT_REPO_OWNER}}|${{SPROUT_PUSHER_PUBKEY}}|" +REPO_ID_LEN=${{#BUZZ_REPO_ID}} +HMAC_INPUT="${{REPO_ID_LEN}}:${{BUZZ_REPO_ID}}|${{BUZZ_REPO_OWNER}}|${{BUZZ_PUSHER_PUBKEY}}|" sort "$HMAC_FILE" | while IFS=' ' read -r ref_name old_oid new_oid is_anc; do REF_LEN=${{#ref_name}} printf '%s%s%s:%s%s' "$old_oid" "$new_oid" "$REF_LEN" "$ref_name" "$is_anc" @@ -620,7 +620,7 @@ done > "$HMAC_FILE.concat" HMAC_INPUT="${{HMAC_INPUT}}$(cat "$HMAC_FILE.concat")|${{TIMESTAMP}}" # Compute HMAC-SHA256 -printf '%s' "$HMAC_INPUT" | openssl dgst -sha256 -hmac "$SPROUT_HOOK_SECRET" -hex 2>/dev/null | sed 's/.*= //' +printf '%s' "$HMAC_INPUT" | openssl dgst -sha256 -hmac "$BUZZ_HOOK_SECRET" -hex 2>/dev/null | sed 's/.*= //' "#, repo_id = repo_id, repo_owner = repo_owner, @@ -686,9 +686,9 @@ WORK_DIR=$(mktemp -d) trap 'rm -rf "$WORK_DIR"' EXIT HMAC_FILE="$WORK_DIR/hmac" echo "refs/heads/main {old} {new} 1" >> "$HMAC_FILE" -SPROUT_REPO_ID="{repo_id}" -REPO_ID_LEN=${{#SPROUT_REPO_ID}} -HMAC_INPUT="${{REPO_ID_LEN}}:${{SPROUT_REPO_ID}}|{owner}|{pusher}|" +BUZZ_REPO_ID="{repo_id}" +REPO_ID_LEN=${{#BUZZ_REPO_ID}} +HMAC_INPUT="${{REPO_ID_LEN}}:${{BUZZ_REPO_ID}}|{owner}|{pusher}|" sort "$HMAC_FILE" | while IFS=' ' read -r ref_name old_oid new_oid is_anc; do REF_LEN=${{#ref_name}} printf '%s%s%s:%s%s' "$old_oid" "$new_oid" "$REF_LEN" "$ref_name" "$is_anc" diff --git a/crates/sprout-relay/src/api/git/store.rs b/crates/buzz-relay/src/api/git/store.rs similarity index 98% rename from crates/sprout-relay/src/api/git/store.rs rename to crates/buzz-relay/src/api/git/store.rs index af892352b..79b942eca 100644 --- a/crates/sprout-relay/src/api/git/store.rs +++ b/crates/buzz-relay/src/api/git/store.rs @@ -6,7 +6,7 @@ //! //! ## The 412 sharp edge //! -//! `rust-s3 = "0.37"` is shared across the workspace with `sprout-media`. The +//! `rust-s3 = "0.37"` is shared across the workspace with `buzz-media`. The //! `fail-on-err` Cargo feature is unified ON across the build graph, which //! means non-2xx responses arrive here as `S3Error::HttpFailWithBody(code, //! body)` *before* the caller sees `ResponseData`. The pointer-CAS path treats @@ -768,23 +768,23 @@ mod probe { //! Empirical probe of rust-s3 + `fail-on-err` + MinIO surfacing of 412. //! //! Run manually: - //! SPROUT_GIT_S3_PROBE=1 cargo test -p sprout-relay --lib \ + //! BUZZ_GIT_S3_PROBE=1 cargo test -p buzz-relay --lib \ //! api::git::store::probe -- --nocapture --test-threads=1 //! - //! Pre-req: `docker compose up minio` and the `sprout-git` bucket exists. + //! Pre-req: `docker compose up minio` and the `buzz-git` bucket exists. use super::*; fn probe_enabled() -> bool { - std::env::var("SPROUT_GIT_S3_PROBE").as_deref() == Ok("1") + std::env::var("BUZZ_GIT_S3_PROBE").as_deref() == Ok("1") } fn store() -> GitStore { GitStore::new( "http://localhost:9000", - "sprout_dev", - "sprout_dev_secret", - "sprout-git", + "buzz_dev", + "buzz_dev_secret", + "buzz-git", ) .expect("connect minio") } @@ -798,7 +798,7 @@ mod probe { #[tokio::test] async fn probe_412_surfacing() { if !probe_enabled() { - eprintln!("skipping: set SPROUT_GIT_S3_PROBE=1 to run against live MinIO"); + eprintln!("skipping: set BUZZ_GIT_S3_PROBE=1 to run against live MinIO"); return; } let st = store(); diff --git a/crates/sprout-relay/src/api/git/transport.rs b/crates/buzz-relay/src/api/git/transport.rs similarity index 98% rename from crates/sprout-relay/src/api/git/transport.rs rename to crates/buzz-relay/src/api/git/transport.rs index 1833539d2..0a5c9da61 100644 --- a/crates/sprout-relay/src/api/git/transport.rs +++ b/crates/buzz-relay/src/api/git/transport.rs @@ -1,4 +1,4 @@ -//! Smart HTTP git transport for Sprout. +//! Smart HTTP git transport for Buzz. //! //! Three endpoints implement the git Smart HTTP protocol: //! - `GET /git/{owner}/{repo}/info/refs?service={svc}` — ref advertisement @@ -72,7 +72,7 @@ impl axum::extract::FromRequestParts> for GitAuth { .status(StatusCode::UNAUTHORIZED) .header( "WWW-Authenticate", - format!("Nostr realm=\"sprout\", method=\"{method}\""), + format!("Nostr realm=\"buzz\", method=\"{method}\""), ) .body(Body::from("missing Authorization header")) .unwrap() @@ -83,7 +83,7 @@ impl axum::extract::FromRequestParts> for GitAuth { .status(StatusCode::UNAUTHORIZED) .header( "WWW-Authenticate", - format!("Nostr realm=\"sprout\", method=\"{method}\""), + format!("Nostr realm=\"buzz\", method=\"{method}\""), ) .body(Body::from("expected Authorization: Nostr ")) .unwrap() @@ -161,11 +161,11 @@ impl axum::extract::FromRequestParts> for GitAuth { // body=None: can't buffer streaming pack data to verify payload hash. // Token is time-bounded (±60s) and URL-locked — acceptable trade-off. let pubkey = - sprout_auth::nip98::verify_nip98_event(&event_json, &expected_url, &event_method, None) + buzz_auth::nip98::verify_nip98_event(&event_json, &expected_url, &event_method, None) .map_err(|e| { - warn!(error = %e, "git NIP-98 auth failed"); - (StatusCode::UNAUTHORIZED, "NIP-98 auth failed").into_response() - })?; + warn!(error = %e, "git NIP-98 auth failed"); + (StatusCode::UNAUTHORIZED, "NIP-98 auth failed").into_response() + })?; // NOTE: NIP-98 event-ID dedup intentionally NOT implemented here. // Git's credential protocol reuses one signed token across multiple requests @@ -492,14 +492,14 @@ pub async fn receive_pack( ); let hooks_dir = repo.path().join("hooks").display().to_string(); let hook_env = vec![ - ("SPROUT_HOOK_URL", hook_url), + ("BUZZ_HOOK_URL", hook_url), ( - "SPROUT_HOOK_SECRET", + "BUZZ_HOOK_SECRET", state.config.git_hook_hmac_secret.clone(), ), - ("SPROUT_REPO_ID", repo_name.to_string()), - ("SPROUT_REPO_OWNER", params.owner.clone()), - ("SPROUT_PUSHER_PUBKEY", pusher_hex.clone()), + ("BUZZ_REPO_ID", repo_name.to_string()), + ("BUZZ_REPO_OWNER", params.owner.clone()), + ("BUZZ_PUSHER_PUBKEY", pusher_hex.clone()), // Override any repo-local core.hooksPath setting; defense in // depth even though the hydrated workspace has no inherited // config. diff --git a/crates/sprout-relay/src/api/media.rs b/crates/buzz-relay/src/api/media.rs similarity index 97% rename from crates/sprout-relay/src/api/media.rs rename to crates/buzz-relay/src/api/media.rs index b9e3fcc4f..8c8b284d2 100644 --- a/crates/sprout-relay/src/api/media.rs +++ b/crates/buzz-relay/src/api/media.rs @@ -15,10 +15,10 @@ use axum::{ Json, }; use base64::Engine; +use buzz_audit::{AuditAction, NewAuditEntry}; +use buzz_auth::Scope; +use buzz_media::{BlobDescriptor, MediaError}; use sha2::{Digest, Sha256}; -use sprout_audit::{AuditAction, NewAuditEntry}; -use sprout_auth::Scope; -use sprout_media::{BlobDescriptor, MediaError}; use crate::state::AppState; @@ -51,7 +51,7 @@ impl FromRequestParts> for AuthenticatedUpload { // content type yet. The upload functions re-verify with the correct // per-type window (600s for images, 3600s for video) after the body // has been consumed and the SHA-256 computed. - sprout_media::auth::verify_blossom_auth_event( + buzz_media::auth::verify_blossom_auth_event( &auth_event, state.config.media.server_domain.as_deref(), 3600, @@ -83,7 +83,7 @@ impl FromRequestParts> for AuthenticatedUpload { // 4. Resolve scopes (API token or dev mode) let scopes = resolve_upload_scopes(headers, state, &auth_event.pubkey).await?; - sprout_auth::require_scope(&scopes, Scope::FilesWrite) + buzz_auth::require_scope(&scopes, Scope::FilesWrite) .map_err(|_| MediaError::InsufficientScope)?; // 5. Relay membership gate (NIP-43). @@ -112,7 +112,7 @@ impl FromRequestParts> for AuthenticatedUpload { /// Expects: /// - `Authorization: Nostr ` — Blossom auth /// - `X-SHA-256: ` — Required per BUD-11 -/// - `X-Auth-Token: sprout_*` — API token for scope resolution (optional in dev mode) +/// - `X-Auth-Token: buzz_*` — API token for scope resolution (optional in dev mode) /// - `Content-Type: video/mp4` — routes to video validation path; all other types use image path /// - Raw binary body (the file bytes) /// @@ -137,7 +137,7 @@ pub async fn upload_blob( .get("content-length") .and_then(|v| v.to_str().ok()) .and_then(|v| v.parse::().ok()); - sprout_media::process_video_upload( + buzz_media::process_video_upload( &state.media_storage, &state.config.media, &auth.auth_event, @@ -166,7 +166,7 @@ pub async fn upload_blob( ); if is_image { - sprout_media::process_upload( + buzz_media::process_upload( &state.media_storage, &state.config.media, &auth.auth_event, @@ -174,7 +174,7 @@ pub async fn upload_blob( ) .await? } else { - sprout_media::process_file_upload( + buzz_media::process_file_upload( &state.media_storage, &state.config.media, &auth.auth_event, @@ -191,7 +191,7 @@ pub async fn upload_blob( } _ => "other", }; - metrics::counter!("sprout_media_uploads_total", "mime" => mime_label.to_owned()).increment(1); + metrics::counter!("buzz_media_uploads_total", "mime" => mime_label.to_owned()).increment(1); // Audit via bounded channel — same pattern as event audit. let desc = descriptor.clone(); @@ -200,7 +200,7 @@ pub async fn upload_blob( .audit_tx .send(NewAuditEntry { event_id: desc.sha256.clone(), - event_kind: sprout_core::kind::KIND_MEDIA_UPLOAD, + event_kind: buzz_core::kind::KIND_MEDIA_UPLOAD, actor_pubkey: uploader, action: AuditAction::MediaUploaded, channel_id: None, @@ -213,7 +213,7 @@ pub async fn upload_blob( .await { tracing::error!("Media audit channel closed — entry lost: {e}"); - metrics::counter!("sprout_audit_send_errors_total").increment(1); + metrics::counter!("buzz_audit_send_errors_total").increment(1); } Ok(Json(descriptor)) @@ -342,7 +342,7 @@ pub async fn get_blob( // primary defence for non-previewable types — combined with `nosniff` and // `CSP: default-src 'none'`, an attachment disposition prevents an uploaded // file from ever executing or rendering as active content in the client. - let disposition = if sprout_media::serve_inline(&content_type) { + let disposition = if buzz_media::serve_inline(&content_type) { "inline" } else { "attachment" @@ -546,7 +546,7 @@ pub async fn head_blob( /// Sidecar-derived extensions are validated as safe tokens to prevent /// object-key confusion if sidecar data is ever tampered with. async fn resolve_s3_key( - storage: &sprout_media::MediaStorage, + storage: &buzz_media::MediaStorage, sha256_ext: &str, ) -> Result { if sha256_ext.contains('.') { @@ -593,7 +593,7 @@ fn extract_blossom_auth(headers: &HeaderMap) -> Result /// Resolve permission scopes for an upload caller. /// /// Resolution order: -/// 1. `X-Auth-Token: sprout_*` header — API token path (validates owner matches Blossom signer) +/// 1. `X-Auth-Token: buzz_*` header — API token path (validates owner matches Blossom signer) /// 2. If `require_auth_token` is false (dev mode) — check pubkey allowlist, then grant file scopes async fn resolve_upload_scopes( headers: &HeaderMap, @@ -604,7 +604,7 @@ async fn resolve_upload_scopes( if let Some(token) = headers .get("x-auth-token") .and_then(|v| v.to_str().ok()) - .filter(|t| t.starts_with("sprout_")) + .filter(|t| t.starts_with("buzz_")) { let hash: [u8; 32] = Sha256::digest(token.as_bytes()).into(); let record = state diff --git a/crates/sprout-relay/src/api/mod.rs b/crates/buzz-relay/src/api/mod.rs similarity index 97% rename from crates/sprout-relay/src/api/mod.rs rename to crates/buzz-relay/src/api/mod.rs index 404746a31..e7c1b6fd7 100644 --- a/crates/sprout-relay/src/api/mod.rs +++ b/crates/buzz-relay/src/api/mod.rs @@ -76,7 +76,7 @@ pub mod relay_members { let agent_pubkey = nostr::PublicKey::from_slice(pubkey_bytes) .map_err(|e| format!("invalid agent pubkey for NIP-OA check: {e}"))?; - match sprout_sdk::nip_oa::verify_auth_tag(tag_json, &agent_pubkey) { + match buzz_sdk::nip_oa::verify_auth_tag(tag_json, &agent_pubkey) { Ok(owner_pubkey) => { let owner_hex = owner_pubkey.to_hex(); let owner_is_member = @@ -148,7 +148,7 @@ pub mod relay_members { ) -> Option { let tag_json = auth_tag_header?; let agent_pubkey = nostr::PublicKey::from_slice(pubkey_bytes).ok()?; - match sprout_sdk::nip_oa::verify_auth_tag(tag_json, &agent_pubkey) { + match buzz_sdk::nip_oa::verify_auth_tag(tag_json, &agent_pubkey) { Ok(owner) => Some(owner), Err(e) => { info!("extract_nip_oa_owner: invalid auth tag: {e}"); @@ -160,8 +160,8 @@ pub mod relay_members { #[cfg(test)] mod tests { use super::*; + use buzz_sdk::nip_oa::compute_auth_tag; use nostr::Keys; - use sprout_sdk::nip_oa::compute_auth_tag; /// Valid NIP-OA auth tag → returns Some(owner_pubkey). #[test] diff --git a/crates/sprout-relay/src/api/nip05.rs b/crates/buzz-relay/src/api/nip05.rs similarity index 94% rename from crates/sprout-relay/src/api/nip05.rs rename to crates/buzz-relay/src/api/nip05.rs index db33a1b6a..4299d4905 100644 --- a/crates/sprout-relay/src/api/nip05.rs +++ b/crates/buzz-relay/src/api/nip05.rs @@ -29,7 +29,7 @@ pub async fn nostr_nip05( None => serde_json::json!({ "names": {}, "relays": {} }), Some(n) => { let name = n.to_lowercase(); - // Extract domain from relay_url (e.g. "ws://sprout.block.xyz" → "sprout.block.xyz") + // Extract domain from relay_url (e.g. "ws://buzz.block.xyz" → "buzz.block.xyz") let domain = extract_domain(&state.config.relay_url); match state.db.get_user_by_nip05(&name, &domain).await { Ok(Some(user)) => { @@ -78,7 +78,7 @@ pub(crate) fn canonicalize_nip05(raw: &str, relay_url: &str) -> Result String { url.trim_start_matches("wss://") .trim_start_matches("ws://") diff --git a/crates/sprout-relay/src/audio/handler.rs b/crates/buzz-relay/src/audio/handler.rs similarity index 99% rename from crates/sprout-relay/src/audio/handler.rs rename to crates/buzz-relay/src/audio/handler.rs index 2eda31c9d..bfa8c9f60 100644 --- a/crates/sprout-relay/src/audio/handler.rs +++ b/crates/buzz-relay/src/audio/handler.rs @@ -29,10 +29,10 @@ use tokio_util::sync::CancellationToken; use tracing::{debug, info, warn}; use uuid::Uuid; -use sprout_auth::generate_challenge; -use sprout_db::channel::MemberRole; +use buzz_auth::generate_challenge; +use buzz_db::channel::MemberRole; -use sprout_core::StoredEvent; +use buzz_core::StoredEvent; use crate::audio::room::PeerCtrl; use crate::state::AppState; diff --git a/crates/sprout-relay/src/audio/mod.rs b/crates/buzz-relay/src/audio/mod.rs similarity index 100% rename from crates/sprout-relay/src/audio/mod.rs rename to crates/buzz-relay/src/audio/mod.rs diff --git a/crates/sprout-relay/src/audio/room.rs b/crates/buzz-relay/src/audio/room.rs similarity index 100% rename from crates/sprout-relay/src/audio/room.rs rename to crates/buzz-relay/src/audio/room.rs diff --git a/crates/sprout-relay/src/audio/wire.rs b/crates/buzz-relay/src/audio/wire.rs similarity index 100% rename from crates/sprout-relay/src/audio/wire.rs rename to crates/buzz-relay/src/audio/wire.rs diff --git a/crates/sprout-relay/src/bin/reindex_kind0.rs b/crates/buzz-relay/src/bin/reindex_kind0.rs similarity index 93% rename from crates/sprout-relay/src/bin/reindex_kind0.rs rename to crates/buzz-relay/src/bin/reindex_kind0.rs index 58cd3bb4d..d0172c068 100644 --- a/crates/sprout-relay/src/bin/reindex_kind0.rs +++ b/crates/buzz-relay/src/bin/reindex_kind0.rs @@ -1,7 +1,7 @@ //! One-shot admin tool: re-index all kind:0 (user metadata) events in Typesense. //! //! Necessary after the indexer change that appends `display_name`/`name`/`nip05` -//! values to the indexed content for kind:0 docs (see `sprout-search`'s +//! values to the indexed content for kind:0 docs (see `buzz-search`'s //! `flatten_kind0_for_indexing`). Existing docs need to be rewritten with the //! appended tokens before they become searchable by display name. //! @@ -11,7 +11,7 @@ //! Usage (from the repo root, with .env sourced): //! //! ``` -//! cargo run --release -p sprout-relay --bin sprout-reindex-kind0 +//! cargo run --release -p buzz-relay --bin buzz-reindex-kind0 //! ``` //! //! Idempotent — Typesense uses upsert semantics, so running twice is safe. @@ -38,9 +38,9 @@ use chrono::{DateTime, Utc}; use tracing::{info, warn}; use tracing_subscriber::{fmt, prelude::*, EnvFilter}; -use sprout_db::{Db, DbConfig, EventQuery}; -use sprout_relay::config::Config; -use sprout_search::{SearchConfig, SearchService}; +use buzz_db::{Db, DbConfig, EventQuery}; +use buzz_relay::config::Config; +use buzz_search::{SearchConfig, SearchService}; /// Page size for the SQL → Typesense pipeline. Small enough to keep DB and /// Typesense memory comfortable, large enough to amortise per-batch overhead. @@ -52,8 +52,8 @@ async fn main() -> anyhow::Result<()> { .with(fmt::layer()) .with( EnvFilter::from_default_env() - .add_directive("sprout_reindex_kind0=info".parse()?) - .add_directive("sprout_relay=info".parse()?), + .add_directive("buzz_reindex_kind0=info".parse()?) + .add_directive("buzz_relay=info".parse()?), ) .init(); diff --git a/crates/sprout-relay/src/config.rs b/crates/buzz-relay/src/config.rs similarity index 79% rename from crates/sprout-relay/src/config.rs rename to crates/buzz-relay/src/config.rs index 4376d89b9..59daa0647 100644 --- a/crates/sprout-relay/src/config.rs +++ b/crates/buzz-relay/src/config.rs @@ -8,8 +8,8 @@ use tracing::warn; /// Errors that can occur while loading relay configuration. #[derive(Debug, Error)] pub enum ConfigError { - /// The `SPROUT_BIND_ADDR` environment variable could not be parsed as a socket address. - #[error("invalid SPROUT_BIND_ADDR: {0}")] + /// The `BUZZ_BIND_ADDR` environment variable could not be parsed as a socket address. + #[error("invalid BUZZ_BIND_ADDR: {0}")] InvalidBindAddr(String), /// A configuration value failed validation. #[error("invalid config: {0}")] @@ -38,7 +38,7 @@ pub struct Config { /// Per-connection outbound message buffer size (number of messages). pub send_buffer_size: usize, /// Authentication provider configuration. - pub auth: sprout_auth::AuthConfig, + pub auth: buzz_auth::AuthConfig, /// Whether REST API requests must present a valid token. Independent of /// WebSocket protocol auth, which is *always* required by REQ/EVENT/COUNT. pub require_auth_token: bool, @@ -86,16 +86,16 @@ pub struct Config { /// signature is cryptographically self-proving). This flag only controls /// whether NIP-OA can grant membership access on closed relays. /// - /// Default: `false`. Set via `SPROUT_ALLOW_NIP_OA_AUTH=true`. + /// Default: `false`. Set via `BUZZ_ALLOW_NIP_OA_AUTH=true`. pub allow_nip_oa_auth: bool, /// Media storage configuration (S3/MinIO). - pub media: sprout_media::MediaConfig, + pub media: buzz_media::MediaConfig, /// Optional override for ephemeral channel TTL (in seconds). /// When set, any channel created with a TTL tag will use this value instead /// of the client-provided one. Useful for testing ephemeral expiry quickly. - /// Example: `SPROUT_EPHEMERAL_TTL_OVERRIDE=60` → all ephemeral channels expire + /// Example: `BUZZ_EPHEMERAL_TTL_OVERRIDE=60` → all ephemeral channels expire /// 60 seconds after the last message. pub ephemeral_ttl_override: Option, @@ -124,13 +124,13 @@ pub struct Config { impl Config { /// Loads configuration from environment variables, falling back to development defaults. pub fn from_env() -> Result { - let bind_addr = std::env::var("SPROUT_BIND_ADDR") + let bind_addr = std::env::var("BUZZ_BIND_ADDR") .unwrap_or_else(|_| "0.0.0.0:3000".to_string()) .parse::() .map_err(|e| ConfigError::InvalidBindAddr(e.to_string()))?; let database_url = std::env::var("DATABASE_URL") - .unwrap_or_else(|_| "postgres://sprout:sprout_dev@localhost:5432/sprout".to_string()); + .unwrap_or_else(|_| "postgres://buzz:buzz_dev@localhost:5432/buzz".to_string()); let redis_url = std::env::var("REDIS_URL").unwrap_or_else(|_| "redis://localhost:6379".to_string()); @@ -139,43 +139,43 @@ impl Config { std::env::var("TYPESENSE_URL").unwrap_or_else(|_| "http://localhost:8108".to_string()); let typesense_key = - std::env::var("TYPESENSE_API_KEY").unwrap_or_else(|_| "sprout_dev_key".to_string()); + std::env::var("TYPESENSE_API_KEY").unwrap_or_else(|_| "buzz_dev_key".to_string()); let relay_url = std::env::var("RELAY_URL").unwrap_or_else(|_| "ws://localhost:3000".to_string()); - let max_connections = std::env::var("SPROUT_MAX_CONNECTIONS") + let max_connections = std::env::var("BUZZ_MAX_CONNECTIONS") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(10_000); - let max_concurrent_handlers = std::env::var("SPROUT_MAX_CONCURRENT_HANDLERS") + let max_concurrent_handlers = std::env::var("BUZZ_MAX_CONCURRENT_HANDLERS") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(1024); - let send_buffer_size = std::env::var("SPROUT_SEND_BUFFER") + let send_buffer_size = std::env::var("BUZZ_SEND_BUFFER") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(1_000); - let require_auth_token = std::env::var("SPROUT_REQUIRE_AUTH_TOKEN") + let require_auth_token = std::env::var("BUZZ_REQUIRE_AUTH_TOKEN") .map(|v| v == "true" || v == "1") .unwrap_or(false); - let pubkey_allowlist_enabled = std::env::var("SPROUT_PUBKEY_ALLOWLIST") + let pubkey_allowlist_enabled = std::env::var("BUZZ_PUBKEY_ALLOWLIST") .map(|v| v == "true" || v == "1") .unwrap_or(false); - let require_relay_membership = std::env::var("SPROUT_REQUIRE_RELAY_MEMBERSHIP") + let require_relay_membership = std::env::var("BUZZ_REQUIRE_RELAY_MEMBERSHIP") .map(|v| v == "true" || v == "1") .unwrap_or(false); - let allow_nip_oa_auth = std::env::var("SPROUT_ALLOW_NIP_OA_AUTH") + let allow_nip_oa_auth = std::env::var("BUZZ_ALLOW_NIP_OA_AUTH") .map(|v| v == "true" || v == "1") .unwrap_or(false); - // Note: intentionally not prefixed with SPROUT_ — this is a relay-identity + // Note: intentionally not prefixed with BUZZ_ — this is a relay-identity // config that may be shared across multiple services (e.g., ACP agent). let relay_owner_pubkey = std::env::var("RELAY_OWNER_PUBKEY") .ok() @@ -195,67 +195,66 @@ impl Config { } }); - let auth = sprout_auth::AuthConfig::default(); + let auth = buzz_auth::AuthConfig::default(); if !require_auth_token { warn!( - "SPROUT_REQUIRE_AUTH_TOKEN is false — REST API requests bypass token auth. \ + "BUZZ_REQUIRE_AUTH_TOKEN is false — REST API requests bypass token auth. \ WebSocket protocol auth is unaffected. Set to true for production." ); } - let cors_origins = std::env::var("SPROUT_CORS_ORIGINS") + let cors_origins = std::env::var("BUZZ_CORS_ORIGINS") .unwrap_or_default() .split(',') .map(|s| s.trim().to_string()) .filter(|s| !s.is_empty()) .collect(); - let relay_private_key = std::env::var("SPROUT_RELAY_PRIVATE_KEY").ok(); + let relay_private_key = std::env::var("BUZZ_RELAY_PRIVATE_KEY").ok(); - let uds_path = std::env::var("SPROUT_UDS_PATH") + let uds_path = std::env::var("BUZZ_UDS_PATH") .ok() .map(|s| s.trim().to_string()) .filter(|s| !s.is_empty()); - let health_port = std::env::var("SPROUT_HEALTH_PORT") + let health_port = std::env::var("BUZZ_HEALTH_PORT") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(8080); - let metrics_port = std::env::var("SPROUT_METRICS_PORT") + let metrics_port = std::env::var("BUZZ_METRICS_PORT") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(9102); - let media = sprout_media::MediaConfig { - s3_endpoint: std::env::var("SPROUT_S3_ENDPOINT") + let media = buzz_media::MediaConfig { + s3_endpoint: std::env::var("BUZZ_S3_ENDPOINT") .unwrap_or_else(|_| "http://localhost:9000".to_string()), - s3_access_key: std::env::var("SPROUT_S3_ACCESS_KEY") - .unwrap_or_else(|_| "sprout_dev".to_string()), - s3_secret_key: std::env::var("SPROUT_S3_SECRET_KEY") - .unwrap_or_else(|_| "sprout_dev_secret".to_string()), - s3_bucket: std::env::var("SPROUT_S3_BUCKET") - .unwrap_or_else(|_| "sprout-media".to_string()), - max_image_bytes: std::env::var("SPROUT_MAX_IMAGE_BYTES") + s3_access_key: std::env::var("BUZZ_S3_ACCESS_KEY") + .unwrap_or_else(|_| "buzz_dev".to_string()), + s3_secret_key: std::env::var("BUZZ_S3_SECRET_KEY") + .unwrap_or_else(|_| "buzz_dev_secret".to_string()), + s3_bucket: std::env::var("BUZZ_S3_BUCKET").unwrap_or_else(|_| "buzz-media".to_string()), + max_image_bytes: std::env::var("BUZZ_MAX_IMAGE_BYTES") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(50 * 1024 * 1024), - max_gif_bytes: std::env::var("SPROUT_MAX_GIF_BYTES") + max_gif_bytes: std::env::var("BUZZ_MAX_GIF_BYTES") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(10 * 1024 * 1024), - max_video_bytes: std::env::var("SPROUT_MAX_VIDEO_BYTES") + max_video_bytes: std::env::var("BUZZ_MAX_VIDEO_BYTES") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(500 * 1024 * 1024), - max_file_bytes: std::env::var("SPROUT_MAX_FILE_BYTES") + max_file_bytes: std::env::var("BUZZ_MAX_FILE_BYTES") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(100 * 1024 * 1024), - public_base_url: std::env::var("SPROUT_MEDIA_BASE_URL") + public_base_url: std::env::var("BUZZ_MEDIA_BASE_URL") .unwrap_or_else(|_| "http://localhost:3000/media".to_string()), - server_domain: std::env::var("SPROUT_MEDIA_SERVER_DOMAIN") + server_domain: std::env::var("BUZZ_MEDIA_SERVER_DOMAIN") .ok() .filter(|s| !s.is_empty()) .or_else(|| { @@ -277,20 +276,20 @@ impl Config { }), }; - let ephemeral_ttl_override = std::env::var("SPROUT_EPHEMERAL_TTL_OVERRIDE") + let ephemeral_ttl_override = std::env::var("BUZZ_EPHEMERAL_TTL_OVERRIDE") .ok() .and_then(|v| v.parse::().ok()) .filter(|&v| v > 0); if let Some(ttl) = ephemeral_ttl_override { warn!( - "SPROUT_EPHEMERAL_TTL_OVERRIDE={ttl}s — all ephemeral channels will use \ + "BUZZ_EPHEMERAL_TTL_OVERRIDE={ttl}s — all ephemeral channels will use \ this TTL instead of the client-provided value." ); } // Git server config - let git_repo_path: std::path::PathBuf = std::env::var("SPROUT_GIT_REPO_PATH") + let git_repo_path: std::path::PathBuf = std::env::var("BUZZ_GIT_REPO_PATH") .unwrap_or_else(|_| "./repos".to_string()) .into(); // Ensure the git repo root exists. The smart-HTTP transport and the @@ -302,30 +301,30 @@ impl Config { // ops to mkdir it out of band. if let Err(e) = std::fs::create_dir_all(&git_repo_path) { return Err(ConfigError::InvalidValue(format!( - "SPROUT_GIT_REPO_PATH={} could not be created: {e}", + "BUZZ_GIT_REPO_PATH={} could not be created: {e}", git_repo_path.display() ))); } - let git_max_pack_bytes: u64 = std::env::var("SPROUT_GIT_MAX_PACK_BYTES") + let git_max_pack_bytes: u64 = std::env::var("BUZZ_GIT_MAX_PACK_BYTES") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(500 * 1024 * 1024); // 500 MB - let git_max_repos_per_pubkey: u32 = std::env::var("SPROUT_GIT_MAX_REPOS_PER_PUBKEY") + let git_max_repos_per_pubkey: u32 = std::env::var("BUZZ_GIT_MAX_REPOS_PER_PUBKEY") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(100); - let git_max_concurrent_ops: usize = std::env::var("SPROUT_GIT_MAX_CONCURRENT_OPS") + let git_max_concurrent_ops: usize = std::env::var("BUZZ_GIT_MAX_CONCURRENT_OPS") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(20); - let git_hook_hmac_secret: String = std::env::var("SPROUT_GIT_HOOK_HMAC_SECRET") + let git_hook_hmac_secret: String = std::env::var("BUZZ_GIT_HOOK_HMAC_SECRET") .unwrap_or_else(|_| { // Generate a random secret if not configured (dev mode). let secret: [u8; 32] = rand::random(); hex::encode(secret) }); // Web UI static file serving - let web_dir = std::env::var("SPROUT_WEB_DIR") + let web_dir = std::env::var("BUZZ_WEB_DIR") .ok() .map(|s| s.trim().to_string()) .filter(|s| !s.is_empty()) @@ -334,22 +333,19 @@ impl Config { if let Some(ref dir) = web_dir { if !dir.join("index.html").is_file() { return Err(ConfigError::InvalidValue(format!( - "SPROUT_WEB_DIR={} does not contain index.html", + "BUZZ_WEB_DIR={} does not contain index.html", dir.display() ))); } - tracing::info!( - "SPROUT_WEB_DIR={} — serving web UI from relay", - dir.display() - ); + tracing::info!("BUZZ_WEB_DIR={} — serving web UI from relay", dir.display()); } // Reject explicitly-configured secrets that are too short. // The auto-generated fallback is always 64 hex chars (32 bytes), so this - // only fires when someone sets SPROUT_GIT_HOOK_HMAC_SECRET to a weak value. - if std::env::var("SPROUT_GIT_HOOK_HMAC_SECRET").is_ok() && git_hook_hmac_secret.len() < 32 { + // only fires when someone sets BUZZ_GIT_HOOK_HMAC_SECRET to a weak value. + if std::env::var("BUZZ_GIT_HOOK_HMAC_SECRET").is_ok() && git_hook_hmac_secret.len() < 32 { return Err(ConfigError::InvalidValue( - "SPROUT_GIT_HOOK_HMAC_SECRET must be at least 32 characters (16 bytes hex)" + "BUZZ_GIT_HOOK_HMAC_SECRET must be at least 32 characters (16 bytes hex)" .to_string(), )); } @@ -426,9 +422,9 @@ mod tests { #[test] fn invalid_bind_addr_returns_error() { let _guard = ENV_MUTEX.lock().unwrap(); - std::env::set_var("SPROUT_BIND_ADDR", "not-an-addr"); + std::env::set_var("BUZZ_BIND_ADDR", "not-an-addr"); let result = Config::from_env(); - std::env::remove_var("SPROUT_BIND_ADDR"); + std::env::remove_var("BUZZ_BIND_ADDR"); assert!(matches!(result, Err(ConfigError::InvalidBindAddr(_)))); } @@ -436,7 +432,7 @@ mod tests { fn server_domain_auto_derived_from_relay_url() { let _guard = ENV_MUTEX.lock().unwrap(); // Clear explicit override so auto-derive kicks in - std::env::remove_var("SPROUT_MEDIA_SERVER_DOMAIN"); + std::env::remove_var("BUZZ_MEDIA_SERVER_DOMAIN"); std::env::set_var("RELAY_URL", "ws://localhost:3000"); let config = Config::from_env().expect("config"); std::env::remove_var("RELAY_URL"); @@ -449,7 +445,7 @@ mod tests { #[test] fn server_domain_auto_derived_default_port() { let _guard = ENV_MUTEX.lock().unwrap(); - std::env::remove_var("SPROUT_MEDIA_SERVER_DOMAIN"); + std::env::remove_var("BUZZ_MEDIA_SERVER_DOMAIN"); std::env::set_var("RELAY_URL", "wss://relay.example.com"); let config = Config::from_env().expect("config"); std::env::remove_var("RELAY_URL"); @@ -464,7 +460,7 @@ mod tests { let _guard = ENV_MUTEX.lock().unwrap(); // Pick a path under temp_dir that definitely doesn't exist yet. let base = std::env::temp_dir().join(format!( - "sprout-test-git-repo-path-{}-{}", + "buzz-test-git-repo-path-{}-{}", std::process::id(), std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) @@ -474,9 +470,9 @@ mod tests { let nested = base.join("nested").join("repos"); assert!(!nested.exists(), "test precondition: path must not exist"); - std::env::set_var("SPROUT_GIT_REPO_PATH", &nested); + std::env::set_var("BUZZ_GIT_REPO_PATH", &nested); let result = Config::from_env(); - std::env::remove_var("SPROUT_GIT_REPO_PATH"); + std::env::remove_var("BUZZ_GIT_REPO_PATH"); let config = result.expect("config should self-bootstrap missing git_repo_path"); assert_eq!(config.git_repo_path, nested); @@ -496,22 +492,22 @@ mod tests { // Try to create a path under a regular file — must fail. // Using /dev/null as the parent guarantees create_dir_all fails on unix. let bogus = std::path::PathBuf::from("/dev/null/cannot-create-here"); - std::env::set_var("SPROUT_GIT_REPO_PATH", &bogus); + std::env::set_var("BUZZ_GIT_REPO_PATH", &bogus); let result = Config::from_env(); - std::env::remove_var("SPROUT_GIT_REPO_PATH"); + std::env::remove_var("BUZZ_GIT_REPO_PATH"); assert!( - matches!(result, Err(ConfigError::InvalidValue(ref msg)) if msg.contains("SPROUT_GIT_REPO_PATH")), - "expected InvalidValue mentioning SPROUT_GIT_REPO_PATH, got {result:?}" + matches!(result, Err(ConfigError::InvalidValue(ref msg)) if msg.contains("BUZZ_GIT_REPO_PATH")), + "expected InvalidValue mentioning BUZZ_GIT_REPO_PATH, got {result:?}" ); } #[test] fn server_domain_explicit_override_wins() { let _guard = ENV_MUTEX.lock().unwrap(); - std::env::set_var("SPROUT_MEDIA_SERVER_DOMAIN", "custom.example.com"); + std::env::set_var("BUZZ_MEDIA_SERVER_DOMAIN", "custom.example.com"); std::env::set_var("RELAY_URL", "ws://localhost:3000"); let config = Config::from_env().expect("config"); - std::env::remove_var("SPROUT_MEDIA_SERVER_DOMAIN"); + std::env::remove_var("BUZZ_MEDIA_SERVER_DOMAIN"); std::env::remove_var("RELAY_URL"); assert_eq!( config.media.server_domain.as_deref(), diff --git a/crates/sprout-relay/src/connection.rs b/crates/buzz-relay/src/connection.rs similarity index 97% rename from crates/sprout-relay/src/connection.rs rename to crates/buzz-relay/src/connection.rs index b92af84a0..3d3e2eac7 100644 --- a/crates/sprout-relay/src/connection.rs +++ b/crates/buzz-relay/src/connection.rs @@ -13,8 +13,8 @@ use tokio_util::sync::CancellationToken; use tracing::{debug, info, trace, warn}; use uuid::Uuid; +use buzz_auth::{generate_challenge, AuthContext}; use nostr::Filter; -use sprout_auth::{generate_challenge, AuthContext}; use crate::handlers; use crate::protocol::{ClientMessage, RelayMessage}; @@ -85,7 +85,7 @@ impl ConnectionState { let count = self.backpressure_count.fetch_add(1, Ordering::Relaxed) + 1; if count >= SLOW_CLIENT_GRACE_LIMIT { warn!(conn_id = %self.conn_id, count, "sustained backpressure — closing slow client"); - metrics::counter!("sprout_ws_backpressure_disconnects_total").increment(1); + metrics::counter!("buzz_ws_backpressure_disconnects_total").increment(1); self.cancel.cancel(); } else { warn!(conn_id = %self.conn_id, count, grace = SLOW_CLIENT_GRACE_LIMIT, "send buffer full — grace {count}/{SLOW_CLIENT_GRACE_LIMIT}"); @@ -139,7 +139,7 @@ pub async fn handle_connection(socket: WebSocket, state: Arc, addr: So }); info!(conn_id = %conn_id, addr = %addr, "WebSocket connection established"); - metrics::counter!("sprout_ws_connections_total").increment(1); + metrics::counter!("buzz_ws_connections_total").increment(1); let challenge_msg = RelayMessage::auth_challenge(&challenge); if tx @@ -153,7 +153,7 @@ pub async fn handle_connection(socket: WebSocket, state: Arc, addr: So // Gauge incremented AFTER challenge send succeeds — early disconnects // don't leak. Decremented in the cleanup path below. - metrics::gauge!("sprout_ws_connections_active").increment(1.0); + metrics::gauge!("buzz_ws_connections_active").increment(1.0); // Register after challenge succeeds — avoids leaked entries on early disconnect. state.conn_manager.register( @@ -200,7 +200,7 @@ pub async fn handle_connection(socket: WebSocket, state: Arc, addr: So let _ = state.pubsub.clear_presence(&auth_ctx.pubkey).await; } } - metrics::gauge!("sprout_ws_connections_active").decrement(1.0); + metrics::gauge!("buzz_ws_connections_active").decrement(1.0); info!(conn_id = %conn_id, addr = %addr, "WebSocket connection closed"); drop(permit); diff --git a/crates/sprout-relay/src/error.rs b/crates/buzz-relay/src/error.rs similarity index 91% rename from crates/sprout-relay/src/error.rs rename to crates/buzz-relay/src/error.rs index 85027b3f5..53f2c0db5 100644 --- a/crates/sprout-relay/src/error.rs +++ b/crates/buzz-relay/src/error.rs @@ -15,15 +15,15 @@ pub enum RelayError { /// A database operation failed. #[error("Database error: {0}")] - Database(#[from] sprout_db::DbError), + Database(#[from] buzz_db::DbError), /// An authentication error from the auth service. #[error("Auth error: {0}")] - Auth(#[from] sprout_auth::AuthError), + Auth(#[from] buzz_auth::AuthError), /// A pub/sub error from the pubsub service. #[error("PubSub error: {0}")] - PubSub(#[from] sprout_pubsub::PubSubError), + PubSub(#[from] buzz_pubsub::PubSubError), /// The relay has reached its maximum number of concurrent connections. #[error("Connection limit reached")] diff --git a/crates/sprout-relay/src/handlers/auth.rs b/crates/buzz-relay/src/handlers/auth.rs similarity index 94% rename from crates/sprout-relay/src/handlers/auth.rs rename to crates/buzz-relay/src/handlers/auth.rs index c7eb0bfd7..1b20c04fb 100644 --- a/crates/sprout-relay/src/handlers/auth.rs +++ b/crates/buzz-relay/src/handlers/auth.rs @@ -18,7 +18,7 @@ use crate::protocol::RelayMessage; use crate::state::AppState; /// Extract a NIP-OA `auth` tag from a verified AUTH event and serialize it as -/// the JSON-array string that [`sprout_sdk::nip_oa::verify_auth_tag`] expects. +/// the JSON-array string that [`buzz_sdk::nip_oa::verify_auth_tag`] expects. /// /// Returns `None` if no `auth` tag is present (direct-member auth path) or if /// more than one `auth` tag exists (per NIP-OA spec: >1 auth tag ⇒ no valid tag). @@ -73,7 +73,7 @@ pub async fn handle_auth(event: nostr::Event, conn: Arc, state: let relay_url = state.config.relay_url.clone(); let auth_svc = Arc::clone(&state.auth); - metrics::counter!("sprout_auth_attempts_total", "method" => "nip42").increment(1); + metrics::counter!("buzz_auth_attempts_total", "method" => "nip42").increment(1); // Pure NIP-42 verification — crypto only, no DB lookups. match auth_svc @@ -85,7 +85,7 @@ pub async fn handle_auth(event: nostr::Event, conn: Arc, state: // Pubkey allowlist gate — only for pubkey-only auth. if state.config.pubkey_allowlist_enabled - && auth_ctx.auth_method == sprout_auth::AuthMethod::Nip42 + && auth_ctx.auth_method == buzz_auth::AuthMethod::Nip42 { let allowed = match state.db.is_pubkey_allowed(pubkey.as_bytes()).await { Ok(v) => v, @@ -97,7 +97,7 @@ pub async fn handle_auth(event: nostr::Event, conn: Arc, state: }; if !allowed { warn!(conn_id = %conn_id, pubkey = %pubkey.to_hex(), "pubkey not in allowlist"); - metrics::counter!("sprout_auth_failures_total", "reason" => "allowlist_denied") + metrics::counter!("buzz_auth_failures_total", "reason" => "allowlist_denied") .increment(1); *conn.auth_state.write().await = AuthState::Failed; conn.send(RelayMessage::ok( @@ -120,7 +120,7 @@ pub async fn handle_auth(event: nostr::Event, conn: Arc, state: Ok(owner) => owner, Err(e) => { warn!(conn_id = %conn_id, pubkey = %pubkey.to_hex(), error = ?e, "not a relay member"); - metrics::counter!("sprout_auth_failures_total", "reason" => "not_relay_member") + metrics::counter!("buzz_auth_failures_total", "reason" => "not_relay_member") .increment(1); *conn.auth_state.write().await = AuthState::Failed; conn.send(RelayMessage::ok( @@ -223,8 +223,7 @@ pub async fn handle_auth(event: nostr::Event, conn: Arc, state: } Err(e) => { warn!(conn_id = %conn_id, error = %e, "NIP-42 auth failed"); - metrics::counter!("sprout_auth_failures_total", "reason" => "nip42_invalid") - .increment(1); + metrics::counter!("buzz_auth_failures_total", "reason" => "nip42_invalid").increment(1); *conn.auth_state.write().await = AuthState::Failed; conn.send(RelayMessage::ok( &event_id_hex, diff --git a/crates/sprout-relay/src/handlers/close.rs b/crates/buzz-relay/src/handlers/close.rs similarity index 100% rename from crates/sprout-relay/src/handlers/close.rs rename to crates/buzz-relay/src/handlers/close.rs diff --git a/crates/sprout-relay/src/handlers/command_executor.rs b/crates/buzz-relay/src/handlers/command_executor.rs similarity index 96% rename from crates/sprout-relay/src/handlers/command_executor.rs rename to crates/buzz-relay/src/handlers/command_executor.rs index d1a167d7d..02abc985b 100644 --- a/crates/sprout-relay/src/handlers/command_executor.rs +++ b/crates/buzz-relay/src/handlers/command_executor.rs @@ -17,9 +17,9 @@ use sha2::{Digest, Sha256}; use tracing::warn; use uuid::Uuid; -use sprout_core::kind::*; -use sprout_db::workflow::{ApprovalStatus, RunStatus}; -use sprout_workflow::executor::TriggerContext; +use buzz_core::kind::*; +use buzz_db::workflow::{ApprovalStatus, RunStatus}; +use buzz_workflow::executor::TriggerContext; use crate::state::AppState; use crate::webhook_secret; @@ -586,14 +586,14 @@ async fn handle_workflow_def( } // 3. Parse YAML from event.content - let (def, definition_json_str) = sprout_workflow::WorkflowEngine::parse_yaml(&event.content) + let (def, definition_json_str) = buzz_workflow::WorkflowEngine::parse_yaml(&event.content) .map_err(|e| IngestError::Rejected(format!("invalid: workflow YAML parse error: {e}")))?; let mut definition_json: serde_json::Value = serde_json::from_str(&definition_json_str) .map_err(|e| IngestError::Internal(format!("error: json parse of definition: {e}")))?; // Generate webhook secret if this workflow uses a Webhook trigger - let webhook_secret = if matches!(def.trigger, sprout_workflow::TriggerDef::Webhook) { + let webhook_secret = if matches!(def.trigger, buzz_workflow::TriggerDef::Webhook) { let secret = webhook_secret::generate_webhook_secret(); webhook_secret::inject_secret(&mut definition_json, &secret); Some(secret) @@ -747,7 +747,7 @@ async fn handle_workflow_trigger( let def_value = workflow.definition.clone(); let trigger_ctx_clone = trigger_ctx.clone(); tokio::spawn(async move { - let def: sprout_workflow::WorkflowDef = match serde_json::from_value(def_value) { + let def: buzz_workflow::WorkflowDef = match serde_json::from_value(def_value) { Ok(d) => d, Err(e) => { tracing::error!("workflow_trigger: failed to parse definition: {e}"); @@ -767,7 +767,7 @@ async fn handle_workflow_trigger( } }; - let result = sprout_workflow::executor::execute_from_step( + let result = buzz_workflow::executor::execute_from_step( &engine, run_id, &def, @@ -1071,8 +1071,8 @@ async fn handle_approval_deny( /// Resume a suspended workflow run after an approval gate has been granted. async fn resume_workflow_after_approval( - engine: Arc, - db: sprout_db::Db, + engine: Arc, + db: buzz_db::Db, run_id: Uuid, workflow_id: Uuid, resume_index: usize, @@ -1102,26 +1102,26 @@ async fn resume_workflow_after_approval( } }; - let def: sprout_workflow::WorkflowDef = - match serde_json::from_value(workflow.definition.clone()) { - Ok(d) => d, - Err(e) => { - tracing::error!("resume_workflow: failed to parse workflow definition: {e}"); - if let Err(db_err) = db - .update_workflow_run( - run_id, - RunStatus::Failed, - run.current_step, - &run.execution_trace, - Some(&format!("definition parse error: {e}")), - ) - .await - { - tracing::error!("resume_workflow: failed to mark run as failed: {db_err}"); - } - return; + let def: buzz_workflow::WorkflowDef = match serde_json::from_value(workflow.definition.clone()) + { + Ok(d) => d, + Err(e) => { + tracing::error!("resume_workflow: failed to parse workflow definition: {e}"); + if let Err(db_err) = db + .update_workflow_run( + run_id, + RunStatus::Failed, + run.current_step, + &run.execution_trace, + Some(&format!("definition parse error: {e}")), + ) + .await + { + tracing::error!("resume_workflow: failed to mark run as failed: {db_err}"); } - }; + return; + } + }; // Reconstruct step_outputs from execution trace for template resolution let mut initial_outputs: std::collections::HashMap = @@ -1146,7 +1146,7 @@ async fn resume_workflow_after_approval( // Execute remaining steps let existing_trace = run.execution_trace.as_array().cloned(); - let result = sprout_workflow::executor::execute_from_step( + let result = buzz_workflow::executor::execute_from_step( &engine, run_id, &def, diff --git a/crates/sprout-relay/src/handlers/count.rs b/crates/buzz-relay/src/handlers/count.rs similarity index 95% rename from crates/sprout-relay/src/handlers/count.rs rename to crates/buzz-relay/src/handlers/count.rs index 22331c9c8..bdc3f5961 100644 --- a/crates/sprout-relay/src/handlers/count.rs +++ b/crates/buzz-relay/src/handlers/count.rs @@ -99,8 +99,7 @@ pub async fn handle_count( match state.db.query_events(&q).await { Ok(stored_events) => { for se in stored_events { - if sprout_core::filter::filters_match(std::slice::from_ref(filter), &se) - { + if buzz_core::filter::filters_match(std::slice::from_ref(filter), &se) { total += 1; } } @@ -138,8 +137,7 @@ pub async fn handle_count( match state.db.query_events(&query).await { Ok(stored_events) => { for se in stored_events { - if sprout_core::filter::filters_match(std::slice::from_ref(filter), &se) - { + if buzz_core::filter::filters_match(std::slice::from_ref(filter), &se) { total += 1; } } diff --git a/crates/sprout-relay/src/handlers/event.rs b/crates/buzz-relay/src/handlers/event.rs similarity index 94% rename from crates/sprout-relay/src/handlers/event.rs rename to crates/buzz-relay/src/handlers/event.rs index f1ba70e32..57efad9c2 100644 --- a/crates/sprout-relay/src/handlers/event.rs +++ b/crates/buzz-relay/src/handlers/event.rs @@ -4,17 +4,17 @@ use std::sync::Arc; use tracing::{debug, error, info, warn}; -use nostr::{Event, PublicKey}; -use sprout_core::event::StoredEvent; -use sprout_core::kind::{ +use buzz_core::event::StoredEvent; +use buzz_core::kind::{ event_kind_u32, is_ephemeral, KIND_AGENT_OBSERVER_FRAME, KIND_GIFT_WRAP, KIND_MESH_CONNECT_REQUEST, KIND_MESH_STATUS_REPORT, KIND_PRESENCE_UPDATE, }; -use sprout_core::observer::{ +use buzz_core::observer::{ content_looks_like_nip44, OBSERVER_AGENT_TAG, OBSERVER_FRAME_CONTROL, OBSERVER_FRAME_TAG, OBSERVER_FRAME_TELEMETRY, }; -use sprout_core::verification::verify_event; +use buzz_core::verification::verify_event; +use nostr::{Event, PublicKey}; use crate::connection::{AuthState, ConnectionState}; use crate::protocol::RelayMessage; @@ -24,7 +24,7 @@ use super::ingest::{IngestAuth, IngestError}; /// Increment the rejection counter with a bounded reason label. fn reject(reason: &'static str) { - metrics::counter!("sprout_events_rejected_total", "reason" => reason).increment(1); + metrics::counter!("buzz_events_rejected_total", "reason" => reason).increment(1); } /// Bound the `kind` label to prevent cardinality explosion from arbitrary Nostr kinds. @@ -115,7 +115,7 @@ pub(crate) async fn dispatch_persistent_event( let matches = state.sub_registry.fan_out(stored_event); let matches = filter_fanout_by_access(state, stored_event, matches).await; - metrics::histogram!("sprout_fanout_recipients").record(matches.len() as f64); + metrics::histogram!("buzz_fanout_recipients").record(matches.len() as f64); debug!( event_id = %event_id_hex, channel_id = ?stored_event.channel_id, @@ -128,7 +128,7 @@ pub(crate) async fn dispatch_persistent_event( // For viewer-private snapshots (kind:30622), live fan-out must reach only the // owner — a kindless `ids:[…]` subscription can otherwise match it. Pull paths // (HTTP /query, WS historical) are gated separately by reader_authorized_for_event. - let dm_visibility_owner: Option = (kind_u32 == sprout_core::kind::KIND_DM_VISIBILITY) + let dm_visibility_owner: Option = (kind_u32 == buzz_core::kind::KIND_DM_VISIBILITY) .then(|| { let p = nostr::SingleLetterTag::lowercase(nostr::Alphabet::P); stored_event @@ -165,13 +165,13 @@ pub(crate) async fn dispatch_persistent_event( // Skip search indexing for NIP-17 gift wraps (ciphertext) and NIP-DV // visibility snapshots (per-viewer private hide state, owner-gated reads). if kind_u32 != KIND_GIFT_WRAP - && kind_u32 != sprout_core::kind::KIND_DM_VISIBILITY + && kind_u32 != buzz_core::kind::KIND_DM_VISIBILITY && state .search_index_tx .try_send(stored_event.clone()) .is_err() { - metrics::counter!("sprout_search_index_errors_total").increment(1); + metrics::counter!("buzz_search_index_errors_total").increment(1); warn!(event_id = %event_id_hex, "Search index channel full — dropping event"); } @@ -182,17 +182,17 @@ pub(crate) async fn dispatch_persistent_event( // DB is genuinely overloaded and the relay should slow down rather than // accumulate unbounded in-memory state. DB write failures in the worker are // logged but not retried (same as the previous per-event tokio::spawn). - let audit_entry = sprout_audit::NewAuditEntry { + let audit_entry = buzz_audit::NewAuditEntry { event_id: event_id_hex.clone(), event_kind: kind_u32, actor_pubkey: actor_pubkey_hex.to_string(), - action: sprout_audit::AuditAction::EventCreated, + action: buzz_audit::AuditAction::EventCreated, channel_id: stored_event.channel_id, metadata: serde_json::Value::Null, }; if let Err(e) = state.audit_tx.send(audit_entry).await { error!(event_id = %event_id_hex, "Audit channel closed — entry lost: {e}"); - metrics::counter!("sprout_audit_send_errors_total").increment(1); + metrics::counter!("buzz_audit_send_errors_total").increment(1); } // Skip workflow triggering for workflow-execution kinds and relay-signed workflow messages. @@ -201,10 +201,10 @@ pub(crate) async fn dispatch_persistent_event( .event .tags .iter() - .any(|t| t.as_slice().first().map(|s| s.as_str()) == Some("sprout:workflow")); + .any(|t| t.as_slice().first().map(|s| s.as_str()) == Some("buzz:workflow")); - if !sprout_core::kind::is_workflow_execution_kind(kind_u32) - && !sprout_core::kind::is_command_kind(kind_u32) + if !buzz_core::kind::is_workflow_execution_kind(kind_u32) + && !buzz_core::kind::is_command_kind(kind_u32) && !is_relay_workflow_msg && kind_u32 != KIND_GIFT_WRAP { @@ -215,7 +215,7 @@ pub(crate) async fn dispatch_persistent_event( if let Err(e) = workflow_engine.on_event(&workflow_event).await { tracing::error!(event_id = ?workflow_event.event.id, "Workflow trigger failed: {e}"); } else { - metrics::counter!("sprout_workflow_runs_total", "trigger" => trigger_kind) + metrics::counter!("buzz_workflow_runs_total", "trigger" => trigger_kind) .increment(1); } }); @@ -234,7 +234,7 @@ pub async fn handle_event(event: Event, conn: Arc, state: Arc kind_str.clone()).increment(1); + metrics::counter!("buzz_events_received_total", "kind" => kind_str.clone()).increment(1); // ── Extract auth from WS connection state ──────────────────────────── let (conn_id, pubkey_bytes, auth_pubkey, scopes, channel_ids) = { @@ -263,7 +263,7 @@ pub async fn handle_event(event: Event, conn: Arc, state: Arc, state: Arc, state: Arc, state: Arc, state: Arc { if result.accepted { - metrics::counter!("sprout_events_stored_total", "kind" => kind_str).increment(1); + metrics::counter!("buzz_events_stored_total", "kind" => kind_str).increment(1); info!( event_id = %result.event_id, kind = kind_u32, @@ -370,7 +370,7 @@ pub async fn handle_event(event: Event, conn: Arc, state: Arc(event: &'a Event, tag_name: &str) -> Result<&'a str, S #[cfg(test)] mod tests { - use nostr::{EventBuilder, Keys, Kind, Tag}; - use sprout_core::kind::{ + use buzz_core::kind::{ KIND_AGENT_OBSERVER_FRAME, KIND_CANVAS, KIND_FORUM_COMMENT, KIND_FORUM_POST, KIND_FORUM_VOTE, KIND_PRESENCE_UPDATE, KIND_STREAM_MESSAGE, KIND_STREAM_MESSAGE_DIFF, }; - use sprout_core::observer::{ + use buzz_core::observer::{ encrypt_observer_payload, OBSERVER_AGENT_TAG, OBSERVER_FRAME_CONTROL, OBSERVER_FRAME_TAG, OBSERVER_FRAME_TELEMETRY, }; + use nostr::{EventBuilder, Keys, Kind, Tag}; #[test] fn channel_scoped_content_kinds_require_h_tags() { @@ -981,8 +981,8 @@ mod tests { use std::sync::atomic::AtomicU8; use std::sync::Arc; + use buzz_core::StoredEvent; use nostr::{EventBuilder, Keys, Kind}; - use sprout_core::StoredEvent; use tokio::sync::{mpsc, Mutex}; use tokio_util::sync::CancellationToken; use uuid::Uuid; @@ -1000,28 +1000,28 @@ mod tests { async fn test_state() -> Arc { let config = test_config(); let pool = sqlx::PgPool::connect_lazy(&config.database_url).expect("lazy pg pool"); - let db = sprout_db::Db::from_pool(pool.clone()); + let db = buzz_db::Db::from_pool(pool.clone()); let redis_pool = deadpool_redis::Config::from_url(&config.redis_url) .create_pool(Some(deadpool_redis::Runtime::Tokio1)) .expect("redis pool"); let pubsub = Arc::new( - sprout_pubsub::PubSubManager::new(&config.redis_url, redis_pool.clone()) + buzz_pubsub::PubSubManager::new(&config.redis_url, redis_pool.clone()) .await .expect("pubsub manager"), ); - let audit = sprout_audit::AuditService::new(pool); - let auth = sprout_auth::AuthService::new(config.auth.clone()); - let search = sprout_search::SearchService::new(sprout_search::SearchConfig { + let audit = buzz_audit::AuditService::new(pool); + let auth = buzz_auth::AuthService::new(config.auth.clone()); + let search = buzz_search::SearchService::new(buzz_search::SearchConfig { url: config.typesense_url.clone(), api_key: config.typesense_key.clone(), collection: "events".to_string(), }); - let workflow_engine = Arc::new(sprout_workflow::WorkflowEngine::new( + let workflow_engine = Arc::new(buzz_workflow::WorkflowEngine::new( db.clone(), - sprout_workflow::WorkflowConfig::default(), + buzz_workflow::WorkflowConfig::default(), )); let media_storage = - sprout_media::MediaStorage::new(&config.media).expect("media storage"); + buzz_media::MediaStorage::new(&config.media).expect("media storage"); let (state, _audit_shutdown) = AppState::new( config, db, diff --git a/crates/sprout-relay/src/handlers/identity_archive.rs b/crates/buzz-relay/src/handlers/identity_archive.rs similarity index 94% rename from crates/sprout-relay/src/handlers/identity_archive.rs rename to crates/buzz-relay/src/handlers/identity_archive.rs index 751b96dd2..1e8ed3634 100644 --- a/crates/sprout-relay/src/handlers/identity_archive.rs +++ b/crates/buzz-relay/src/handlers/identity_archive.rs @@ -9,8 +9,8 @@ use std::sync::Arc; use nostr::{Event, PublicKey}; use tracing::{info, warn}; -use sprout_core::kind::{KIND_IA_ARCHIVE_REQUEST, KIND_IA_UNARCHIVE_REQUEST, KIND_PROFILE}; -use sprout_db::EventQuery; +use buzz_core::kind::{KIND_IA_ARCHIVE_REQUEST, KIND_IA_UNARCHIVE_REQUEST, KIND_PROFILE}; +use buzz_db::EventQuery; use crate::handlers::side_effects::{ publish_nipia_archival_list, publish_nipia_archived, publish_nipia_unarchived, @@ -311,7 +311,7 @@ fn extract_single_auth_tag_json(event: &Event) -> Result { fn verify_auth_tag_owner(auth_tag_json: &str, target_hex: &str) -> Result { let target_pubkey = PublicKey::from_hex(target_hex).map_err(|e| format!("invalid target pubkey: {e}"))?; - sprout_sdk::nip_oa::verify_auth_tag(auth_tag_json, &target_pubkey) + buzz_sdk::nip_oa::verify_auth_tag(auth_tag_json, &target_pubkey) .map(|owner| owner.to_hex()) .map_err(|e| e.to_string()) } @@ -428,33 +428,33 @@ mod tests { async fn test_pool() -> Option { let url = std::env::var("DATABASE_URL") - .unwrap_or_else(|_| "postgres://sprout:sprout_dev@localhost:5432/sprout".into()); + .unwrap_or_else(|_| "postgres://buzz:buzz_dev@localhost:5432/buzz".into()); sqlx::PgPool::connect(&url).await.ok() } async fn test_state(pool: sqlx::PgPool) -> Option> { - let db = sprout_db::Db::from_pool(pool.clone()); + let db = buzz_db::Db::from_pool(pool.clone()); let config = crate::config::Config::from_env().ok()?; let redis_pool = deadpool_redis::Config::from_url(&config.redis_url) .create_pool(Some(deadpool_redis::Runtime::Tokio1)) .ok()?; let pubsub = Arc::new( - sprout_pubsub::PubSubManager::new(&config.redis_url, redis_pool.clone()) + buzz_pubsub::PubSubManager::new(&config.redis_url, redis_pool.clone()) .await .ok()?, ); - let audit = sprout_audit::AuditService::new(pool); - let auth = sprout_auth::AuthService::new(config.auth.clone()); - let search = sprout_search::SearchService::new(sprout_search::SearchConfig { + let audit = buzz_audit::AuditService::new(pool); + let auth = buzz_auth::AuthService::new(config.auth.clone()); + let search = buzz_search::SearchService::new(buzz_search::SearchConfig { url: config.typesense_url.clone(), api_key: config.typesense_key.clone(), collection: "events".to_string(), }); - let workflow_engine = Arc::new(sprout_workflow::WorkflowEngine::new( + let workflow_engine = Arc::new(buzz_workflow::WorkflowEngine::new( db.clone(), - sprout_workflow::WorkflowConfig::default(), + buzz_workflow::WorkflowConfig::default(), )); - let media_storage = sprout_media::MediaStorage::new(&config.media).ok()?; + let media_storage = buzz_media::MediaStorage::new(&config.media).ok()?; let (state, _audit_shutdown) = crate::state::AppState::new( config, db, @@ -471,9 +471,9 @@ mod tests { } fn auth_tag(owner_keys: &Keys, target_pubkey: &nostr::PublicKey) -> Tag { - let tag_json = sprout_sdk::nip_oa::compute_auth_tag(owner_keys, target_pubkey, "") + let tag_json = buzz_sdk::nip_oa::compute_auth_tag(owner_keys, target_pubkey, "") .expect("compute auth tag"); - sprout_sdk::nip_oa::parse_auth_tag(&tag_json).expect("parse auth tag") + buzz_sdk::nip_oa::parse_auth_tag(&tag_json).expect("parse auth tag") } fn profile_event(target_keys: &Keys, auth_tag: Tag, created_at: u64) -> Event { diff --git a/crates/sprout-relay/src/handlers/imeta.rs b/crates/buzz-relay/src/handlers/imeta.rs similarity index 99% rename from crates/sprout-relay/src/handlers/imeta.rs rename to crates/buzz-relay/src/handlers/imeta.rs index 441bd9712..a7341698b 100644 --- a/crates/sprout-relay/src/handlers/imeta.rs +++ b/crates/buzz-relay/src/handlers/imeta.rs @@ -1,6 +1,6 @@ //! imeta tag validation helpers — shared between ingest pipeline and bridge. -use sprout_media::validation::mime_to_ext; +use buzz_media::validation::mime_to_ext; /// Validate imeta tags for correctness and safety. /// @@ -207,7 +207,7 @@ pub fn validate_imeta_tags(tags: &[Vec], media_base_url: &str) -> Result /// and that the claimed metadata (size, MIME) matches the sidecar. pub async fn verify_imeta_blobs( tags: &[Vec], - storage: &sprout_media::MediaStorage, + storage: &buzz_media::MediaStorage, ) -> Result<(), String> { for tag in tags { let mut x_value = String::new(); diff --git a/crates/sprout-relay/src/handlers/ingest.rs b/crates/buzz-relay/src/handlers/ingest.rs similarity index 98% rename from crates/sprout-relay/src/handlers/ingest.rs rename to crates/buzz-relay/src/handlers/ingest.rs index 91004ec8f..9eb7dabc7 100644 --- a/crates/sprout-relay/src/handlers/ingest.rs +++ b/crates/buzz-relay/src/handlers/ingest.rs @@ -9,9 +9,8 @@ use chrono::Utc; use tracing::{debug, error, info, warn}; use uuid::Uuid; -use nostr::Event; -use sprout_auth::Scope; -use sprout_core::kind::{ +use buzz_auth::Scope; +use buzz_core::kind::{ event_kind_u32, is_identity_archive_request_kind, is_parameterized_replaceable, is_relay_admin_kind, KIND_AGENT_ENGRAM, KIND_AGENT_PROFILE, KIND_APPROVAL_DENY, KIND_APPROVAL_GRANT, KIND_AUTH, KIND_BOOKMARK_LIST, KIND_BOOKMARK_SET, KIND_CANVAS, @@ -33,7 +32,8 @@ use sprout_core::kind::{ KIND_USER_STATUS, KIND_WORKFLOW_DEF, KIND_WORKFLOW_TRIGGER, RELAY_ADMIN_ADD_MEMBER, RELAY_ADMIN_CHANGE_ROLE, RELAY_ADMIN_REMOVE_MEMBER, }; -use sprout_core::verification::verify_event; +use buzz_core::verification::verify_event; +use nostr::Event; use crate::state::AppState; @@ -270,7 +270,7 @@ pub(crate) enum ReactionChannelResult { /// Derive channel_id from the target event for NIP-25 reactions. pub(crate) async fn derive_reaction_channel( - db: &sprout_db::Db, + db: &buzz_db::Db, event: &Event, ) -> ReactionChannelResult { let target_hex = match event.tags.iter().rev().find_map(|tag| { @@ -457,8 +457,8 @@ pub(crate) struct ThreadMetadataOwned { } impl ThreadMetadataOwned { - pub fn as_params(&self) -> sprout_db::event::ThreadMetadataParams<'_> { - sprout_db::event::ThreadMetadataParams { + pub fn as_params(&self) -> buzz_db::event::ThreadMetadataParams<'_> { + buzz_db::event::ThreadMetadataParams { event_id: &self.event_id, event_created_at: self.event_created_at, channel_id: self.channel_id, @@ -897,7 +897,7 @@ fn validate_engram_envelope(event: &Event) -> Result<(), String> { /// decryption happen at the reader. The intent is to refuse obvious junk so a /// malformed event cannot win NIP-33 replacement against a valid head and then /// be silently skipped by `validate_and_decrypt`. Mirrors the validator in -/// `sprout-pair-relay::validate_nip44_content`. +/// `buzz-pair-relay::validate_nip44_content`. fn validate_engram_nip44_content(content: &str) -> Result<(), String> { if content.is_empty() { return Err("agent-engram content must not be empty (NIP-44 ciphertext)".to_string()); @@ -990,7 +990,7 @@ pub async fn ingest_event( } // ── 1c. Reject relay-only kinds from external submission ───────────── - if sprout_core::kind::is_relay_only_kind(kind_u32) { + if buzz_core::kind::is_relay_only_kind(kind_u32) { return Err(IngestError::Rejected("restricted: relay-only kind".into())); } @@ -1080,7 +1080,7 @@ pub async fn ingest_event( // ── 4b. Route command kinds to command executor ────────────────────── // Command kinds are routed AFTER signature verification, timestamp check, // pubkey/auth match, and scope validation — never before. - if sprout_core::kind::is_command_kind(kind_u32) { + if buzz_core::kind::is_command_kind(kind_u32) { return super::command_executor::handle_command(state, event, auth).await; } @@ -1244,18 +1244,18 @@ pub async fn ingest_event( .map_err(|e| IngestError::Internal(format!("database error: {e}")))?; match remove_result { - sprout_db::relay_members::RemoveResult::Removed => {} - sprout_db::relay_members::RemoveResult::NotFound => { + buzz_db::relay_members::RemoveResult::Removed => {} + buzz_db::relay_members::RemoveResult::NotFound => { return Err(IngestError::Rejected( "invalid: you are not a relay member".into(), )); } - sprout_db::relay_members::RemoveResult::IsOwner => { + buzz_db::relay_members::RemoveResult::IsOwner => { return Err(IngestError::Rejected( "invalid: relay owner cannot leave".into(), )); } - sprout_db::relay_members::RemoveResult::RoleMismatch => { + buzz_db::relay_members::RemoveResult::RoleMismatch => { // remove_relay_member (no role filter) never returns RoleMismatch — // this arm is unreachable but exhaustiveness requires it. return Err(IngestError::Internal( @@ -1416,10 +1416,10 @@ pub async fn ingest_event( }) .unwrap_or_else(|| "stream".to_string()); - let visibility: sprout_db::channel::ChannelVisibility = visibility_str + let visibility: buzz_db::channel::ChannelVisibility = visibility_str .parse() .map_err(|_| IngestError::Rejected(format!("invalid visibility: {visibility_str}")))?; - let channel_type: sprout_db::channel::ChannelType = + let channel_type: buzz_db::channel::ChannelType = channel_type_str.parse().map_err(|_| { IngestError::Rejected(format!("invalid channel_type: {channel_type_str}")) })?; @@ -1656,7 +1656,7 @@ pub async fn ingest_event( }); } - let (stored_event, was_inserted) = if sprout_core::kind::is_replaceable(kind_u32) { + let (stored_event, was_inserted) = if buzz_core::kind::is_replaceable(kind_u32) { // NIP-16 replaceable event — atomic replace with stale-write protection. // channel_id is None for global kinds (0, 1, 3) due to step 5b above. state @@ -1666,12 +1666,12 @@ pub async fn ingest_event( .map_err(|e| IngestError::Internal(format!("error: {e}")))? } else if is_parameterized_replaceable(kind_u32) { // NIP-33 parameterized replaceable — keyed by (kind, pubkey, d_tag). - let d_tag = sprout_db::event::extract_d_tag(&event).unwrap_or_default(); - if d_tag.len() > sprout_db::event::D_TAG_MAX_LEN { + let d_tag = buzz_db::event::extract_d_tag(&event).unwrap_or_default(); + if d_tag.len() > buzz_db::event::D_TAG_MAX_LEN { return Err(IngestError::Rejected(format!( "invalid: d tag too long ({} bytes, max {})", d_tag.len(), - sprout_db::event::D_TAG_MAX_LEN, + buzz_db::event::D_TAG_MAX_LEN, ))); } state @@ -1697,7 +1697,7 @@ pub async fn ingest_event( state.invalidate_channel_deleted(); } return Err(match e { - sprout_db::DbError::AuthEventRejected => { + buzz_db::DbError::AuthEventRejected => { IngestError::Rejected("invalid: AUTH events cannot be stored".into()) } other => IngestError::Internal(format!("error: database error: {other}")), @@ -1750,7 +1750,7 @@ pub async fn ingest_event( #[cfg(test)] mod tests { use super::*; - use sprout_core::kind::{ + use buzz_core::kind::{ KIND_CANVAS, KIND_FORUM_COMMENT, KIND_FORUM_POST, KIND_FORUM_VOTE, KIND_LONG_FORM, KIND_PRESENCE_UPDATE, KIND_STREAM_MESSAGE, KIND_STREAM_MESSAGE_DIFF, KIND_USER_STATUS, }; @@ -1952,7 +1952,7 @@ mod tests { #[test] fn mesh_llm_relay_status_is_global_only_and_relay_only() { assert!(is_global_only_kind(KIND_MESH_LLM_RELAY_STATUS)); - assert!(sprout_core::kind::is_relay_only_kind( + assert!(buzz_core::kind::is_relay_only_kind( KIND_MESH_LLM_RELAY_STATUS )); assert!(!requires_h_channel_scope(KIND_MESH_LLM_RELAY_STATUS)); diff --git a/crates/sprout-relay/src/handlers/mesh_signaling.rs b/crates/buzz-relay/src/handlers/mesh_signaling.rs similarity index 96% rename from crates/sprout-relay/src/handlers/mesh_signaling.rs rename to crates/buzz-relay/src/handlers/mesh_signaling.rs index 873afb787..4de6d15c1 100644 --- a/crates/sprout-relay/src/handlers/mesh_signaling.rs +++ b/crates/buzz-relay/src/handlers/mesh_signaling.rs @@ -1,6 +1,6 @@ //! Mesh hole-punch signaling: the relay's only role in the v1 direct-iroh mesh. //! -//! v1 mesh is "Sprout-coordinated direct iroh" — no server-side iroh relay/proxy. +//! v1 mesh is "Buzz-coordinated direct iroh" — no server-side iroh relay/proxy. //! The relay's entire job for connectivity is: when a member asks to dial a peer //! it discovered via kind:30621, validate that BOTH ends are relay members, then //! emit a *paired* live "call-me-now" so both desktops hole-punch at the same @@ -22,11 +22,11 @@ use std::sync::Arc; -use nostr::{EventBuilder, Kind, Tag}; -use sprout_core::event::StoredEvent; -use sprout_core::kind::{ +use buzz_core::event::StoredEvent; +use buzz_core::kind::{ event_kind_u32, KIND_MESH_CALL_ME_NOW, KIND_MESH_CONNECT_REQUEST, KIND_MESH_STATUS_REPORT, }; +use nostr::{EventBuilder, Kind, Tag}; use crate::api::relay_members::{check_relay_membership, MembershipDecision}; use crate::state::AppState; @@ -76,7 +76,7 @@ pub async fn handle_mesh_event_http( event: &nostr::Event, ) -> Result<(), String> { let event_clone = event.clone(); - tokio::task::spawn_blocking(move || sprout_core::verification::verify_event(&event_clone)) + tokio::task::spawn_blocking(move || buzz_core::verification::verify_event(&event_clone)) .await .map_err(|_| "error: internal verification error".to_string())? .map_err(|e| format!("invalid: {e}"))?; @@ -176,7 +176,7 @@ pub fn call_me_now_content( ) -> String { let mut obj = serde_json::json!({ "v": 1, - "type": "sprout-iroh-call-me-now", + "type": "buzz-iroh-call-me-now", "peer_endpoint_addr": peer_endpoint_addr, "attempt_id": attempt_id, "expires_at": expires_at, @@ -226,7 +226,7 @@ pub async fn handle_connect_request( // Membership gate, applied SYMMETRICALLY to both ends — direct relay members // only, gated purely by relay access. The requester reached this handler via // a NIP-42-authed WS, but that auth can be ViaOwner (NIP-OA delegated) when - // SPROUT_ALLOW_NIP_OA_AUTH is on; v1 mesh excludes delegated identities, so we + // BUZZ_ALLOW_NIP_OA_AUTH is on; v1 mesh excludes delegated identities, so we // re-check the requester here with no auth tag (which makes ViaOwner // unreachable — only Member/OpenRelay/Denied) to match the target check. require_mesh_member(state, requester_pubkey_hex) @@ -317,7 +317,7 @@ async fn publish_channelless_ephemeral(state: &Arc, event: &nostr::Eve } let stored = StoredEvent::new(event.clone(), None); let matches = state.sub_registry.fan_out(&stored); - metrics::histogram!("sprout_fanout_recipients").record(matches.len() as f64); + metrics::histogram!("buzz_fanout_recipients").record(matches.len() as f64); if let Ok(event_json) = serde_json::to_string(event) { for (target_conn_id, sub_id) in &matches { let msg = format!(r#"["EVENT","{sub_id}",{event_json}]"#); @@ -406,7 +406,7 @@ mod tests { fn call_me_now_content_shape() { let s = call_me_now_content("ENDPOINT", None, "att-1", 1234); let v: serde_json::Value = serde_json::from_str(&s).unwrap(); - assert_eq!(v["type"], "sprout-iroh-call-me-now"); + assert_eq!(v["type"], "buzz-iroh-call-me-now"); assert_eq!(v["peer_endpoint_addr"], "ENDPOINT"); assert_eq!(v["attempt_id"], "att-1"); assert_eq!(v["expires_at"], 1234); @@ -498,27 +498,27 @@ mod tests { async fn test_state() -> std::sync::Arc { let config = test_config(); let pool = sqlx::PgPool::connect_lazy(&config.database_url).expect("lazy pg pool"); - let db = sprout_db::Db::from_pool(pool.clone()); + let db = buzz_db::Db::from_pool(pool.clone()); let redis_pool = deadpool_redis::Config::from_url(&config.redis_url) .create_pool(Some(deadpool_redis::Runtime::Tokio1)) .expect("redis pool"); let pubsub = std::sync::Arc::new( - sprout_pubsub::PubSubManager::new(&config.redis_url, redis_pool.clone()) + buzz_pubsub::PubSubManager::new(&config.redis_url, redis_pool.clone()) .await .expect("pubsub manager"), ); - let audit = sprout_audit::AuditService::new(pool); - let auth = sprout_auth::AuthService::new(config.auth.clone()); - let search = sprout_search::SearchService::new(sprout_search::SearchConfig { + let audit = buzz_audit::AuditService::new(pool); + let auth = buzz_auth::AuthService::new(config.auth.clone()); + let search = buzz_search::SearchService::new(buzz_search::SearchConfig { url: config.typesense_url.clone(), api_key: config.typesense_key.clone(), collection: "events".to_string(), }); - let workflow_engine = std::sync::Arc::new(sprout_workflow::WorkflowEngine::new( + let workflow_engine = std::sync::Arc::new(buzz_workflow::WorkflowEngine::new( db.clone(), - sprout_workflow::WorkflowConfig::default(), + buzz_workflow::WorkflowConfig::default(), )); - let media_storage = sprout_media::MediaStorage::new(&config.media).expect("media storage"); + let media_storage = buzz_media::MediaStorage::new(&config.media).expect("media storage"); let (state, _audit_shutdown) = crate::state::AppState::new( config, db, @@ -636,7 +636,7 @@ mod tests { let requester_content: serde_json::Value = serde_json::from_str(&requester_event.content).expect("requester content JSON"); - assert_eq!(requester_content["type"], "sprout-iroh-call-me-now"); + assert_eq!(requester_content["type"], "buzz-iroh-call-me-now"); assert_eq!(requester_content["peer_endpoint_addr"], "PEER_ADDR"); assert_eq!(requester_content["peer_endpoint_id"], "PEER_ID"); assert_eq!(requester_content["attempt_id"], "attempt-1"); @@ -644,7 +644,7 @@ mod tests { let target_content: serde_json::Value = serde_json::from_str(&target_event.content).expect("target content JSON"); - assert_eq!(target_content["type"], "sprout-iroh-call-me-now"); + assert_eq!(target_content["type"], "buzz-iroh-call-me-now"); assert_eq!(target_content["peer_endpoint_addr"], "SELF_ADDR"); assert_eq!(target_content["peer_endpoint_id"], "SELF_ID"); assert_eq!(target_content["attempt_id"], "attempt-1"); diff --git a/crates/sprout-relay/src/handlers/mod.rs b/crates/buzz-relay/src/handlers/mod.rs similarity index 96% rename from crates/sprout-relay/src/handlers/mod.rs rename to crates/buzz-relay/src/handlers/mod.rs index bc6a14ed1..da47d80a1 100644 --- a/crates/sprout-relay/src/handlers/mod.rs +++ b/crates/buzz-relay/src/handlers/mod.rs @@ -41,7 +41,7 @@ pub fn resolve_ttl(event: &nostr::Event, ephemeral_ttl_override: Option) -> tracing::debug!( original, override_val = ovr, - "Applying SPROUT_EPHEMERAL_TTL_OVERRIDE" + "Applying BUZZ_EPHEMERAL_TTL_OVERRIDE" ); Some(ovr) } diff --git a/crates/sprout-relay/src/handlers/relay_admin.rs b/crates/buzz-relay/src/handlers/relay_admin.rs similarity index 98% rename from crates/sprout-relay/src/handlers/relay_admin.rs rename to crates/buzz-relay/src/handlers/relay_admin.rs index 742fc20d6..a034865d4 100644 --- a/crates/sprout-relay/src/handlers/relay_admin.rs +++ b/crates/buzz-relay/src/handlers/relay_admin.rs @@ -16,10 +16,8 @@ use std::sync::Arc; use nostr::Event; use tracing::{info, warn}; -use sprout_core::kind::{ - RELAY_ADMIN_ADD_MEMBER, RELAY_ADMIN_CHANGE_ROLE, RELAY_ADMIN_REMOVE_MEMBER, -}; -use sprout_db::relay_members::RemoveResult; +use buzz_core::kind::{RELAY_ADMIN_ADD_MEMBER, RELAY_ADMIN_CHANGE_ROLE, RELAY_ADMIN_REMOVE_MEMBER}; +use buzz_db::relay_members::RemoveResult; use crate::handlers::side_effects::{ publish_nip43_member_added, publish_nip43_member_removed, publish_nip43_membership_list, diff --git a/crates/sprout-relay/src/handlers/req.rs b/crates/buzz-relay/src/handlers/req.rs similarity index 97% rename from crates/sprout-relay/src/handlers/req.rs rename to crates/buzz-relay/src/handlers/req.rs index 014f93743..5e5420643 100644 --- a/crates/sprout-relay/src/handlers/req.rs +++ b/crates/buzz-relay/src/handlers/req.rs @@ -5,16 +5,16 @@ use std::sync::Arc; use tracing::{debug, warn}; -use hex; -use nostr::Filter; -use sprout_core::filter::filters_match; -use sprout_core::kind::{ +use buzz_core::filter::filters_match; +use buzz_core::kind::{ KIND_AGENT_ENGRAM, KIND_AGENT_OBSERVER_FRAME, KIND_DM_VISIBILITY, KIND_GIFT_WRAP, KIND_MEMBER_ADDED_NOTIFICATION, KIND_MEMBER_REMOVED_NOTIFICATION, }; -use sprout_db::EventQuery; +use buzz_db::EventQuery; +use hex; +use nostr::Filter; -use sprout_auth::Scope; +use buzz_auth::Scope; use crate::connection::{AuthState, ConnectionState}; use crate::protocol::RelayMessage; @@ -224,7 +224,7 @@ pub async fn handle_req( // Result-level read auth: a viewer-private snapshot (kind:30622) is // delivered only to its owner, even if reached via a kindless // `ids:[…]` subscription that skips the filter-level `#p` gate. - if !sprout_core::filter::reader_authorized_for_event(&stored.event, &viewer_hex) { + if !buzz_core::filter::reader_authorized_for_event(&stored.event, &viewer_hex) { continue; } @@ -378,7 +378,7 @@ async fn handle_search_req( break; } - let search_query = sprout_search::SearchQuery { + let search_query = buzz_search::SearchQuery { q: search_text.clone(), filter_by: Some(filter_by.clone()), sort_by: None, // Typesense default = relevance (text_match score) @@ -414,7 +414,7 @@ async fn handle_search_req( } }; - let event_map: std::collections::HashMap<[u8; 32], &sprout_core::StoredEvent> = + let event_map: std::collections::HashMap<[u8; 32], &buzz_core::StoredEvent> = events .iter() .map(|ev| (ev.event.id.to_bytes(), ev)) @@ -441,7 +441,7 @@ async fn handle_search_req( continue; } } - if !sprout_core::filter::reader_authorized_for_event( + if !buzz_core::filter::reader_authorized_for_event( &stored.event, reader_pubkey_hex, ) { @@ -497,7 +497,7 @@ pub fn filter_fully_pushable(filter: &Filter) -> bool { !ks.is_empty() && ks .iter() - .all(|k| sprout_core::kind::is_parameterized_replaceable(k.as_u16() as u32)) + .all(|k| buzz_core::kind::is_parameterized_replaceable(k.as_u16() as u32)) }); for (tag_key, tag_values) in filter.generic_tags.iter() { @@ -567,7 +567,7 @@ fn filter_to_query_params(filter: &Filter, channel_id: Option) -> Ev // We use Some(vec![]) which the DB layer treats as "no matching kinds". vec![] } else { - // Cast to i32 for Postgres INT column; safe because all Sprout kinds fit in i32. + // Cast to i32 for Postgres INT column; safe because all Buzz kinds fit in i32. ks.iter().map(|k| k.as_u16() as i32).collect() } }); @@ -647,7 +647,7 @@ fn filter_to_query_params(filter: &Filter, channel_id: Option) -> Ev !ks.is_empty() && ks .iter() - .all(|&k| sprout_core::kind::is_parameterized_replaceable(k as u32)) + .all(|&k| buzz_core::kind::is_parameterized_replaceable(k as u32)) }); let d_tag_key = nostr::SingleLetterTag::lowercase(nostr::Alphabet::D); let (d_tag, d_tags) = if filter_is_nip33_only { @@ -874,7 +874,7 @@ mod tests { let authed = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; let other = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; let snapshot_id = "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"; - let dm_vis = nostr::Kind::Custom(sprout_core::kind::KIND_DM_VISIBILITY as u16); + let dm_vis = nostr::Kind::Custom(buzz_core::kind::KIND_DM_VISIBILITY as u16); // Knowing another viewer's snapshot id must NOT authorize reading it: // ids alone, or ids + someone else's #p, are both rejected. @@ -896,7 +896,7 @@ mod tests { // The ids exemption still applies to other p-gated kinds (member notifs). let member_notif_ids = Filter::new() .kind(nostr::Kind::Custom( - sprout_core::kind::KIND_MEMBER_ADDED_NOTIFICATION as u16, + buzz_core::kind::KIND_MEMBER_ADDED_NOTIFICATION as u16, )) .id(nostr::EventId::from_hex(snapshot_id).unwrap()); assert!(p_gated_filters_authorized(&[member_notif_ids], authed)); @@ -930,20 +930,20 @@ mod tests { let other = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; let missing_p = Filter::new().kind(nostr::Kind::Custom( - sprout_core::kind::KIND_AGENT_OBSERVER_FRAME as u16, + buzz_core::kind::KIND_AGENT_OBSERVER_FRAME as u16, )); assert!(!p_gated_filters_authorized(&[missing_p], authed)); let wrong_p = Filter::new() .kind(nostr::Kind::Custom( - sprout_core::kind::KIND_AGENT_OBSERVER_FRAME as u16, + buzz_core::kind::KIND_AGENT_OBSERVER_FRAME as u16, )) .custom_tags(p_tag, [other]); assert!(!p_gated_filters_authorized(&[wrong_p], authed)); let matching_p = Filter::new() .kind(nostr::Kind::Custom( - sprout_core::kind::KIND_AGENT_OBSERVER_FRAME as u16, + buzz_core::kind::KIND_AGENT_OBSERVER_FRAME as u16, )) .custom_tags(p_tag, [authed]); assert!(p_gated_filters_authorized(&[matching_p], authed)); @@ -1158,7 +1158,7 @@ mod tests { let (agent, _, _) = three_pubkeys(); let f = Filter::new() .kind(nostr::Kind::Custom( - sprout_core::kind::KIND_AGENT_OBSERVER_FRAME as u16, + buzz_core::kind::KIND_AGENT_OBSERVER_FRAME as u16, )) .search("x"); assert!(!p_gated_filters_authorized(&[f], &agent)); diff --git a/crates/sprout-relay/src/handlers/side_effects.rs b/crates/buzz-relay/src/handlers/side_effects.rs similarity index 98% rename from crates/sprout-relay/src/handlers/side_effects.rs rename to crates/buzz-relay/src/handlers/side_effects.rs index 362a2c7f3..8c72142ed 100644 --- a/crates/sprout-relay/src/handlers/side_effects.rs +++ b/crates/buzz-relay/src/handlers/side_effects.rs @@ -6,13 +6,13 @@ use nostr::{Event, EventBuilder, Kind, Tag}; use tracing::{info, warn}; use uuid::Uuid; -use sprout_core::kind::{ +use buzz_core::kind::{ event_kind_u32, is_parameterized_replaceable, KIND_AGENT_PROFILE, KIND_DM_VISIBILITY, KIND_GIT_REPO_ANNOUNCEMENT, KIND_IA_ARCHIVED, KIND_IA_ARCHIVED_LIST, KIND_IA_UNARCHIVED, KIND_MEMBER_ADDED_NOTIFICATION, KIND_MEMBER_REMOVED_NOTIFICATION, KIND_NIP29_GROUP_ADMINS, KIND_NIP29_GROUP_MEMBERS, KIND_NIP29_GROUP_METADATA, KIND_NIP43_MEMBERSHIP_LIST, KIND_REACTION, }; -use sprout_db::channel::MemberRole; +use buzz_db::channel::MemberRole; use super::event::dispatch_persistent_event; use crate::protocol::RelayMessage; @@ -131,7 +131,7 @@ pub async fn handle_side_effects( /// Validate a standard NIP-09 deletion event before it is stored. /// -/// Sprout accepts standard deletions for self-authored events only. Channel +/// Buzz accepts standard deletions for self-authored events only. Channel /// admin deletions continue to use kind 9005. pub async fn validate_standard_deletion_event( event: &Event, @@ -214,7 +214,7 @@ pub async fn validate_admin_event( 9000 => { // Validate role tag if present let role_str = extract_tag_value(event, "role").unwrap_or_else(|| "member".to_string()); - if role_str.parse::().is_err() { + if role_str.parse::().is_err() { return Err(anyhow::anyhow!("invalid role: {role_str}")); } @@ -229,13 +229,13 @@ pub async fn validate_admin_event( } // Only owners/admins may grant elevated roles. - let role: sprout_db::channel::MemberRole = role_str.parse().unwrap(); + let role: buzz_db::channel::MemberRole = role_str.parse().unwrap(); if role.is_elevated() { - let actor_role: sprout_db::channel::MemberRole = actor_member + let actor_role: buzz_db::channel::MemberRole = actor_member .unwrap() .role .parse() - .unwrap_or(sprout_db::channel::MemberRole::Member); + .unwrap_or(buzz_db::channel::MemberRole::Member); if !actor_role.is_elevated() { return Err(anyhow::anyhow!( "only owners/admins may grant elevated roles" @@ -634,7 +634,7 @@ async fn emit_addressable_discovery_event( let min_ts = { let existing = state .db - .query_events(&sprout_db::event::EventQuery { + .query_events(&buzz_db::event::EventQuery { kinds: Some(vec![kind as i32]), channel_id: Some(channel_id), limit: Some(1), @@ -711,7 +711,7 @@ pub async fn emit_group_discovery_events( tags.push(Tag::parse(["p", &pubkey_hex])?); } } - // Sprout channels always require explicit membership + // Buzz channels always require explicit membership tags.push(Tag::parse(["closed"])?); // Channel type tag so clients can distinguish stream/forum/dm without inference tags.push(Tag::parse(["t", &channel.channel_type])?); @@ -1018,7 +1018,7 @@ async fn handle_edit_metadata(event: &Event, state: &Arc) -> anyhow::R .db .update_channel( channel_id, - sprout_db::channel::ChannelUpdate { + buzz_db::channel::ChannelUpdate { name: Some(val.to_string()), ..Default::default() }, @@ -1030,7 +1030,7 @@ async fn handle_edit_metadata(event: &Event, state: &Arc) -> anyhow::R .db .update_channel( channel_id, - sprout_db::channel::ChannelUpdate { + buzz_db::channel::ChannelUpdate { description: Some(val.to_string()), ..Default::default() }, @@ -1070,7 +1070,7 @@ async fn handle_edit_metadata(event: &Event, state: &Arc) -> anyhow::R .db .update_channel( channel_id, - sprout_db::channel::ChannelUpdate { + buzz_db::channel::ChannelUpdate { visibility: Some(val.to_string()), ..Default::default() }, @@ -1112,7 +1112,7 @@ async fn handle_edit_metadata(event: &Event, state: &Arc) -> anyhow::R .db .update_channel( channel_id, - sprout_db::channel::ChannelUpdate { + buzz_db::channel::ChannelUpdate { ttl_seconds: Some(ttl_change), ..Default::default() }, @@ -1262,10 +1262,10 @@ async fn handle_create_group(event: &Event, state: &Arc) -> anyhow::Re let channel_type_str = extract_tag_value(event, "channel_type").unwrap_or_else(|| "stream".to_string()); - let visibility: sprout_db::channel::ChannelVisibility = visibility_str + let visibility: buzz_db::channel::ChannelVisibility = visibility_str .parse() .map_err(|_| anyhow::anyhow!("invalid visibility: {visibility_str}"))?; - let channel_type: sprout_db::channel::ChannelType = channel_type_str + let channel_type: buzz_db::channel::ChannelType = channel_type_str .parse() .map_err(|_| anyhow::anyhow!("invalid channel_type: {channel_type_str}"))?; @@ -1313,7 +1313,7 @@ async fn handle_create_group(event: &Event, state: &Arc) -> anyhow::Re state.invalidate_membership(channel.id, &actor_bytes); // Open channels appear in everyone's accessible set; private channels only // affect the creator (the sole initial member). - if visibility == sprout_db::channel::ChannelVisibility::Open { + if visibility == buzz_db::channel::ChannelVisibility::Open { state.invalidate_all_accessible_channels(); } @@ -1420,7 +1420,7 @@ async fn handle_join_request(event: &Event, state: &Arc) -> anyhow::Re .add_member( channel_id, &actor_bytes, - sprout_db::channel::MemberRole::Member, + buzz_db::channel::MemberRole::Member, None, ) .await?; @@ -1447,7 +1447,7 @@ async fn handle_join_request(event: &Event, state: &Arc) -> anyhow::Re channel_id, &actor_bytes, &actor_bytes, - sprout_core::kind::KIND_MEMBER_ADDED_NOTIFICATION, + buzz_core::kind::KIND_MEMBER_ADDED_NOTIFICATION, ) .await { @@ -1537,7 +1537,7 @@ async fn handle_a_tag_deletion(event: &Event, state: &Arc) -> anyhow:: let d_tag = parts[2]; match kind_num { - sprout_core::kind::KIND_WORKFLOW_DEF => { + buzz_core::kind::KIND_WORKFLOW_DEF => { // Try UUID first (workflow_id); fall back to name-based lookup. if let Ok(wf_id) = uuid::Uuid::parse_str(d_tag) { state @@ -2078,7 +2078,7 @@ async fn seed_manifest_pointer( /// /// The seeded empty manifest is the source of truth; this event is the /// derived notification. Fires once per announce, signed by the relay, -/// carrying the announcer's pubkey in the `p` tag (sprout extension). +/// carrying the announcer's pubkey in the `p` tag (buzz extension). async fn emit_initial_ref_state( state: &Arc, owner_hex: &str, @@ -2239,7 +2239,7 @@ pub async fn publish_nip43_member_removed( /// /// Idempotent: checks for existing kind:39000 events before emitting. pub async fn reconcile_channel_events(state: &Arc) -> anyhow::Result<()> { - use sprout_db::event::EventQuery; + use buzz_db::event::EventQuery; let channels = state.db.list_channels(None).await?; if channels.is_empty() { @@ -2370,7 +2370,7 @@ pub async fn publish_dm_visibility_snapshot( let ts = { let existing = state .db - .query_events(&sprout_db::event::EventQuery { + .query_events(&buzz_db::event::EventQuery { kinds: Some(vec![KIND_DM_VISIBILITY as i32]), pubkey: Some(state.relay_keypair.public_key().to_bytes().to_vec()), d_tag: Some(viewer_hex.clone()), diff --git a/crates/sprout-relay/src/lib.rs b/crates/buzz-relay/src/lib.rs similarity index 86% rename from crates/sprout-relay/src/lib.rs rename to crates/buzz-relay/src/lib.rs index 42c51c118..57becfc5d 100644 --- a/crates/sprout-relay/src/lib.rs +++ b/crates/buzz-relay/src/lib.rs @@ -1,6 +1,6 @@ #![deny(unsafe_code)] #![warn(missing_docs)] -//! NIP-01 WebSocket relay for Sprout private team communication. +//! NIP-01 WebSocket relay for Buzz private team communication. /// REST API route handlers. pub mod api; @@ -30,7 +30,7 @@ pub mod state; pub mod subscription; /// Webhook secret generation and constant-time comparison. pub mod webhook_secret; -/// Workflow action sink — relay-side implementation of [`sprout_workflow::ActionSink`]. +/// Workflow action sink — relay-side implementation of [`buzz_workflow::ActionSink`]. pub mod workflow_sink; pub use config::Config; diff --git a/crates/sprout-relay/src/main.rs b/crates/buzz-relay/src/main.rs similarity index 87% rename from crates/sprout-relay/src/main.rs rename to crates/buzz-relay/src/main.rs index 7fe4588c4..db60e3201 100644 --- a/crates/sprout-relay/src/main.rs +++ b/crates/buzz-relay/src/main.rs @@ -4,27 +4,27 @@ use std::sync::Arc; use tracing::{error, info}; use tracing_subscriber::{fmt, prelude::*, EnvFilter}; -use sprout_audit::AuditService; -use sprout_auth::AuthService; -use sprout_db::{Db, DbConfig}; -use sprout_pubsub::PubSubManager; -use sprout_search::{SearchConfig, SearchService}; - -use sprout_relay::config::Config; -use sprout_relay::metrics as relay_metrics; -use sprout_relay::router::{build_health_router, build_router}; -use sprout_relay::state::AppState; -use sprout_workflow::WorkflowEngine; +use buzz_audit::AuditService; +use buzz_auth::AuthService; +use buzz_db::{Db, DbConfig}; +use buzz_pubsub::PubSubManager; +use buzz_search::{SearchConfig, SearchService}; + +use buzz_relay::config::Config; +use buzz_relay::metrics as relay_metrics; +use buzz_relay::router::{build_health_router, build_router}; +use buzz_relay::state::AppState; +use buzz_workflow::WorkflowEngine; #[tokio::main] async fn main() -> anyhow::Result<()> { // JSON-only structured logs — simple, machine-parseable, CAKE-compatible. tracing_subscriber::registry() .with(fmt::layer().json().flatten_event(true)) - .with(EnvFilter::from_default_env().add_directive("sprout_relay=info".parse()?)) + .with(EnvFilter::from_default_env().add_directive("buzz_relay=info".parse()?)) .init(); - info!("Starting sprout-relay"); + info!("Starting buzz-relay"); let config = Config::from_env().map_err(|e| { error!("Invalid configuration: {e}"); @@ -65,11 +65,11 @@ async fn main() -> anyhow::Result<()> { // that no one can administer. if config.require_relay_membership && config.relay_owner_pubkey.is_none() { error!( - "SPROUT_REQUIRE_RELAY_MEMBERSHIP=true but RELAY_OWNER_PUBKEY is not set or invalid. \ + "BUZZ_REQUIRE_RELAY_MEMBERSHIP=true but RELAY_OWNER_PUBKEY is not set or invalid. \ Set RELAY_OWNER_PUBKEY to a valid 64-char hex pubkey." ); return Err(anyhow::anyhow!( - "RELAY_OWNER_PUBKEY required when SPROUT_REQUIRE_RELAY_MEMBERSHIP=true" + "RELAY_OWNER_PUBKEY required when BUZZ_REQUIRE_RELAY_MEMBERSHIP=true" )); } @@ -78,7 +78,7 @@ async fn main() -> anyhow::Result<()> { // or bootstrapping if we'll reject the config anyway. if config.require_relay_membership && config.relay_private_key.is_none() { return Err(anyhow::anyhow!( - "SPROUT_RELAY_PRIVATE_KEY is required when SPROUT_REQUIRE_RELAY_MEMBERSHIP=true. \ + "BUZZ_RELAY_PRIVATE_KEY is required when BUZZ_REQUIRE_RELAY_MEMBERSHIP=true. \ NIP-43 events signed with an ephemeral key become unverifiable after restart." )); } @@ -96,7 +96,7 @@ async fn main() -> anyhow::Result<()> { "Fatal: failed to backfill allowlist with membership enforcement enabled: {e}" ); return Err(anyhow::anyhow!( - "Failed to backfill pubkey_allowlist (required when SPROUT_REQUIRE_RELAY_MEMBERSHIP=true): {e}" + "Failed to backfill pubkey_allowlist (required when BUZZ_REQUIRE_RELAY_MEMBERSHIP=true): {e}" )); } else { error!("Failed to backfill pubkey_allowlist (non-fatal): {e}"); @@ -115,7 +115,7 @@ async fn main() -> anyhow::Result<()> { // in a broken state. error!("Fatal: failed to bootstrap relay owner with membership enforcement enabled: {e}"); return Err(anyhow::anyhow!( - "Failed to bootstrap relay owner (required when SPROUT_REQUIRE_RELAY_MEMBERSHIP=true): {e}" + "Failed to bootstrap relay owner (required when BUZZ_REQUIRE_RELAY_MEMBERSHIP=true): {e}" )); } else { error!( @@ -177,12 +177,12 @@ async fn main() -> anyhow::Result<()> { error!("Typesense collection setup failed (non-fatal): {e}"); } - let workflow_config = sprout_workflow::WorkflowConfig::default(); + let workflow_config = buzz_workflow::WorkflowConfig::default(); let workflow_engine = Arc::new(WorkflowEngine::new(db.clone(), workflow_config)); let relay_keypair = if let Some(hex) = &config.relay_private_key { nostr::Keys::parse(hex) - .map_err(|e| anyhow::anyhow!("invalid SPROUT_RELAY_PRIVATE_KEY: {e}"))? + .map_err(|e| anyhow::anyhow!("invalid BUZZ_RELAY_PRIVATE_KEY: {e}"))? } else if !config.require_auth_token { // Dev mode: use a deterministic keypair so addressable events (kind:39000/39001/39002) // replace correctly across restarts. Without this, each restart generates a new pubkey @@ -192,13 +192,13 @@ async fn main() -> anyhow::Result<()> { let keys = nostr::Keys::parse(DEV_RELAY_PRIVKEY).expect("hardcoded dev key is valid"); tracing::warn!( pubkey = %keys.public_key().to_hex(), - "Using hardcoded dev relay keypair (SPROUT_REQUIRE_AUTH_TOKEN=false). \ - Set SPROUT_RELAY_PRIVATE_KEY for production." + "Using hardcoded dev relay keypair (BUZZ_REQUIRE_AUTH_TOKEN=false). \ + Set BUZZ_RELAY_PRIVATE_KEY for production." ); keys } else { panic!( - "SPROUT_RELAY_PRIVATE_KEY must be set when SPROUT_REQUIRE_AUTH_TOKEN=true. \ + "BUZZ_RELAY_PRIVATE_KEY must be set when BUZZ_REQUIRE_AUTH_TOKEN=true. \ A stable relay identity is required for production." ); }; @@ -207,7 +207,7 @@ async fn main() -> anyhow::Result<()> { .media .validate() .map_err(|e| anyhow::anyhow!("invalid media config: {e}"))?; - let media_storage = sprout_media::MediaStorage::new(&config.media) + let media_storage = buzz_media::MediaStorage::new(&config.media) .map_err(|e| anyhow::anyhow!("failed to initialize media storage: {e}"))?; info!("Media storage connected"); @@ -229,19 +229,19 @@ async fn main() -> anyhow::Result<()> { // linearizable conditional-write axiom (A3) before serving git traffic. // Failure is fatal: a backend that cannot satisfy pointer CAS invalidates // the manifest-pointer protocol. This is a deployment gate, not a proof. - if std::env::var("SPROUT_GIT_CONFORMANCE_PROBE") + if std::env::var("BUZZ_GIT_CONFORMANCE_PROBE") .map(|v| v != "false") .unwrap_or(true) { - let race_width = std::env::var("SPROUT_GIT_PROBE_WRITERS") + let race_width = std::env::var("BUZZ_GIT_PROBE_WRITERS") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(32); - let race_rounds = std::env::var("SPROUT_GIT_PROBE_ROUNDS") + let race_rounds = std::env::var("BUZZ_GIT_PROBE_ROUNDS") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(3); - let cfg = sprout_relay::api::git::store::ProbeConfig { + let cfg = buzz_relay::api::git::store::ProbeConfig { race_width, race_rounds, }; @@ -269,7 +269,7 @@ async fn main() -> anyhow::Result<()> { let startup_state = Arc::clone(&state); tokio::spawn(async move { if let Err(e) = - sprout_relay::handlers::side_effects::publish_nip43_membership_list(&startup_state) + buzz_relay::handlers::side_effects::publish_nip43_membership_list(&startup_state) .await { tracing::warn!(error = %e, "failed to publish initial NIP-43 membership list on startup"); @@ -281,9 +281,9 @@ async fn main() -> anyhow::Result<()> { // Emit kind:39000/39002 discovery events for channels that exist in the DB // but don't have corresponding events (e.g. seeded via direct SQL inserts). - // Only runs when SPROUT_RECONCILE_CHANNELS=true (dev/CI environments). + // Only runs when BUZZ_RECONCILE_CHANNELS=true (dev/CI environments). // Production relays create channels through the event pipeline and don't need this. - if std::env::var("SPROUT_RECONCILE_CHANNELS").is_ok() { + if std::env::var("BUZZ_RECONCILE_CHANNELS").is_ok() { let reconcile_state = Arc::clone(&state); tokio::spawn(async move { // Try immediately, then retry every 5s for up to 2 minutes. @@ -292,10 +292,8 @@ async fn main() -> anyhow::Result<()> { if attempt > 0 { tokio::time::sleep(std::time::Duration::from_secs(5)).await; } - match sprout_relay::handlers::side_effects::reconcile_channel_events( - &reconcile_state, - ) - .await + match buzz_relay::handlers::side_effects::reconcile_channel_events(&reconcile_state) + .await { Ok(()) => {} Err(e) => { @@ -308,7 +306,7 @@ async fn main() -> anyhow::Result<()> { // Wire the action sink — must happen after AppState (which creates // sub_registry, conn_manager) and before the cron loop starts. - let action_sink = Arc::new(sprout_relay::workflow_sink::RelayActionSink::new(&state)); + let action_sink = Arc::new(buzz_relay::workflow_sink::RelayActionSink::new(&state)); workflow_engine.set_action_sink(action_sink); // Start the cron loop AFTER the action sink is wired. @@ -323,7 +321,7 @@ async fn main() -> anyhow::Result<()> { // together with the workflow engine in a future multi-pod coordination pass. { let reaper_state = Arc::clone(&state); - let reaper_interval_secs: u64 = std::env::var("SPROUT_REAPER_INTERVAL_SECS") + let reaper_interval_secs: u64 = std::env::var("BUZZ_REAPER_INTERVAL_SECS") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(60); @@ -351,7 +349,7 @@ async fn main() -> anyhow::Result<()> { for channel_id in &expired { // Emit a system message so members see why the channel was archived. - if let Err(e) = sprout_relay::handlers::side_effects::emit_system_message( + if let Err(e) = buzz_relay::handlers::side_effects::emit_system_message( &reaper_state, *channel_id, serde_json::json!({ "type": "channel_auto_archived" }), @@ -362,12 +360,11 @@ async fn main() -> anyhow::Result<()> { } // Update NIP-29 discovery events so clients see the archived state. - if let Err(e) = - sprout_relay::handlers::side_effects::emit_group_discovery_events( - &reaper_state, - *channel_id, - ) - .await + if let Err(e) = buzz_relay::handlers::side_effects::emit_group_discovery_events( + &reaper_state, + *channel_id, + ) + .await { error!(channel = %channel_id, "reaper discovery update failed: {e}"); } @@ -396,7 +393,7 @@ async fn main() -> anyhow::Result<()> { } else { Some(channel_event.channel_id) }; - let stored = sprout_core::StoredEvent::new(channel_event.event, channel_id); + let stored = buzz_core::StoredEvent::new(channel_event.event, channel_id); // Skip events that were already fanned out in-process (local echo). // The cache has TTL-based eviction (60s) so entries are bounded @@ -408,13 +405,13 @@ async fn main() -> anyhow::Result<()> { } let matches = state_for_sub.sub_registry.fan_out(&stored); - let matches = sprout_relay::handlers::event::filter_fanout_by_access( + let matches = buzz_relay::handlers::event::filter_fanout_by_access( &state_for_sub, &stored, matches, ) .await; - metrics::counter!("sprout_multinode_fanout_total").increment(1); + metrics::counter!("buzz_multinode_fanout_total").increment(1); if matches.is_empty() { continue; } @@ -444,7 +441,7 @@ async fn main() -> anyhow::Result<()> { } } Err(tokio::sync::broadcast::error::RecvError::Lagged(n)) => { - metrics::counter!("sprout_multinode_fanout_lag_total").increment(n); + metrics::counter!("buzz_multinode_fanout_lag_total").increment(n); tracing::warn!("Multi-node fan-out lagged by {n} messages"); } Err(tokio::sync::broadcast::error::RecvError::Closed) => { @@ -476,8 +473,8 @@ async fn main() -> anyhow::Result<()> { /// /// ```text /// ┌─────────────────────────────────────────────────────────┐ -/// │ Listener 1: TCP SPROUT_BIND_ADDR:3000 (app router) │ -/// │ Listener 2: UDS SPROUT_UDS_PATH (app, optional)│ +/// │ Listener 1: TCP BUZZ_BIND_ADDR:3000 (app router) │ +/// │ Listener 2: UDS BUZZ_UDS_PATH (app, optional)│ /// │ Listener 3: TCP 0.0.0.0:8080 (health only) │ /// │ Listener 4: TCP 0.0.0.0:9102 (metrics, via │ /// │ PrometheusBuilder — already bound) │ @@ -524,7 +521,7 @@ async fn serve( let tcp_listener = tokio::net::TcpListener::bind(&config.bind_addr) .await .map_err(|e| anyhow::anyhow!("Failed to bind {}: {e}", config.bind_addr))?; - info!(addr = %config.bind_addr, "sprout-relay TCP listening"); + info!(addr = %config.bind_addr, "buzz-relay TCP listening"); // ── App listener (UDS, optional) ───────────────────────────────────────── #[cfg(unix)] @@ -536,14 +533,14 @@ async fn serve( } Ok(_) => { return Err(anyhow::anyhow!( - "SPROUT_UDS_PATH {uds_path} exists but is not a socket" + "BUZZ_UDS_PATH {uds_path} exists but is not a socket" )); } Err(_) => {} } let uds_listener = tokio::net::UnixListener::bind(uds_path) .map_err(|e| anyhow::anyhow!("Failed to bind UDS {uds_path}: {e}"))?; - info!(path = %uds_path, "sprout-relay UDS listening"); + info!(path = %uds_path, "buzz-relay UDS listening"); let router_uds = router.clone(); let mut uds_rx = shutdown_tx.subscribe(); @@ -573,7 +570,7 @@ async fn serve( #[cfg(not(unix))] if config.uds_path.is_some() { - tracing::warn!("SPROUT_UDS_PATH set but UDS not supported on this platform"); + tracing::warn!("BUZZ_UDS_PATH set but UDS not supported on this platform"); } // TCP-only path. diff --git a/crates/sprout-relay/src/mesh_status_publisher.rs b/crates/buzz-relay/src/mesh_status_publisher.rs similarity index 95% rename from crates/sprout-relay/src/mesh_status_publisher.rs rename to crates/buzz-relay/src/mesh_status_publisher.rs index 1c967cb13..bc2ddd09b 100644 --- a/crates/sprout-relay/src/mesh_status_publisher.rs +++ b/crates/buzz-relay/src/mesh_status_publisher.rs @@ -1,17 +1,17 @@ //! Relay-signed mesh-LLM status publication. //! -//! The relay owns the Nostr publication surface for Sprout mesh status. mesh-llm +//! The relay owns the Nostr publication surface for Buzz mesh status. mesh-llm //! remains the source of live runtime truth, but the relay sanitizes that status //! and republishes a member-readable, relay-signed parameterized event. This -//! keeps discovery inside Sprout's relay-membership boundary and avoids mesh's +//! keeps discovery inside Buzz's relay-membership boundary and avoids mesh's //! public-relay publisher path. use std::sync::Arc; +use buzz_core::kind::KIND_MESH_LLM_RELAY_STATUS; use nostr::{EventBuilder, Kind, Tag}; use serde::{Deserialize, Serialize}; use serde_json::Value; -use sprout_core::kind::KIND_MESH_LLM_RELAY_STATUS; use tracing::info; use crate::handlers::event::dispatch_persistent_event; @@ -23,7 +23,7 @@ use crate::state::AppState; /// d_tag)`, and the author is always the relay key here). This avoids two /// members' reports clobbering each other and sidesteps any read-modify-write /// race on a single shared note. Discovery reads all notes with the `k` tag. -pub const MESH_STATUS_D_TAG_PREFIX: &str = "sprout-relay-mesh"; +pub const MESH_STATUS_D_TAG_PREFIX: &str = "buzz-relay-mesh"; /// Build the per-reporter d-tag for a mesh status note. pub fn mesh_status_d_tag(reporter_pubkey_hex: &str) -> String { @@ -31,7 +31,7 @@ pub fn mesh_status_d_tag(reporter_pubkey_hex: &str) -> String { } /// Content schema discriminator. -pub const MESH_STATUS_TYPE: &str = "sprout-mesh-status"; +pub const MESH_STATUS_TYPE: &str = "buzz-mesh-status"; /// Sanitized relay-published mesh status. /// @@ -41,10 +41,10 @@ pub const MESH_STATUS_TYPE: &str = "sprout-mesh-status"; /// serving node that has not admitted the caller's pubkey. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SproutMeshStatus { +pub struct BuzzMeshStatus { /// Schema version. pub v: u32, - /// Constant discriminator: `sprout-mesh-status`. + /// Constant discriminator: `buzz-mesh-status`. #[serde(rename = "type")] pub status_type: String, /// Unix timestamp when the relay generated this projection. @@ -103,7 +103,7 @@ pub struct MeshTargetCapacity { } /// Build the relay-owned status projection from mesh's `/api/status` JSON. -pub fn sanitize_mesh_status(payload: &Value, now_unix: u64) -> SproutMeshStatus { +pub fn sanitize_mesh_status(payload: &Value, now_unix: u64) -> BuzzMeshStatus { let endpoint_addr = string_field(payload, "token").unwrap_or_default(); let node_id = string_field(payload, "node_id"); let mesh_id = string_field(payload, "mesh_id"); @@ -176,7 +176,7 @@ pub fn sanitize_mesh_status(payload: &Value, now_unix: u64) -> SproutMeshStatus .then_with(|| a.endpoint_addr.cmp(&b.endpoint_addr)) }); - SproutMeshStatus { + BuzzMeshStatus { v: 1, status_type: MESH_STATUS_TYPE.to_string(), updated_at: now_unix, @@ -204,7 +204,7 @@ pub async fn publish_mesh_status_from_payload( pub async fn publish_mesh_status( state: &Arc, reporter_pubkey_hex: &str, - status: &SproutMeshStatus, + status: &BuzzMeshStatus, ) -> anyhow::Result<()> { let content = serde_json::to_string(status)?; let d_tag = mesh_status_d_tag(reporter_pubkey_hex); @@ -313,7 +313,7 @@ mod tests { "token": "endpoint-token-a", "node_id": "node-a", "mesh_id": "mesh-1", - "mesh_name": "sprout", + "mesh_name": "buzz", "hosted_models": ["Qwen3-8B-Q4_K_M"], "serving_models": ["Qwen3-8B-Q4_K_M"], "my_vram_gb": 12.0, diff --git a/crates/sprout-relay/src/metrics.rs b/crates/buzz-relay/src/metrics.rs similarity index 98% rename from crates/sprout-relay/src/metrics.rs rename to crates/buzz-relay/src/metrics.rs index 940397bd3..954e78434 100644 --- a/crates/sprout-relay/src/metrics.rs +++ b/crates/buzz-relay/src/metrics.rs @@ -11,7 +11,7 @@ //! ``` //! //! Framework metrics (`http_requests_total`, `http_request_latency_ms`) are -//! recorded by [`track_metrics`] middleware on the app router. Sprout-specific +//! recorded by [`track_metrics`] middleware on the app router. Buzz-specific //! metrics are recorded inline at their call sites. use std::time::Instant; @@ -53,7 +53,7 @@ pub fn install(port: u16) { .set_buckets_for_metric(Matcher::Suffix("_seconds".to_owned()), &DURATION_BUCKETS_S) .expect("valid seconds bucket boundaries") .set_buckets_for_metric( - Matcher::Full("sprout_fanout_recipients".to_owned()), + Matcher::Full("buzz_fanout_recipients".to_owned()), &FANOUT_BUCKETS, ) .expect("valid fanout bucket boundaries") diff --git a/crates/sprout-relay/src/nip11.rs b/crates/buzz-relay/src/nip11.rs similarity index 96% rename from crates/sprout-relay/src/nip11.rs rename to crates/buzz-relay/src/nip11.rs index 67e75fedb..7c0fdb4e6 100644 --- a/crates/sprout-relay/src/nip11.rs +++ b/crates/buzz-relay/src/nip11.rs @@ -14,7 +14,7 @@ use crate::connection::MAX_FRAME_BYTES; pub(crate) const SUPPORTED_NIPS: &[u32] = &[1, 2, 10, 11, 16, 17, 23, 25, 29, 33, 38, 42, 50]; /// NIP-43 (relay membership). Advertised only when the relay actually -/// enforces membership (`SPROUT_REQUIRE_RELAY_MEMBERSHIP=true`) AND has a +/// enforces membership (`BUZZ_REQUIRE_RELAY_MEMBERSHIP=true`) AND has a /// stable signing key — both are required for kind 13534/8000/8001 events /// to be verifiable by clients. pub(crate) const NIP_RELAY_MEMBERSHIP: u32 = 43; @@ -93,7 +93,7 @@ impl RelayInfo { /// `relay_self` is the relay's own signing pubkey (hex), advertised as the /// NIP-11 `self` field. NIP-11 defines `self` generically as the relay's /// identity key; other NIPs reference it. Notably NIP-29 (group metadata - /// kinds 39000/39001/39002, which Sprout signs with `state.relay_keypair` + /// kinds 39000/39001/39002, which Buzz signs with `state.relay_keypair` /// unconditionally) requires clients to verify those events against /// `self`. Pass `Some` whenever the relay has a stable signing key. /// @@ -114,8 +114,8 @@ impl RelayInfo { } Self { - name: "Sprout Relay".to_string(), - description: "Sprout — private team communication relay".to_string(), + name: "Buzz Relay".to_string(), + description: "Buzz — private team communication relay".to_string(), pubkey: None, contact: None, supported_nips, @@ -183,7 +183,7 @@ mod tests { #[test] fn auth_required_is_advertised_true() { // REQ, EVENT, and COUNT all unconditionally require - // `AuthState::Authenticated` (see `crates/sprout-relay/src/handlers/`), + // `AuthState::Authenticated` (see `crates/buzz-relay/src/handlers/`), // so the NIP-11 doc must advertise it. assert!(relay_limitation().auth_required); } diff --git a/crates/sprout-relay/src/protocol.rs b/crates/buzz-relay/src/protocol.rs similarity index 99% rename from crates/sprout-relay/src/protocol.rs rename to crates/buzz-relay/src/protocol.rs index e997976e7..e96b2f7c5 100644 --- a/crates/sprout-relay/src/protocol.rs +++ b/crates/buzz-relay/src/protocol.rs @@ -219,8 +219,8 @@ impl RelayMessage { #[cfg(test)] mod tests { use super::*; + use buzz_core::test_helpers::make_event; use nostr::{EventBuilder, Keys, Kind}; - use sprout_core::test_helpers::make_event; fn make_auth_event(keys: &Keys, challenge: &str, relay: &str) -> Event { let url: nostr::RelayUrl = relay.parse().expect("url"); diff --git a/crates/sprout-relay/src/router.rs b/crates/buzz-relay/src/router.rs similarity index 98% rename from crates/sprout-relay/src/router.rs rename to crates/buzz-relay/src/router.rs index f72516bf3..226592a07 100644 --- a/crates/sprout-relay/src/router.rs +++ b/crates/buzz-relay/src/router.rs @@ -82,7 +82,7 @@ pub fn build_router(state: Arc) -> Router { .merge(git_router) .merge(git_policy_router); - // When SPROUT_WEB_DIR is set, serve the SPA as a fallback for unmatched routes. + // When BUZZ_WEB_DIR is set, serve the SPA as a fallback for unmatched routes. if let Some(ref web_dir) = state.config.web_dir { let index_path = web_dir.join("index.html"); let spa_fallback = ServeDir::new(web_dir).not_found_service(tower::service_fn( @@ -227,7 +227,7 @@ async fn readiness_handler(State(state): State>) -> impl IntoRespo async fn status_handler(State(state): State>) -> impl IntoResponse { let uptime_secs = state.started_at.elapsed().as_secs(); Json(json!({ - "service": "sprout-relay", + "service": "buzz-relay", "version": env!("CARGO_PKG_VERSION"), "uptime_seconds": uptime_secs, })) @@ -246,7 +246,7 @@ fn build_cors_layer(cors_origins: &[String]) -> CorsLayer { if origins.is_empty() { tracing::error!( - "SPROUT_CORS_ORIGINS set but no valid origins could be parsed — \ + "BUZZ_CORS_ORIGINS set but no valid origins could be parsed — \ refusing to fall back to permissive CORS. Fix the origins or unset \ the variable for development mode." ); diff --git a/crates/sprout-relay/src/state.rs b/crates/buzz-relay/src/state.rs similarity index 95% rename from crates/sprout-relay/src/state.rs rename to crates/buzz-relay/src/state.rs index 33d7796b7..3617975a0 100644 --- a/crates/sprout-relay/src/state.rs +++ b/crates/buzz-relay/src/state.rs @@ -12,15 +12,15 @@ use tokio::task::JoinHandle; use tokio_util::sync::CancellationToken; use uuid::Uuid; +use buzz_audit::AuditService; +use buzz_auth::AuthService; +use buzz_core::event::StoredEvent; +use buzz_db::Db; +use buzz_media::MediaStorage; +use buzz_pubsub::PubSubManager; +use buzz_search::SearchService; +use buzz_workflow::WorkflowEngine; use deadpool_redis; -use sprout_audit::AuditService; -use sprout_auth::AuthService; -use sprout_core::event::StoredEvent; -use sprout_db::Db; -use sprout_media::MediaStorage; -use sprout_pubsub::PubSubManager; -use sprout_search::SearchService; -use sprout_workflow::WorkflowEngine; use crate::audio::AudioRoomManager; use crate::config::Config; @@ -145,7 +145,7 @@ impl ConnectionManager { let count = conn.backpressure_count.fetch_add(1, Ordering::Relaxed) + 1; if count >= SLOW_CLIENT_GRACE_LIMIT { tracing::warn!(conn_id = %conn_id, count, "fan-out: sustained backpressure — cancelling slow client"); - metrics::counter!("sprout_ws_backpressure_disconnects_total").increment(1); + metrics::counter!("buzz_ws_backpressure_disconnects_total").increment(1); conn.cancel.cancel(); } else { tracing::warn!(conn_id = %conn_id, count, grace = SLOW_CLIENT_GRACE_LIMIT, "fan-out: send buffer full — grace {count}/{SLOW_CLIENT_GRACE_LIMIT}"); @@ -227,7 +227,7 @@ pub struct AppState { pub search_index_tx: mpsc::Sender, /// Bounded channel for audit logging — backpressure instead of unbounded spawns. /// Uses .send().await (blocks caller if full) because audit entries must not be lost. - pub audit_tx: mpsc::Sender, + pub audit_tx: mpsc::Sender, /// Media storage client (S3/MinIO). pub media_storage: Arc, /// Git object-store backend (content-addressed packs/manifests plus @@ -292,11 +292,11 @@ impl AppState { let t = std::time::Instant::now(); match search_for_worker.index_event(&stored_event).await { Ok(()) => { - metrics::histogram!("sprout_search_index_seconds") + metrics::histogram!("buzz_search_index_seconds") .record(t.elapsed().as_secs_f64()); } Err(e) => { - metrics::counter!("sprout_search_index_errors_total").increment(1); + metrics::counter!("buzz_search_index_errors_total").increment(1); tracing::error!( event_id = %stored_event.event.id.to_hex(), "Search index failed: {e}" @@ -308,7 +308,7 @@ impl AppState { }); let audit_arc = Arc::new(audit); - let (audit_tx, mut audit_rx) = mpsc::channel::(1000); + let (audit_tx, mut audit_rx) = mpsc::channel::(1000); let audit_for_worker = Arc::clone(&audit_arc); let audit_cancel = CancellationToken::new(); let audit_cancel_worker = audit_cancel.clone(); @@ -432,13 +432,13 @@ impl AppState { &self, channel_id: Uuid, pubkey: &[u8], - ) -> Result { + ) -> Result { let key = (channel_id, pubkey.to_vec()); if let Some(cached) = self.membership_cache.get(&key) { - metrics::counter!("sprout_membership_cache_hits_total").increment(1); + metrics::counter!("buzz_membership_cache_hits_total").increment(1); return Ok(cached); } - metrics::counter!("sprout_membership_cache_misses_total").increment(1); + metrics::counter!("buzz_membership_cache_misses_total").increment(1); let result = self.db.is_member(channel_id, pubkey).await?; self.membership_cache.insert(key, result); Ok(result) @@ -477,13 +477,13 @@ impl AppState { pub async fn get_accessible_channel_ids_cached( &self, pubkey: &[u8], - ) -> Result, sprout_db::DbError> { + ) -> Result, buzz_db::DbError> { let key = pubkey.to_vec(); if let Some(cached) = self.accessible_channels_cache.get(&key) { - metrics::counter!("sprout_accessible_channels_cache_hits_total").increment(1); + metrics::counter!("buzz_accessible_channels_cache_hits_total").increment(1); return Ok(cached); } - metrics::counter!("sprout_accessible_channels_cache_misses_total").increment(1); + metrics::counter!("buzz_accessible_channels_cache_misses_total").increment(1); let result = self.db.get_accessible_channel_ids(pubkey).await?; self.accessible_channels_cache.insert(key, result.clone()); Ok(result) @@ -501,7 +501,7 @@ impl AppState { pub async fn channel_visibility_cached( &self, channel_id: Uuid, - ) -> Result { + ) -> Result { if let Some(cached) = self.channel_visibility_cache.get(&channel_id) { return Ok(cached); } @@ -541,13 +541,13 @@ impl AuditShutdownHandle { /// Log a single audit entry with metrics. Extracted so the normal loop /// and the post-cancel drain share the same logic. -async fn log_audit_entry(audit: &sprout_audit::AuditService, entry: sprout_audit::NewAuditEntry) { +async fn log_audit_entry(audit: &buzz_audit::AuditService, entry: buzz_audit::NewAuditEntry) { let t = std::time::Instant::now(); if let Err(e) = audit.log(entry).await { - metrics::counter!("sprout_audit_log_errors_total").increment(1); + metrics::counter!("buzz_audit_log_errors_total").increment(1); tracing::error!("Audit log failed: {e}"); } else { - metrics::histogram!("sprout_audit_log_seconds").record(t.elapsed().as_secs_f64()); + metrics::histogram!("buzz_audit_log_seconds").record(t.elapsed().as_secs_f64()); } } diff --git a/crates/sprout-relay/src/subscription.rs b/crates/buzz-relay/src/subscription.rs similarity index 98% rename from crates/sprout-relay/src/subscription.rs rename to crates/buzz-relay/src/subscription.rs index cbc8477a9..3b2169340 100644 --- a/crates/sprout-relay/src/subscription.rs +++ b/crates/buzz-relay/src/subscription.rs @@ -6,7 +6,7 @@ use dashmap::DashMap; use nostr::{Alphabet, Filter, Kind, SingleLetterTag}; use uuid::Uuid; -use sprout_core::{filter::filters_match, StoredEvent}; +use buzz_core::{filter::filters_match, StoredEvent}; /// Connection identifier — a UUID assigned to each WebSocket connection. pub type ConnId = Uuid; @@ -67,7 +67,7 @@ impl SubscriptionRegistry { .entry(conn_id) .or_default() .insert(sub_id.clone(), (filters.clone(), channel_id)); - metrics::gauge!("sprout_subscriptions_active").increment(1.0); + metrics::gauge!("buzz_subscriptions_active").increment(1.0); if let Some(ch_id) = channel_id { match extract_kinds_from_filters(&filters) { @@ -136,7 +136,7 @@ impl SubscriptionRegistry { if let Some(mut conn_subs) = self.subs.get_mut(&conn_id) { if let Some((filters, channel_id)) = conn_subs.remove(sub_id) { self.remove_from_index(conn_id, sub_id, &filters, channel_id); - metrics::gauge!("sprout_subscriptions_active").decrement(1.0); + metrics::gauge!("buzz_subscriptions_active").decrement(1.0); } } } @@ -148,7 +148,7 @@ impl SubscriptionRegistry { for (sub_id, (filters, channel_id)) in &conn_subs { self.remove_from_index(conn_id, sub_id, filters, *channel_id); } - metrics::gauge!("sprout_subscriptions_active").decrement(count as f64); + metrics::gauge!("buzz_subscriptions_active").decrement(count as f64); } } @@ -457,9 +457,9 @@ fn extract_kinds_from_filters(filters: &[Filter]) -> Option> { #[cfg(test)] mod tests { use super::*; + use buzz_core::StoredEvent; use chrono::Utc; use nostr::{EventBuilder, Keys, Kind, Tag}; - use sprout_core::StoredEvent; fn make_stored_event(kind: Kind, channel_id: Option) -> StoredEvent { let keys = Keys::generate(); @@ -1028,7 +1028,7 @@ mod tests { let registry = SubscriptionRegistry::new(); let conn_a = Uuid::new_v4(); let conn_b = Uuid::new_v4(); - let kind = Kind::Custom(sprout_core::kind::KIND_AGENT_OBSERVER_FRAME as u16); + let kind = Kind::Custom(buzz_core::kind::KIND_AGENT_OBSERVER_FRAME as u16); let p_a = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; let p_b = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; @@ -1059,7 +1059,7 @@ mod tests { fn test_global_p_kind_index_removal_cleanup() { let registry = SubscriptionRegistry::new(); let conn_id = Uuid::new_v4(); - let kind = Kind::Custom(sprout_core::kind::KIND_AGENT_OBSERVER_FRAME as u16); + let kind = Kind::Custom(buzz_core::kind::KIND_AGENT_OBSERVER_FRAME as u16); let p = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; let filter = Filter::new().kind(kind).custom_tags(p_tag(), [p]); let key = GlobalPKindIndexKey { diff --git a/crates/sprout-relay/src/webhook_secret.rs b/crates/buzz-relay/src/webhook_secret.rs similarity index 100% rename from crates/sprout-relay/src/webhook_secret.rs rename to crates/buzz-relay/src/webhook_secret.rs diff --git a/crates/sprout-relay/src/workflow_sink.rs b/crates/buzz-relay/src/workflow_sink.rs similarity index 93% rename from crates/sprout-relay/src/workflow_sink.rs rename to crates/buzz-relay/src/workflow_sink.rs index 46a6b9cf7..3d40cafce 100644 --- a/crates/sprout-relay/src/workflow_sink.rs +++ b/crates/buzz-relay/src/workflow_sink.rs @@ -8,10 +8,10 @@ use std::future::Future; use std::pin::Pin; use std::sync::{Arc, Weak}; +use buzz_core::kind::KIND_STREAM_MESSAGE; +use buzz_workflow::action_sink::{ActionSink, ActionSinkError}; use chrono::Utc; use nostr::{EventBuilder, Kind, Tag}; -use sprout_core::kind::KIND_STREAM_MESSAGE; -use sprout_workflow::action_sink::{ActionSink, ActionSinkError}; use tracing::info; use uuid::Uuid; @@ -72,7 +72,7 @@ impl ActionSink for RelayActionSink { .get_channel(channel_uuid) .await .map_err(|e| match &e { - sprout_db::DbError::ChannelNotFound(_) | sprout_db::DbError::NotFound(_) => { + buzz_db::DbError::ChannelNotFound(_) | buzz_db::DbError::NotFound(_) => { ActionSinkError::ChannelNotFound(channel_id_canonical.clone()) } _ => ActionSinkError::Database(e.to_string()), @@ -103,13 +103,13 @@ impl ActionSink for RelayActionSink { // - Signed by relay keypair (event.pubkey = relay pubkey) // - `p` tag attributes the message to the workflow owner // - `h` tag scopes to the channel (NIP-29, canonical UUID) - // - `sprout:workflow` tag prevents recursive workflow triggering + // - `buzz:workflow` tag prevents recursive workflow triggering let tags = vec![ Tag::parse(["p", &author_pubkey_hex]) .map_err(|e| ActionSinkError::EventBuild(format!("p tag: {e}")))?, Tag::parse(["h", &channel_id_canonical]) .map_err(|e| ActionSinkError::EventBuild(format!("h tag: {e}")))?, - Tag::parse(["sprout:workflow", "true"]) + Tag::parse(["buzz:workflow", "true"]) .map_err(|e| ActionSinkError::EventBuild(format!("workflow tag: {e}")))?, ]; @@ -137,7 +137,7 @@ impl ActionSink for RelayActionSink { // 4. Persist event with thread metadata (matches REST handler path). // Workflow messages are always top-level: depth=0, no parent/root. - let thread_meta = Some(sprout_db::event::ThreadMetadataParams { + let thread_meta = Some(buzz_db::event::ThreadMetadataParams { event_id: &event_id_bytes, event_created_at, channel_id: channel_uuid, diff --git a/crates/sprout-sdk/Cargo.toml b/crates/buzz-sdk/Cargo.toml similarity index 72% rename from crates/sprout-sdk/Cargo.toml rename to crates/buzz-sdk/Cargo.toml index 8c1b54808..da8fd0bd0 100644 --- a/crates/sprout-sdk/Cargo.toml +++ b/crates/buzz-sdk/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "sprout-sdk" +name = "buzz-sdk" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true repository.workspace = true -description = "Typed Nostr event builders for Sprout operations" +description = "Typed Nostr event builders for Buzz operations" [dependencies] -sprout-core = { workspace = true } +buzz-core = { workspace = true } nostr = { workspace = true } uuid = { workspace = true } serde = { workspace = true } diff --git a/crates/sprout-sdk/examples/compute_auth_tag.rs b/crates/buzz-sdk/examples/compute_auth_tag.rs similarity index 97% rename from crates/sprout-sdk/examples/compute_auth_tag.rs rename to crates/buzz-sdk/examples/compute_auth_tag.rs index e8c268dd3..b2bc5a5e6 100644 --- a/crates/sprout-sdk/examples/compute_auth_tag.rs +++ b/crates/buzz-sdk/examples/compute_auth_tag.rs @@ -5,8 +5,8 @@ //! //! Prints the JSON auth tag to stdout. +use buzz_sdk::nip_oa; use nostr::{Keys, PublicKey}; -use sprout_sdk::nip_oa; fn main() { let args: Vec = std::env::args().collect(); diff --git a/crates/sprout-sdk/src/builders.rs b/crates/buzz-sdk/src/builders.rs similarity index 99% rename from crates/sprout-sdk/src/builders.rs rename to crates/buzz-sdk/src/builders.rs index eb0c98bb2..bf6910cae 100644 --- a/crates/sprout-sdk/src/builders.rs +++ b/crates/buzz-sdk/src/builders.rs @@ -3,8 +3,7 @@ //! All functions return `Result`. //! The caller signs: `builder.sign_with_keys(&keys)?`. -use nostr::{EventBuilder, Kind, Tag}; -use sprout_core::{ +use buzz_core::{ kind::{ KIND_AGENT_OBSERVER_FRAME, KIND_APPROVAL_DENY, KIND_APPROVAL_GRANT, KIND_DELETION, KIND_DM_ADD_MEMBER, KIND_DM_OPEN, KIND_EMOJI_SET, KIND_GIT_REPO_ANNOUNCEMENT, @@ -15,6 +14,7 @@ use sprout_core::{ OBSERVER_FRAME_TELEMETRY, }, }; +use nostr::{EventBuilder, Kind, Tag}; use uuid::Uuid; use crate::{ @@ -59,7 +59,7 @@ fn check_pubkey_hex(s: &str, field: &str) -> Result { /// Validate and normalize a NIP-30 custom emoji shortcode. /// -/// Shortcodes are case-insensitive in Sprout's relay-global set; lowercase +/// Shortcodes are case-insensitive in Buzz's relay-global set; lowercase /// normalization prevents `party_parrot` and `Party_Parrot` from colliding. pub fn normalize_custom_emoji_shortcode(shortcode: &str) -> Result { let trimmed = shortcode.trim().trim_matches(':'); @@ -339,7 +339,7 @@ pub fn build_edit( // ── Builder 6: build_delete_message ────────────────────────────────────────── -/// Build a Sprout-native delete event (kind 9005). +/// Build a Buzz-native delete event (kind 9005). pub fn build_delete_message( channel_id: Uuid, target_event_id: nostr::EventId, @@ -432,12 +432,12 @@ pub fn build_remove_reaction(reaction_event_id: nostr::EventId) -> Result Result { @@ -1122,7 +1122,7 @@ mod tests { let sender = keys(); let recipient = keys(); let agent = keys(); - let encrypted = sprout_core::observer::encrypt_observer_payload( + let encrypted = buzz_core::observer::encrypt_observer_payload( &sender, &recipient.public_key(), &serde_json::json!({"type": "acp_read"}), diff --git a/crates/sprout-sdk/src/lib.rs b/crates/buzz-sdk/src/lib.rs similarity index 89% rename from crates/sprout-sdk/src/lib.rs rename to crates/buzz-sdk/src/lib.rs index e62a63544..517f8a7ca 100644 --- a/crates/sprout-sdk/src/lib.rs +++ b/crates/buzz-sdk/src/lib.rs @@ -1,6 +1,6 @@ #![deny(unsafe_code)] #![warn(missing_docs)] -//! `sprout-sdk` — typed Nostr event builders for Sprout operations. +//! `buzz-sdk` — typed Nostr event builders for Buzz operations. //! //! # Mental Model //! @@ -18,8 +18,8 @@ pub mod nip_oa; pub use builders::*; -/// Re-export kind constants so consumers don't need sprout-core directly. -pub use sprout_core::kind; +/// Re-export kind constants so consumers don't need buzz-core directly. +pub use buzz_core::kind; // ── Types ──────────────────────────────────────────────────────────────────── @@ -76,14 +76,14 @@ pub struct CustomEmoji { pub url: String, } -// ── Channel / Member enums (re-exported from sprout-core) ──────────────────── +// ── Channel / Member enums (re-exported from buzz-core) ──────────────────── /// Channel type. -pub use sprout_core::channel::ChannelType as ChannelKind; +pub use buzz_core::channel::ChannelType as ChannelKind; /// Channel visibility. -pub use sprout_core::channel::ChannelVisibility as Visibility; +pub use buzz_core::channel::ChannelVisibility as Visibility; /// Member role. -pub use sprout_core::channel::MemberRole; +pub use buzz_core::channel::MemberRole; // ── Error ──────────────────────────────────────────────────────────────────── diff --git a/crates/sprout-sdk/src/mentions.rs b/crates/buzz-sdk/src/mentions.rs similarity index 99% rename from crates/sprout-sdk/src/mentions.rs rename to crates/buzz-sdk/src/mentions.rs index 5a7b2fe52..f4b54bd89 100644 --- a/crates/sprout-sdk/src/mentions.rs +++ b/crates/buzz-sdk/src/mentions.rs @@ -1,4 +1,4 @@ -//! `@name` and NIP-27 `nostr:npub1…` mention resolution helpers for Sprout chat messages. +//! `@name` and NIP-27 `nostr:npub1…` mention resolution helpers for Buzz chat messages. //! //! These helpers are **pure** — no network calls, no async. Callers query //! channel membership (kind 39002) and profile (kind 0) events themselves, @@ -33,7 +33,7 @@ use nostr::{FromBech32, PublicKey}; /// Maximum number of mention p-tags allowed on a single message. /// -/// Matches the cap enforced by Sprout message builders and the legacy MCP +/// Matches the cap enforced by Buzz message builders and the legacy MCP /// inline implementation. pub const MENTION_CAP: usize = 50; diff --git a/crates/sprout-sdk/src/nip_oa.rs b/crates/buzz-sdk/src/nip_oa.rs similarity index 100% rename from crates/sprout-sdk/src/nip_oa.rs rename to crates/buzz-sdk/src/nip_oa.rs diff --git a/crates/sprout-search/Cargo.toml b/crates/buzz-search/Cargo.toml similarity index 79% rename from crates/sprout-search/Cargo.toml rename to crates/buzz-search/Cargo.toml index 43effac2c..3c571733a 100644 --- a/crates/sprout-search/Cargo.toml +++ b/crates/buzz-search/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "sprout-search" +name = "buzz-search" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true repository.workspace = true -description = "Typesense-backed full-text search for Sprout" +description = "Typesense-backed full-text search for Buzz" [dependencies] -sprout-core = { workspace = true } +buzz-core = { workspace = true } reqwest = { workspace = true } tokio = { workspace = true } serde = { workspace = true } diff --git a/crates/sprout-search/src/collection.rs b/crates/buzz-search/src/collection.rs similarity index 100% rename from crates/sprout-search/src/collection.rs rename to crates/buzz-search/src/collection.rs diff --git a/crates/sprout-search/src/error.rs b/crates/buzz-search/src/error.rs similarity index 100% rename from crates/sprout-search/src/error.rs rename to crates/buzz-search/src/error.rs diff --git a/crates/sprout-search/src/index.rs b/crates/buzz-search/src/index.rs similarity index 98% rename from crates/sprout-search/src/index.rs rename to crates/buzz-search/src/index.rs index 29b4f4b9f..8e59c70e0 100644 --- a/crates/sprout-search/src/index.rs +++ b/crates/buzz-search/src/index.rs @@ -3,8 +3,8 @@ use serde_json::{json, Value}; use tracing::{debug, warn}; -use sprout_core::event::StoredEvent; -use sprout_core::kind::event_kind_i32; +use buzz_core::event::StoredEvent; +use buzz_core::kind::event_kind_i32; use crate::error::SearchError; @@ -62,7 +62,7 @@ pub fn event_to_document(event: &StoredEvent) -> Result { // // NOTE: existing kind:0 docs indexed before this change won't have the // appended tokens. Running `just reindex-search` (or the - // `sprout-relay reindex-search` admin path) repopulates them. New / + // `buzz-relay reindex-search` admin path) repopulates them. New / // updated profiles get the tokens automatically. let content_indexed = if event_kind_i32(nostr_event) == 0 { flatten_kind0_for_indexing(nostr_event.content.as_str()) @@ -73,7 +73,7 @@ pub fn event_to_document(event: &StoredEvent) -> Result { let doc = json!({ "id": nostr_event.id.to_string(), "content": content_indexed, - // Cast to i32 for Typesense schema (int32 field). nostr Kind is u16; all Sprout kinds fit in i32. + // Cast to i32 for Typesense schema (int32 field). nostr Kind is u16; all Buzz kinds fit in i32. "kind": event_kind_i32(nostr_event), "pubkey": nostr_event.pubkey.to_string(), "channel_id": channel_id_val, @@ -295,8 +295,8 @@ pub async fn delete_event( #[cfg(test)] mod tests { use super::*; + use buzz_core::event::StoredEvent; use nostr::{EventBuilder, Keys, Kind}; - use sprout_core::event::StoredEvent; use uuid::Uuid; fn make_stored_event(content: &str, kind: Kind, channel_id: Option) -> StoredEvent { diff --git a/crates/sprout-search/src/lib.rs b/crates/buzz-search/src/lib.rs similarity index 95% rename from crates/sprout-search/src/lib.rs rename to crates/buzz-search/src/lib.rs index d8cec5284..2a9c3b418 100644 --- a/crates/sprout-search/src/lib.rs +++ b/crates/buzz-search/src/lib.rs @@ -1,6 +1,6 @@ #![deny(unsafe_code)] #![warn(missing_docs)] -//! Sprout search — Typesense integration for full-text event search. +//! Buzz search — Typesense integration for full-text event search. /// Typesense collection schema management. pub mod collection; @@ -14,7 +14,7 @@ pub mod query; pub use error::SearchError; pub use query::{SearchHit, SearchQuery, SearchResult}; -use sprout_core::event::StoredEvent; +use buzz_core::event::StoredEvent; /// Configuration for the Typesense search backend. /// @@ -24,11 +24,11 @@ use sprout_core::event::StoredEvent; /// | Field | Environment variable | Default (dev only) | /// |--------------|-------------------------|--------------------------| /// | `url` | `TYPESENSE_URL` | `http://localhost:8108` | -/// | `api_key` | `TYPESENSE_API_KEY` | `sprout_dev_key` | +/// | `api_key` | `TYPESENSE_API_KEY` | `buzz_dev_key` | /// | `collection` | `TYPESENSE_COLLECTION` | `events` | /// /// In production, always set `TYPESENSE_API_KEY` explicitly. The fallback -/// value `sprout_dev_key` is intentionally weak and only suitable for local +/// value `buzz_dev_key` is intentionally weak and only suitable for local /// development with a locally-running Typesense instance. #[derive(Debug, Clone)] pub struct SearchConfig { @@ -44,7 +44,7 @@ impl Default for SearchConfig { fn default() -> Self { Self { url: std::env::var("TYPESENSE_URL").unwrap_or_else(|_| "http://localhost:8108".into()), - api_key: std::env::var("TYPESENSE_API_KEY").unwrap_or_else(|_| "sprout_dev_key".into()), + api_key: std::env::var("TYPESENSE_API_KEY").unwrap_or_else(|_| "buzz_dev_key".into()), collection: std::env::var("TYPESENSE_COLLECTION").unwrap_or_else(|_| "events".into()), } } @@ -163,7 +163,7 @@ mod integration_tests { let client = reqwest::Client::new(); client .get("http://localhost:8108/health") - .header("X-TYPESENSE-API-KEY", "sprout_dev_key") + .header("X-TYPESENSE-API-KEY", "buzz_dev_key") .timeout(std::time::Duration::from_secs(2)) .send() .await @@ -174,7 +174,7 @@ mod integration_tests { fn make_service(collection: &str) -> SearchService { SearchService::new(SearchConfig { url: "http://localhost:8108".into(), - api_key: "sprout_dev_key".into(), + api_key: "buzz_dev_key".into(), collection: collection.to_string(), }) } @@ -227,7 +227,7 @@ mod integration_tests { let service = make_service(&collection); service.ensure_collection().await.unwrap(); - let unique_token = format!("sprout_search_test_{}", Uuid::new_v4().simple()); + let unique_token = format!("buzz_search_test_{}", Uuid::new_v4().simple()); let stored = make_stored_event(&format!("hello {}", unique_token), Kind::TextNote); let event_id = stored.event.id.to_string(); diff --git a/crates/sprout-search/src/query.rs b/crates/buzz-search/src/query.rs similarity index 99% rename from crates/sprout-search/src/query.rs rename to crates/buzz-search/src/query.rs index 95c71da5e..e8fbf1700 100644 --- a/crates/sprout-search/src/query.rs +++ b/crates/buzz-search/src/query.rs @@ -271,7 +271,7 @@ mod tests { { "document": { "id": "abc123", - "content": "hello sprout", + "content": "hello buzz", "kind": 1, "pubkey": "deadbeef", "channel_id": "chan-uuid", @@ -304,7 +304,7 @@ mod tests { let h0 = &result.hits[0]; assert_eq!(h0.event_id, "abc123"); - assert_eq!(h0.content, "hello sprout"); + assert_eq!(h0.content, "hello buzz"); assert_eq!(h0.kind, 1); assert_eq!(h0.pubkey, "deadbeef"); assert_eq!(h0.channel_id.as_deref(), Some("chan-uuid")); diff --git a/crates/sprout-test-client/Cargo.toml b/crates/buzz-test-client/Cargo.toml similarity index 83% rename from crates/sprout-test-client/Cargo.toml rename to crates/buzz-test-client/Cargo.toml index b9229388b..bba41127b 100644 --- a/crates/sprout-test-client/Cargo.toml +++ b/crates/buzz-test-client/Cargo.toml @@ -1,16 +1,16 @@ [package] -name = "sprout-test-client" +name = "buzz-test-client" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true repository.workspace = true -description = "Integration test client and E2E test suite for Sprout" +description = "Integration test client and E2E test suite for Buzz" [dependencies] anyhow = { workspace = true } -sprout-core = { workspace = true } -sprout-ws-client = { workspace = true } +buzz-core = { workspace = true } +buzz-ws-client = { workspace = true } nostr = { workspace = true } tokio = { workspace = true } tokio-tungstenite = { workspace = true } @@ -37,5 +37,5 @@ chrono = { workspace = true } s3 = { version = "0.37", package = "rust-s3", default-features = false, features = ["tokio-rustls-tls", "fail-on-err", "tags"] } [[bin]] -name = "sprout-test-cli" +name = "buzz-test-cli" path = "src/main.rs" diff --git a/crates/sprout-test-client/src/bin/mention.rs b/crates/buzz-test-client/src/bin/mention.rs similarity index 80% rename from crates/sprout-test-client/src/bin/mention.rs rename to crates/buzz-test-client/src/bin/mention.rs index 37a4d86b9..61fc70465 100644 --- a/crates/sprout-test-client/src/bin/mention.rs +++ b/crates/buzz-test-client/src/bin/mention.rs @@ -1,8 +1,8 @@ -//! Send an @mention event to a Sprout channel targeting a specific pubkey. +//! Send an @mention event to a Buzz channel targeting a specific pubkey. //! Usage: mention +use buzz_test_client::BuzzTestClient; use nostr::{EventBuilder, Keys, Kind, Tag}; -use sprout_test_client::SproutTestClient; #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -17,11 +17,11 @@ async fn main() -> anyhow::Result<()> { let target_pubkey = &args[2]; let message = args[3..].join(" "); - let url = std::env::var("SPROUT_RELAY_URL").unwrap_or_else(|_| "ws://localhost:3000".into()); + let url = std::env::var("BUZZ_RELAY_URL").unwrap_or_else(|_| "ws://localhost:3000".into()); let keys = Keys::generate(); println!("Sender pubkey: {}", keys.public_key().to_hex()); - let mut client = SproutTestClient::connect(&url, &keys).await?; + let mut client = BuzzTestClient::connect(&url, &keys).await?; let h_tag = Tag::parse(["h", channel_id])?; let p_tag = Tag::parse(["p", target_pubkey])?; diff --git a/crates/sprout-test-client/src/lib.rs b/crates/buzz-test-client/src/lib.rs similarity index 97% rename from crates/sprout-test-client/src/lib.rs rename to crates/buzz-test-client/src/lib.rs index 0d900122d..11c3fdda9 100644 --- a/crates/sprout-test-client/src/lib.rs +++ b/crates/buzz-test-client/src/lib.rs @@ -1,7 +1,7 @@ #![deny(unsafe_code)] #![warn(missing_docs)] -//! Minimal NIP-01 WebSocket test client for the Sprout relay. +//! Minimal NIP-01 WebSocket test client for the Buzz relay. use std::time::Duration; @@ -10,10 +10,10 @@ use serde_json::{json, Value}; use thiserror::Error; use tracing::debug; -use sprout_ws_client::NostrWsConnection; -pub use sprout_ws_client::{parse_relay_message, OkResponse, RelayMessage, WsClientError}; +use buzz_ws_client::NostrWsConnection; +pub use buzz_ws_client::{parse_relay_message, OkResponse, RelayMessage, WsClientError}; -/// Errors returned by [`SproutTestClient`] operations. +/// Errors returned by [`BuzzTestClient`] operations. #[derive(Debug, Error)] pub enum TestClientError { /// A WebSocket transport error occurred. @@ -80,12 +80,12 @@ impl From for TestClientError { } } -/// WebSocket test client for integration testing against a running Sprout relay. -pub struct SproutTestClient { +/// WebSocket test client for integration testing against a running Buzz relay. +pub struct BuzzTestClient { inner: NostrWsConnection, } -impl SproutTestClient { +impl BuzzTestClient { /// Connects to the relay at `url` and performs NIP-42 authentication with `keys`. pub async fn connect(url: &str, keys: &Keys) -> Result { let mut client = Self::connect_unauthenticated(url).await?; diff --git a/crates/sprout-test-client/src/main.rs b/crates/buzz-test-client/src/main.rs similarity index 85% rename from crates/sprout-test-client/src/main.rs rename to crates/buzz-test-client/src/main.rs index 52e9749a8..858f795c8 100644 --- a/crates/sprout-test-client/src/main.rs +++ b/crates/buzz-test-client/src/main.rs @@ -1,9 +1,9 @@ -//! `sprout-test-cli` — Manual testing CLI for the Sprout relay. +//! `buzz-test-cli` — Manual testing CLI for the Buzz relay. //! //! # Usage //! //! ```text -//! sprout-test-cli [OPTIONS] +//! buzz-test-cli [OPTIONS] //! //! Options: //! --url Relay WebSocket URL [default: ws://localhost:3000] @@ -17,25 +17,25 @@ //! //! Send a message: //! ```text -//! sprout-test-cli --channel my-channel --send "Hello, Sprout!" +//! buzz-test-cli --channel my-channel --send "Hello, Buzz!" //! ``` //! //! Subscribe and watch events: //! ```text -//! sprout-test-cli --channel my-channel --subscribe +//! buzz-test-cli --channel my-channel --subscribe //! ``` use std::time::Duration; +use buzz_test_client::{BuzzTestClient, RelayMessage}; use nostr::{Filter, Keys}; -use sprout_test_client::{RelayMessage, SproutTestClient}; #[tokio::main] async fn main() { tracing_subscriber::fmt() .with_env_filter( std::env::var("RUST_LOG") - .unwrap_or_else(|_| "sprout_test_client=debug".to_string()) + .unwrap_or_else(|_| "buzz_test_client=debug".to_string()) .as_str(), ) .init(); @@ -47,8 +47,8 @@ async fn main() { let channel = opts.channel.as_deref().unwrap_or("default"); let kind = opts.kind.unwrap_or(9); - let keys = match std::env::var("SPROUT_PRIVATE_KEY") { - Ok(sk) => Keys::parse(&sk).expect("invalid SPROUT_PRIVATE_KEY"), + let keys = match std::env::var("BUZZ_PRIVATE_KEY") { + Ok(sk) => Keys::parse(&sk).expect("invalid BUZZ_PRIVATE_KEY"), Err(_) => Keys::generate(), }; println!("Using pubkey: {}", keys.public_key()); @@ -66,7 +66,7 @@ async fn main() { async fn run_send(url: &str, keys: &Keys, channel: &str, message: &str, kind: u16) { println!("Connecting to {url}..."); - let mut client = match SproutTestClient::connect(url, keys).await { + let mut client = match BuzzTestClient::connect(url, keys).await { Ok(c) => c, Err(e) => { eprintln!("Failed to connect: {e}"); @@ -94,7 +94,7 @@ async fn run_send(url: &str, keys: &Keys, channel: &str, message: &str, kind: u1 async fn run_subscribe(url: &str, keys: &Keys, channel: &str, kind: u16) { println!("Connecting to {url}..."); - let mut client = match SproutTestClient::connect(url, keys).await { + let mut client = match BuzzTestClient::connect(url, keys).await { Ok(c) => c, Err(e) => { eprintln!("Failed to connect: {e}"); @@ -140,7 +140,7 @@ async fn run_subscribe(url: &str, keys: &Keys, channel: &str, kind: u16) { break; } Ok(_) => {} - Err(sprout_test_client::TestClientError::Timeout) => { + Err(buzz_test_client::TestClientError::Timeout) => { // Keep waiting. } Err(e) => { @@ -209,10 +209,10 @@ fn parse_args(args: &[String]) -> CliOpts { fn print_help() { println!( - r#"sprout-test-cli — Manual testing CLI for the Sprout relay + r#"buzz-test-cli — Manual testing CLI for the Buzz relay USAGE: - sprout-test-cli [OPTIONS] + buzz-test-cli [OPTIONS] OPTIONS: --url Relay WebSocket URL [default: ws://localhost:3000] @@ -224,13 +224,13 @@ OPTIONS: EXAMPLES: # Send a message to a channel - sprout-test-cli --channel my-channel --send "Hello, Sprout!" + buzz-test-cli --channel my-channel --send "Hello, Buzz!" # Subscribe and watch live events - sprout-test-cli --channel my-channel --subscribe + buzz-test-cli --channel my-channel --subscribe # Use a different relay URL - sprout-test-cli --url ws://relay.example.com --channel test --subscribe + buzz-test-cli --url ws://relay.example.com --channel test --subscribe "# ); } diff --git a/crates/sprout-test-client/tests/e2e_git.rs b/crates/buzz-test-client/tests/e2e_git.rs similarity index 93% rename from crates/sprout-test-client/tests/e2e_git.rs rename to crates/buzz-test-client/tests/e2e_git.rs index 3d7689e87..a341d3c8c 100644 --- a/crates/sprout-test-client/tests/e2e_git.rs +++ b/crates/buzz-test-client/tests/e2e_git.rs @@ -14,7 +14,7 @@ //! ```text //! cargo build --release -p git-credential-nostr //! GIT_CREDENTIAL_NOSTR_BIN=$PWD/target/release/git-credential-nostr \ -//! cargo test -p sprout-test-client --test e2e_git -- --ignored --nocapture +//! cargo test -p buzz-test-client --test e2e_git -- --ignored --nocapture //! ``` use std::path::{Path, PathBuf}; @@ -61,7 +61,7 @@ async fn post_event(event: &nostr::Event) { ); } -/// Run `git` with the Sprout credential helper and isolated config. +/// Run `git` with the Buzz credential helper and isolated config. fn git_status(args: &[&str], cwd: &Path, owner_nsec: &str) -> std::process::Output { let helper = credential_helper(); Command::new("git") @@ -90,7 +90,7 @@ fn git_status(args: &[&str], cwd: &Path, owner_nsec: &str) -> std::process::Outp .expect("spawn git") } -/// Run `git` with the Sprout credential helper and isolated config. Asserts the +/// Run `git` with the Buzz credential helper and isolated config. Asserts the /// command succeeds; returns stdout. fn git(args: &[&str], cwd: &Path, owner_nsec: &str) -> String { let out = git_status(args, cwd, owner_nsec); @@ -116,18 +116,18 @@ struct PointerSnapshot { impl GitS3Probe { fn from_env() -> Self { - let endpoint = std::env::var("SPROUT_GIT_S3_ENDPOINT") - .or_else(|_| std::env::var("SPROUT_S3_ENDPOINT")) + let endpoint = std::env::var("BUZZ_GIT_S3_ENDPOINT") + .or_else(|_| std::env::var("BUZZ_S3_ENDPOINT")) .unwrap_or_else(|_| "http://localhost:9000".to_string()); - let access_key = std::env::var("SPROUT_GIT_S3_ACCESS_KEY") - .or_else(|_| std::env::var("SPROUT_S3_ACCESS_KEY")) - .unwrap_or_else(|_| "sprout_dev".to_string()); - let secret_key = std::env::var("SPROUT_GIT_S3_SECRET_KEY") - .or_else(|_| std::env::var("SPROUT_S3_SECRET_KEY")) - .unwrap_or_else(|_| "sprout_dev_secret".to_string()); - let bucket = std::env::var("SPROUT_GIT_S3_BUCKET") - .or_else(|_| std::env::var("SPROUT_S3_BUCKET")) - .unwrap_or_else(|_| "sprout-media".to_string()); + let access_key = std::env::var("BUZZ_GIT_S3_ACCESS_KEY") + .or_else(|_| std::env::var("BUZZ_S3_ACCESS_KEY")) + .unwrap_or_else(|_| "buzz_dev".to_string()); + let secret_key = std::env::var("BUZZ_GIT_S3_SECRET_KEY") + .or_else(|_| std::env::var("BUZZ_S3_SECRET_KEY")) + .unwrap_or_else(|_| "buzz_dev_secret".to_string()); + let bucket = std::env::var("BUZZ_GIT_S3_BUCKET") + .or_else(|_| std::env::var("BUZZ_S3_BUCKET")) + .unwrap_or_else(|_| "buzz-media".to_string()); let region = Region::Custom { region: "us-east-1".into(), @@ -348,7 +348,7 @@ async fn git_concurrent_push_one_wins_and_repo_recovers() { post_event(&announce).await; tokio::time::sleep(std::time::Duration::from_secs(2)).await; - let tmp = tempdir_named("sprout-e2e-git-concurrent"); + let tmp = tempdir_named("buzz-e2e-git-concurrent"); let url = format!("{}/git/{}/{}", relay_http_url(), owner_hex, repo); git(&["clone", "--quiet", &url, "seed"], tmp.path(), &owner_nsec); @@ -462,7 +462,7 @@ impl Drop for TempDir { } } fn tempdir() -> TempDir { - tempdir_named("sprout-e2e-git") + tempdir_named("buzz-e2e-git") } fn tempdir_named(prefix: &str) -> TempDir { diff --git a/crates/sprout-test-client/tests/e2e_long_form.rs b/crates/buzz-test-client/tests/e2e_long_form.rs similarity index 94% rename from crates/sprout-test-client/tests/e2e_long_form.rs rename to crates/buzz-test-client/tests/e2e_long_form.rs index 8e2ee2612..2f60a8985 100644 --- a/crates/sprout-test-client/tests/e2e_long_form.rs +++ b/crates/buzz-test-client/tests/e2e_long_form.rs @@ -20,8 +20,8 @@ use std::time::Duration; +use buzz_test_client::BuzzTestClient; use nostr::{Alphabet, EventBuilder, Filter, Keys, Kind, SingleLetterTag, Tag, Timestamp}; -use sprout_test_client::SproutTestClient; const KIND_LONG_FORM: u16 = 30023; @@ -60,9 +60,7 @@ fn build_long_form_event( async fn test_long_form_accepted() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let event = build_long_form_event( &keys, @@ -88,9 +86,7 @@ async fn test_long_form_accepted() { async fn test_long_form_retrievable() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let d_tag = format!("retrieve-{}", uuid::Uuid::new_v4().simple()); let event = build_long_form_event( @@ -136,9 +132,7 @@ async fn test_long_form_retrievable() { async fn test_long_form_stray_h_tag_ignored() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); // Publish with a stray h-tag (a UUID that doesn't correspond to any channel). let fake_channel = uuid::Uuid::new_v4().to_string(); @@ -192,9 +186,7 @@ async fn test_long_form_stray_h_tag_ignored() { async fn test_long_form_nip33_replacement() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let d_tag = format!("replace-{}", uuid::Uuid::new_v4().simple()); @@ -254,9 +246,7 @@ async fn test_long_form_nip33_replacement() { async fn test_long_form_stale_write_rejected() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let d_tag = format!("stale-{}", uuid::Uuid::new_v4().simple()); @@ -333,9 +323,7 @@ async fn test_long_form_stale_write_rejected() { async fn test_long_form_a_tag_deletion() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); // Publish a note. let d_tag = format!("a-del-{}", uuid::Uuid::new_v4().simple()); @@ -414,9 +402,7 @@ async fn test_long_form_a_tag_deletion() { async fn test_long_form_malformed_e_plus_a_does_not_delete() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let d_tag = format!("mixed-del-{}", uuid::Uuid::new_v4().simple()); let note = build_long_form_event(&keys, &d_tag, "Survivor", "Body.", vec![]); @@ -466,20 +452,18 @@ async fn test_long_form_malformed_e_plus_a_does_not_delete() { /// `notes set` re-publish preserves the original `published_at` while letting /// `created_at` advance. This is the contract that NIP-23 readers rely on to /// tell "when the author first wrote this" from "when they last updated it", -/// and the carry-forward logic in `sprout-cli`'s `build_set_event` (unit-tested +/// and the carry-forward logic in `buzz-cli`'s `build_set_event` (unit-tested /// there) only works if the relay round-trips the tag faithfully. /// /// The carry rule is duplicated inline here (rather than reaching into -/// `sprout-cli`) so this e2e crate stays free of CLI deps; the rule's +/// `buzz-cli`) so this e2e crate stays free of CLI deps; the rule's /// correctness is unit-tested in `commands::notes::tests`. #[tokio::test] #[ignore] async fn test_long_form_set_twice_preserves_published_at() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let d_tag = format!("preserve-pat-{}", uuid::Uuid::new_v4().simple()); let original_published_at: u64 = 1_700_000_000; diff --git a/crates/sprout-test-client/tests/e2e_media.rs b/crates/buzz-test-client/tests/e2e_media.rs similarity index 98% rename from crates/sprout-test-client/tests/e2e_media.rs rename to crates/buzz-test-client/tests/e2e_media.rs index 090a7454e..6225c14d9 100644 --- a/crates/sprout-test-client/tests/e2e_media.rs +++ b/crates/buzz-test-client/tests/e2e_media.rs @@ -6,13 +6,13 @@ //! # Running //! //! ```text -//! cargo test -p sprout-test-client --test e2e_media -- --ignored --nocapture +//! cargo test -p buzz-test-client --test e2e_media -- --ignored --nocapture //! ``` //! //! Override the relay URL: //! //! ```text -//! RELAY_HTTP_URL=http://localhost:3000 cargo test -p sprout-test-client --test e2e_media -- --ignored +//! RELAY_HTTP_URL=http://localhost:3000 cargo test -p buzz-test-client --test e2e_media -- --ignored //! ``` use std::time::Duration; diff --git a/crates/sprout-test-client/tests/e2e_media_extended.rs b/crates/buzz-test-client/tests/e2e_media_extended.rs similarity index 98% rename from crates/sprout-test-client/tests/e2e_media_extended.rs rename to crates/buzz-test-client/tests/e2e_media_extended.rs index 7832d0e93..bd7fcfbca 100644 --- a/crates/sprout-test-client/tests/e2e_media_extended.rs +++ b/crates/buzz-test-client/tests/e2e_media_extended.rs @@ -1,7 +1,7 @@ //! Extended media upload integration tests — auth edge cases, content validation, //! multi-format uploads, WebSocket imeta validation. //! -//! Run: cargo test -p sprout-test-client --test e2e_media_extended -- --ignored --nocapture +//! Run: cargo test -p buzz-test-client --test e2e_media_extended -- --ignored --nocapture use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; use nostr::{EventBuilder, JsonUtil, Keys, Kind, Tag, Timestamp}; @@ -480,7 +480,7 @@ async fn test_concurrent_upload_same_file() { #[tokio::test] #[ignore] async fn test_ws_valid_imeta() { - use sprout_test_client::SproutTestClient; + use buzz_test_client::BuzzTestClient; let keys = Keys::generate(); let pubkey_hex = keys.public_key().to_hex(); @@ -517,7 +517,7 @@ async fn test_ws_valid_imeta() { assert_eq!(resp.status(), 200); // Connect via WebSocket - let mut client = SproutTestClient::connect(&relay_ws_url(), &keys) + let mut client = BuzzTestClient::connect(&relay_ws_url(), &keys) .await .unwrap(); @@ -551,7 +551,7 @@ async fn test_ws_valid_imeta() { #[tokio::test] #[ignore] async fn test_ws_invalid_imeta_external_url() { - use sprout_test_client::SproutTestClient; + use buzz_test_client::BuzzTestClient; let keys = Keys::generate(); let pubkey_hex = keys.public_key().to_hex(); @@ -580,7 +580,7 @@ async fn test_ws_invalid_imeta_external_url() { let channel_id = channel_uuid.to_string(); let sha = "a".repeat(64); - let mut client = SproutTestClient::connect(&relay_ws_url(), &keys) + let mut client = BuzzTestClient::connect(&relay_ws_url(), &keys) .await .unwrap(); @@ -617,7 +617,7 @@ async fn test_ws_invalid_imeta_external_url() { #[tokio::test] #[ignore] async fn test_ws_invalid_imeta_missing_fields() { - use sprout_test_client::SproutTestClient; + use buzz_test_client::BuzzTestClient; let keys = Keys::generate(); let pubkey_hex = keys.public_key().to_hex(); @@ -646,7 +646,7 @@ async fn test_ws_invalid_imeta_missing_fields() { let channel_id = channel_uuid.to_string(); let sha = "b".repeat(64); - let mut client = SproutTestClient::connect(&relay_ws_url(), &keys) + let mut client = BuzzTestClient::connect(&relay_ws_url(), &keys) .await .unwrap(); diff --git a/crates/sprout-test-client/tests/e2e_media_video.rs b/crates/buzz-test-client/tests/e2e_media_video.rs similarity index 98% rename from crates/sprout-test-client/tests/e2e_media_video.rs rename to crates/buzz-test-client/tests/e2e_media_video.rs index 79d365e18..cea40264d 100644 --- a/crates/sprout-test-client/tests/e2e_media_video.rs +++ b/crates/buzz-test-client/tests/e2e_media_video.rs @@ -6,7 +6,7 @@ //! # Running //! //! ```text -//! cargo test -p sprout-test-client --test e2e_media_video -- --ignored --nocapture +//! cargo test -p buzz-test-client --test e2e_media_video -- --ignored --nocapture //! ``` use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; @@ -473,7 +473,7 @@ fn tiny_jpeg() -> Vec { #[tokio::test] #[ignore] async fn test_video_poster_imeta_accepted_via_ws() { - use sprout_test_client::SproutTestClient; + use buzz_test_client::BuzzTestClient; let client = http_client(); let keys = Keys::generate(); @@ -533,7 +533,7 @@ async fn test_video_poster_imeta_accepted_via_ws() { assert_eq!(poster_resp.status(), StatusCode::OK, "poster upload failed"); // 4. Send message with imeta referencing both video and poster - let mut ws = SproutTestClient::connect(&relay_ws_url(), &keys) + let mut ws = BuzzTestClient::connect(&relay_ws_url(), &keys) .await .unwrap(); @@ -572,7 +572,7 @@ async fn test_video_poster_imeta_accepted_via_ws() { #[tokio::test] #[ignore] async fn test_video_poster_imeta_rejects_video_as_poster() { - use sprout_test_client::SproutTestClient; + use buzz_test_client::BuzzTestClient; let client = http_client(); let keys = Keys::generate(); @@ -618,7 +618,7 @@ async fn test_video_poster_imeta_rejects_video_as_poster() { let video_size = video_desc["size"].as_u64().unwrap(); // 3. Send message with imeta `image` pointing to the VIDEO (not an image) - let mut ws = SproutTestClient::connect(&relay_ws_url(), &keys) + let mut ws = BuzzTestClient::connect(&relay_ws_url(), &keys) .await .unwrap(); diff --git a/crates/sprout-test-client/tests/e2e_mesh_llm.rs b/crates/buzz-test-client/tests/e2e_mesh_llm.rs similarity index 91% rename from crates/sprout-test-client/tests/e2e_mesh_llm.rs rename to crates/buzz-test-client/tests/e2e_mesh_llm.rs index c7779d782..5209fc627 100644 --- a/crates/sprout-test-client/tests/e2e_mesh_llm.rs +++ b/crates/buzz-test-client/tests/e2e_mesh_llm.rs @@ -1,11 +1,11 @@ //! End-to-end acceptance tests for the relay-hosted mesh-LLM feature. //! -//! These tests require a running sprout-relay with mesh embedded -//! (`SPROUT_MESH_ENABLED=true`, `SPROUT_REQUIRE_RELAY_MEMBERSHIP=true`) and, +//! These tests require a running buzz-relay with mesh embedded +//! (`BUZZ_MESH_ENABLED=true`, `BUZZ_REQUIRE_RELAY_MEMBERSHIP=true`) and, //! for the live-inference rows, two desktop mesh nodes (serve + client). //! All tests are `#[ignore]` by default — they need infra CI does not host //! (native llama, multi-node, model download). The deterministic trust -//! invariants are unit-tested in `sprout-relay` (`mesh_status_publisher`, +//! invariants are unit-tested in `buzz-relay` (`mesh_status_publisher`, //! `mesh_signaling`); this file is the opt-in full-stack acceptance layer. //! //! # Running (manual / runbook) @@ -13,8 +13,8 @@ //! ```text //! # 1. one-time local llama build (see docs/mesh-llm-local-build.md) //! # 2. start a mesh-enabled relay -//! SPROUT_MESH_ENABLED=true SPROUT_REQUIRE_RELAY_MEMBERSHIP=true \ -//! cargo run -p sprout-relay +//! BUZZ_MESH_ENABLED=true BUZZ_REQUIRE_RELAY_MEMBERSHIP=true \ +//! cargo run -p buzz-relay //! # 3. run the trust assertions (no GPU needed): //! RELAY_URL=ws://localhost:3000 \ //! cargo test --test e2e_mesh_llm trust -- --ignored --nocapture @@ -34,18 +34,18 @@ //! | 2 | non-member REQ for kind:30621 returns nothing | `trust_nonmember_read_denied` | — | //! | 3 | non-member iroh dial denied (NIP-98→membership) | runbook (needs iroh dial) | relay `mesh_signaling` admission units | //! | 4 | B's agent completes a chat against A's model over mesh | `live_agent_completes_chat_over_mesh` | runbook | -//! | 5 | dropped member → typed auth failure reaches lastError | runbook (desktop harness) | sprout-agent `-32001` unit | +//! | 5 | dropped member → typed auth failure reaches lastError | runbook (desktop harness) | buzz-agent `-32001` unit | //! | 6 | split: model too big → 2 serve nodes → chat completes | `live_split_model_completes` | runbook | use std::time::Duration; +use buzz_test_client::BuzzTestClient; use nostr::{Filter, Keys, Kind}; -use sprout_test_client::SproutTestClient; -/// Sprout's relay-owned mesh status kind (must match `sprout_core::kind`). +/// Buzz's relay-owned mesh status kind (must match `buzz_core::kind`). const KIND_MESH_LLM_RELAY_STATUS: u16 = 30621; -const MESH_STATUS_D_TAG: &str = "sprout-relay-mesh"; -const MESH_STATUS_TYPE: &str = "sprout-mesh-status"; +const MESH_STATUS_D_TAG: &str = "buzz-relay-mesh"; +const MESH_STATUS_TYPE: &str = "buzz-mesh-status"; fn relay_url() -> String { std::env::var("RELAY_URL").unwrap_or_else(|_| "ws://localhost:3000".to_string()) @@ -100,7 +100,7 @@ async fn trust_member_reads_mesh_status() { let Some(member) = keys_from_env("MEMBER_NSEC") else { return; }; - let mut client = SproutTestClient::connect(&url, &member) + let mut client = BuzzTestClient::connect(&url, &member) .await .expect("member connect+auth"); @@ -165,7 +165,7 @@ async fn trust_member_reads_mesh_status() { /// Assertion 2: a valid Nostr identity that is NOT a relay member gets nothing /// back for a kind:30621 REQ — membership gates the read. /// -/// Requires a relay with `SPROUT_REQUIRE_RELAY_MEMBERSHIP=true` and a published +/// Requires a relay with `BUZZ_REQUIRE_RELAY_MEMBERSHIP=true` and a published /// status event that members can see (paired with assertion 1). #[tokio::test] #[ignore] @@ -174,7 +174,7 @@ async fn trust_nonmember_read_denied() { let Some(stranger) = keys_from_env("STRANGER_NSEC") else { return; }; - let mut client = match SproutTestClient::connect(&url, &stranger).await { + let mut client = match BuzzTestClient::connect(&url, &stranger).await { Ok(c) => c, // A closed relay may refuse NIP-42 auth for a non-member outright — // that is also a valid "denied" outcome. @@ -205,7 +205,7 @@ async fn trust_nonmember_read_denied() { // ── (4) the demo: B's agent completes a chat against A's model over the mesh ── /// Assertion 4 (the headline demo): with desktop A serving a model and desktop -/// B running a mesh client + a launched sprout-agent pointed at B's local +/// B running a mesh client + a launched buzz-agent pointed at B's local /// `:9337/v1`, a chat completion returns a non-empty response routed over the /// mesh to A's GPU. /// @@ -271,7 +271,7 @@ async fn live_agent_completes_chat_over_mesh() { /// Assertion 6 (split): a model too large for one node + two serve nodes in the /// same mesh → mesh auto-splits → the same chat (assertion 4) completes via the -/// split route. Auto-split is mesh runtime behavior (no Sprout code); this row +/// split route. Auto-split is mesh runtime behavior (no Buzz code); this row /// only verifies two serve desktops in one mesh produce a working split. /// /// Runbook only — needs a known too-large-for-one-node fixture + 2 serve nodes. diff --git a/crates/sprout-test-client/tests/e2e_nostr_interop.rs b/crates/buzz-test-client/tests/e2e_nostr_interop.rs similarity index 95% rename from crates/sprout-test-client/tests/e2e_nostr_interop.rs rename to crates/buzz-test-client/tests/e2e_nostr_interop.rs index 86228e96a..48142c157 100644 --- a/crates/sprout-test-client/tests/e2e_nostr_interop.rs +++ b/crates/buzz-test-client/tests/e2e_nostr_interop.rs @@ -21,8 +21,8 @@ use std::time::Duration; +use buzz_test_client::{BuzzTestClient, RelayMessage, TestClientError}; use nostr::{Alphabet, EventBuilder, Filter, Keys, Kind, SingleLetterTag, Tag}; -use sprout_test_client::{RelayMessage, SproutTestClient, TestClientError}; // ── Helpers ─────────────────────────────────────────────────────────────────── @@ -192,9 +192,7 @@ async fn test_nip50_search_returns_results_and_eose() { let unique_token = format!("searchtoken_{}", uuid::Uuid::new_v4().simple()); let content = format!("Hello world {unique_token}"); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let ok = client .send_text_message(&keys, &channel, &content, 9) @@ -270,9 +268,7 @@ async fn test_nip50_search_mixed_filters_rejected() { let keys = Keys::generate(); let channel = create_test_channel(&keys).await; - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let sid = sub_id("nip50-mixed"); @@ -332,9 +328,7 @@ async fn test_nip50_search_empty_results() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let sid = sub_id("nip50-empty"); // Must include kinds to avoid triggering P_GATED_KINDS check (wildcard @@ -376,9 +370,7 @@ async fn test_nip10_thread_reply_creates_metadata() { // Send root message via REST. let root_event_id = send_rest_message(&keys, &channel, "root message for NIP-10 test").await; - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); // Build reply event with NIP-10 e-tag. let h_tag = Tag::parse(["h", &channel]).expect("h tag"); @@ -433,9 +425,7 @@ async fn test_nip10_unknown_parent_rejected() { let keys = Keys::generate(); let channel = create_test_channel(&keys).await; - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); // Use a random 32-byte hex as a nonexistent parent ID. let fake_parent_id = hex::encode([0xdeu8; 32]); @@ -479,9 +469,7 @@ async fn test_nip10_root_mismatch_rejected() { // Use a different random ID as the claimed root. let wrong_root_id = hex::encode([0xabu8; 32]); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let h_tag = Tag::parse(["h", &channel]).expect("h tag"); // wrong_root as "root" marker, real_parent as "reply" marker — mismatch. @@ -522,7 +510,7 @@ async fn test_nip17_gift_wrap_accepted() { let auth_keys = Keys::generate(); let recipient_keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &auth_keys) + let mut client = BuzzTestClient::connect(&url, &auth_keys) .await .expect("connect"); @@ -554,9 +542,7 @@ async fn test_nip17_gift_wrap_requires_p_filter() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let sid = sub_id("nip17-no-p"); // No #p filter — should be rejected. @@ -614,7 +600,7 @@ async fn test_nip17_gift_wrap_recipient_receives() { let b_pubkey_hex = keys_b.public_key().to_hex(); // Connect B first and subscribe. - let mut client_b = SproutTestClient::connect(&url, &keys_b) + let mut client_b = BuzzTestClient::connect(&url, &keys_b) .await .expect("client B connect"); @@ -636,7 +622,7 @@ async fn test_nip17_gift_wrap_recipient_receives() { .expect("client B EOSE"); // Connect A and send gift wrap addressed to B. - let mut client_a = SproutTestClient::connect(&url, &keys_a) + let mut client_a = BuzzTestClient::connect(&url, &keys_a) .await .expect("client A connect"); @@ -700,7 +686,7 @@ async fn test_dm_discovery_events_emitted() { let b_pubkey_hex = keys_b.public_key().to_hex(); // Connect A and subscribe to discovery + membership events BEFORE creating the DM. - let mut client_a = SproutTestClient::connect(&url, &keys_a) + let mut client_a = BuzzTestClient::connect(&url, &keys_a) .await .expect("client A connect"); @@ -834,9 +820,7 @@ async fn test_nip10_thread_reply_not_in_top_level() { let root_event_id = send_rest_message(&keys, &channel, &root_content).await; // Send reply via WS with NIP-10 e-tag. - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let reply_content = format!("reply-hidden-{}", uuid::Uuid::new_v4()); let h_tag = Tag::parse(["h", &channel]).expect("h tag"); @@ -899,7 +883,7 @@ async fn test_nip17_gift_wrap_not_searchable() { let keys_b = Keys::generate(); let channel = create_test_channel(&keys_a).await; - let mut client = SproutTestClient::connect(&url, &keys_a) + let mut client = BuzzTestClient::connect(&url, &keys_a) .await .expect("connect"); @@ -930,8 +914,7 @@ async fn test_nip17_gift_wrap_not_searchable() { // 3. Query Typesense DIRECTLY — bypasses all relay-level filtering. let ts_url = std::env::var("TYPESENSE_URL").unwrap_or_else(|_| "http://localhost:8108".to_string()); - let ts_key = - std::env::var("TYPESENSE_API_KEY").unwrap_or_else(|_| "sprout_dev_key".to_string()); + let ts_key = std::env::var("TYPESENSE_API_KEY").unwrap_or_else(|_| "buzz_dev_key".to_string()); let http = reqwest::Client::new(); let resp = http @@ -999,9 +982,7 @@ async fn test_nip50_search_relevance_order() { // Wait for Typesense indexing. tokio::time::sleep(Duration::from_secs(3)).await; - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let sid = sub_id("nip50-relevance"); let query = format!("{prefix} alpha bravo charlie"); @@ -1052,9 +1033,7 @@ async fn test_historical_req_dedup_preserves_or_semantics() { let content = format!("dedup-or-{}", uuid::Uuid::new_v4()); let event_id = send_rest_message(&keys, &channel, &content).await; - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); // Generate a random wrong author key. let wrong_author = Keys::generate(); @@ -1108,9 +1087,7 @@ async fn test_empty_kinds_returns_zero_events() { // Send a message so there IS data in the channel. send_rest_message(&keys, &channel, "should not appear").await; - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let sid = sub_id("empty-kinds"); // kinds:[] = match nothing per NIP-01. @@ -1143,7 +1120,7 @@ async fn test_empty_kinds_returns_zero_events() { /// (kind:30622, queried by `#p` since snapshots are `#p`-gated to their owner). /// Returns `None` if no snapshot exists yet. async fn read_snapshot_event( - client: &mut SproutTestClient, + client: &mut BuzzTestClient, viewer_hex: &str, ) -> Option { let sid = sub_id("nipdv-snapshot"); @@ -1169,7 +1146,7 @@ async fn read_snapshot_event( } /// Helper: the set of hidden DM channel ids from the viewer's latest snapshot. -async fn read_hidden_dms(client: &mut SproutTestClient, viewer_hex: &str) -> Vec { +async fn read_hidden_dms(client: &mut BuzzTestClient, viewer_hex: &str) -> Vec { match read_snapshot_event(client, viewer_hex).await { None => Vec::new(), Some(ev) => ev @@ -1200,7 +1177,7 @@ async fn test_nipdv_hide_then_reopen_updates_snapshot() { // A opens a DM with B. let channel_id = create_dm(&keys_a, &b_pubkey_hex).await; - let mut client_a = SproutTestClient::connect(&url, &keys_a) + let mut client_a = BuzzTestClient::connect(&url, &keys_a) .await .expect("client A connect"); @@ -1267,7 +1244,7 @@ async fn test_nipdv_same_second_reopen_supersedes_hide() { let channel_id = create_dm(&keys_a, &b_pubkey_hex).await; - let mut client_a = SproutTestClient::connect(&url, &keys_a) + let mut client_a = BuzzTestClient::connect(&url, &keys_a) .await .expect("client A connect"); @@ -1381,7 +1358,7 @@ async fn test_nipdv_two_viewers_independent_snapshots() { post_signed_event(&keys_b, 41012, vec![Tag::parse(["h", &dm_b]).unwrap()]).await; // A's snapshot must still list A's hidden DM (B's write must not clobber it). - let mut client_a = SproutTestClient::connect(&url, &keys_a) + let mut client_a = BuzzTestClient::connect(&url, &keys_a) .await .expect("client A connect"); let a_hidden = read_hidden_dms(&mut client_a, &a_pubkey_hex).await; @@ -1396,7 +1373,7 @@ async fn test_nipdv_two_viewers_independent_snapshots() { client_a.disconnect().await.expect("disconnect A"); // B's snapshot lists only B's hidden DM. - let mut client_b = SproutTestClient::connect(&url, &keys_b) + let mut client_b = BuzzTestClient::connect(&url, &keys_b) .await .expect("client B connect"); let b_hidden = read_hidden_dms(&mut client_b, &b_pubkey_hex).await; @@ -1432,7 +1409,7 @@ async fn test_nipdv_ws_req_rejects_third_party() { .await; // B subscribes for A's snapshot over WS — must be CLOSED, never EVENT. - let mut client_b = SproutTestClient::connect(&url, &keys_b) + let mut client_b = BuzzTestClient::connect(&url, &keys_b) .await .expect("client B connect"); let sid = sub_id("nipdv-cross-ws"); @@ -1494,7 +1471,7 @@ async fn test_nipdv_ids_query_rejects_third_party() { .await; // A reads its own snapshot to learn its event id. - let mut client_a = SproutTestClient::connect(&url, &keys_a) + let mut client_a = BuzzTestClient::connect(&url, &keys_a) .await .expect("client A connect"); let snapshot = read_snapshot_event(&mut client_a, &a_pubkey_hex) @@ -1550,7 +1527,7 @@ async fn test_nipdv_explicit_kind_query_forbidden_for_third_party() { ) .await; - let mut client_a = SproutTestClient::connect(&url, &keys_a) + let mut client_a = BuzzTestClient::connect(&url, &keys_a) .await .expect("client A connect"); let snapshot = read_snapshot_event(&mut client_a, &a_pubkey_hex) @@ -1600,7 +1577,7 @@ async fn test_nipdv_search_rejects_third_party() { ) .await; - let mut client_a = SproutTestClient::connect(&url, &keys_a) + let mut client_a = BuzzTestClient::connect(&url, &keys_a) .await .expect("client A connect"); let snapshot = read_snapshot_event(&mut client_a, &a_pubkey_hex) @@ -1614,7 +1591,7 @@ async fn test_nipdv_search_rejects_third_party() { // B issues a kindless search filter carrying A's snapshot id — the bypass // shape. Must return zero results, not A's hidden set. - let mut client_b = SproutTestClient::connect(&url, &keys_b) + let mut client_b = BuzzTestClient::connect(&url, &keys_b) .await .expect("client B connect"); let sid = sub_id("nipdv-search-bypass"); diff --git a/crates/sprout-test-client/tests/e2e_relay.rs b/crates/buzz-test-client/tests/e2e_relay.rs similarity index 95% rename from crates/sprout-test-client/tests/e2e_relay.rs rename to crates/buzz-test-client/tests/e2e_relay.rs index ce58b3dbd..c377998c9 100644 --- a/crates/sprout-test-client/tests/e2e_relay.rs +++ b/crates/buzz-test-client/tests/e2e_relay.rs @@ -1,4 +1,4 @@ -//! End-to-end integration tests for the Sprout relay. +//! End-to-end integration tests for the Buzz relay. //! //! These tests require a running relay instance. By default they are marked //! `#[ignore]` so that `cargo test` does not fail in CI when the relay is not @@ -20,8 +20,8 @@ use std::time::Duration; +use buzz_test_client::{BuzzTestClient, RelayMessage, TestClientError}; use nostr::{Alphabet, EventBuilder, Filter, Keys, Kind, SingleLetterTag, Tag}; -use sprout_test_client::{RelayMessage, SproutTestClient, TestClientError}; fn relay_url() -> String { std::env::var("RELAY_URL").unwrap_or_else(|_| "ws://localhost:3000".to_string()) @@ -85,7 +85,7 @@ async fn test_connect_and_authenticate() { let url = relay_url(); let keys = Keys::generate(); - let client = SproutTestClient::connect(&url, &keys) + let client = BuzzTestClient::connect(&url, &keys) .await .expect("should connect and authenticate"); @@ -102,7 +102,7 @@ async fn test_send_event_and_receive_via_subscription() { let keys_b = Keys::generate(); let channel = create_test_channel(&keys_a).await; - let mut client_a = SproutTestClient::connect(&url, &keys_a) + let mut client_a = BuzzTestClient::connect(&url, &keys_a) .await .expect("client A connect"); @@ -122,7 +122,7 @@ async fn test_send_event_and_receive_via_subscription() { .await .expect("client A EOSE"); - let mut client_b = SproutTestClient::connect(&url, &keys_b) + let mut client_b = BuzzTestClient::connect(&url, &keys_b) .await .expect("client B connect"); @@ -161,9 +161,7 @@ async fn test_subscription_filters_by_kind() { let keys = Keys::generate(); let channel = create_test_channel(&keys).await; - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let sid = sub_id("filter-kind"); let filter = Filter::new() @@ -231,9 +229,7 @@ async fn test_close_subscription_stops_delivery() { let keys = Keys::generate(); let channel = create_test_channel(&keys).await; - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let sid = sub_id("close-sub"); let filter = Filter::new() @@ -284,7 +280,7 @@ async fn test_unauthenticated_rejected() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect_unauthenticated(&url) + let mut client = BuzzTestClient::connect_unauthenticated(&url) .await .expect("connect unauthenticated"); @@ -324,8 +320,8 @@ async fn test_multiple_concurrent_clients() { let keys: Vec = (0..3).map(|_| Keys::generate()).collect(); let channel = create_test_channel(&keys[0]).await; - let mut clients: Vec = - futures_util::future::try_join_all(keys.iter().map(|k| SproutTestClient::connect(&url, k))) + let mut clients: Vec = + futures_util::future::try_join_all(keys.iter().map(|k| BuzzTestClient::connect(&url, k))) .await .expect("all clients connect"); @@ -380,9 +376,7 @@ async fn test_stored_events_returned_before_eose() { let keys = Keys::generate(); let channel = create_test_channel(&keys).await; - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let content = format!("stored-{}", uuid::Uuid::new_v4()); let ok = client @@ -424,9 +418,7 @@ async fn test_ephemeral_event_not_stored() { let keys = Keys::generate(); let channel = create_test_channel(&keys).await; - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let ok = client .send_text_message(&keys, &channel, "ephemeral content", ephemeral_kind) @@ -467,9 +459,7 @@ async fn test_ephemeral_event_not_stored() { async fn test_auth_event_kind_rejected() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let relay_url_parsed: nostr::RelayUrl = url.parse().unwrap(); let auth_event = nostr::EventBuilder::auth("fake-challenge", relay_url_parsed) @@ -503,9 +493,7 @@ async fn test_auth_event_kind_rejected() { async fn test_subscription_limit_enforced() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); // Open 1024 subscriptions (the relay's MAX_SUBSCRIPTIONS). for i in 0..1024 { @@ -621,7 +609,7 @@ async fn test_pubkey_mismatch_rejected() { let keys_b = Keys::generate(); let channel = create_test_channel(&keys_a).await; - let mut client = SproutTestClient::connect(&url, &keys_a) + let mut client = BuzzTestClient::connect(&url, &keys_a) .await .expect("connect as keys_a"); @@ -646,9 +634,7 @@ async fn test_eose_sent_for_empty_subscription() { let keys = Keys::generate(); let channel = create_test_channel(&keys).await; - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let sid = sub_id("empty-eose"); let filter = Filter::new() @@ -705,9 +691,7 @@ async fn test_kind0_nip05_sync() { let valid_handle = format!("{}@{}", unique_name, relay_domain); // Step 1: Connect and publish kind:0 with a valid nip05 handle. - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let kind0_content = serde_json::json!({ "display_name": "Kind0 Test User", @@ -839,7 +823,7 @@ async fn test_nip29_put_user_default_policy_allows() { let channel_id = create_test_channel(&channel_owner_keys).await; // Connect as channel_owner. - let mut ws = SproutTestClient::connect(&url, &channel_owner_keys) + let mut ws = BuzzTestClient::connect(&url, &channel_owner_keys) .await .expect("connect as channel_owner"); @@ -890,7 +874,7 @@ async fn test_nip29_put_user_nobody_blocks() { let channel_id = create_test_channel(&channel_owner_keys).await; // Connect as channel_owner. - let mut ws = SproutTestClient::connect(&url, &channel_owner_keys) + let mut ws = BuzzTestClient::connect(&url, &channel_owner_keys) .await .expect("connect as channel_owner"); @@ -944,7 +928,7 @@ async fn test_nip29_put_user_self_add_bypasses_policy() { let channel_id = create_test_channel(&agent_keys).await; // Connect as agent. - let mut ws = SproutTestClient::connect(&url, &agent_keys) + let mut ws = BuzzTestClient::connect(&url, &agent_keys) .await .expect("connect as agent"); @@ -995,7 +979,7 @@ async fn test_nip29_put_user_owner_only_blocks() { let channel_id = create_test_channel(&channel_owner_keys).await; // Connect as channel_owner. - let mut ws = SproutTestClient::connect(&url, &channel_owner_keys) + let mut ws = BuzzTestClient::connect(&url, &channel_owner_keys) .await .expect("connect as channel_owner"); @@ -1032,7 +1016,7 @@ async fn test_nip29_standard_client_flow() { let keys = Keys::generate(); let channel_id = create_test_channel(&keys).await; - let mut client = SproutTestClient::connect(&url, &keys) + let mut client = BuzzTestClient::connect(&url, &keys) .await .expect("connect and authenticate via NIP-42"); @@ -1213,9 +1197,7 @@ async fn test_membership_notification_kind_rejected() { let keys = Keys::generate(); let channel_id = create_test_channel(&keys).await; - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let p_tag = Tag::parse(["p", &keys.public_key().to_hex()]).expect("p tag"); let h_tag = Tag::parse(["h", &channel_id]).expect("h tag"); @@ -1254,7 +1236,7 @@ async fn test_membership_notification_emitted_on_add() { let agent_pubkey_hex = agent_keys.public_key().to_hex(); // Connect as agent — NIP-42 auth establishes the authenticated pubkey. - let mut agent_client = SproutTestClient::connect(&url, &agent_keys) + let mut agent_client = BuzzTestClient::connect(&url, &agent_keys) .await .expect("connect as agent"); @@ -1355,9 +1337,7 @@ async fn test_membership_notification_requires_p_filter() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let sid = sub_id("no-p-filter"); let filter = Filter::new().kinds(vec![Kind::Custom(44100), Kind::Custom(44101)]); @@ -1408,9 +1388,7 @@ async fn test_membership_notification_wildcard_filter_rejected() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let sid = sub_id("wildcard-filter"); // Empty filter — no kinds, no #p — can match kind:44100/44101. @@ -1465,7 +1443,7 @@ async fn test_membership_notification_requires_own_p_filter() { let keys_b_pubkey_hex = keys_b.public_key().to_hex(); // Connect as keys_a. - let mut client = SproutTestClient::connect(&url, &keys_a) + let mut client = BuzzTestClient::connect(&url, &keys_a) .await .expect("connect as keys_a"); @@ -1528,7 +1506,7 @@ async fn test_membership_notification_emitted_on_remove() { let agent_pubkey_hex = agent_keys.public_key().to_hex(); // Connect as agent — NIP-42 auth establishes the authenticated pubkey. - let mut agent_client = SproutTestClient::connect(&url, &agent_keys) + let mut agent_client = BuzzTestClient::connect(&url, &agent_keys) .await .expect("connect as agent"); @@ -1677,7 +1655,7 @@ async fn test_membership_notification_multi_p_rejected() { let keys_b_pubkey_hex = keys_b.public_key().to_hex(); // Connect as keys_a. - let mut client = SproutTestClient::connect(&url, &keys_a) + let mut client = BuzzTestClient::connect(&url, &keys_a) .await .expect("connect as keys_a"); @@ -1739,9 +1717,7 @@ async fn test_membership_notification_mixed_filter_rejected() { let keys = Keys::generate(); let channel_id = create_test_channel(&keys).await; - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let sid = sub_id("mixed-filter"); // Filter 1: has #h + membership kinds (would skip per-filter #h check) @@ -1793,7 +1769,7 @@ async fn test_membership_notification_mixed_filter_rejected() { // ─── Private channel membership permission tests ─────────────────────────────── /// Create a private channel over WebSocket and return the channel UUID. -async fn create_private_channel_ws(client: &mut SproutTestClient, keys: &Keys) -> String { +async fn create_private_channel_ws(client: &mut BuzzTestClient, keys: &Keys) -> String { let channel_uuid = uuid::Uuid::new_v4().to_string(); let channel_name = format!("relay-e2e-private-{}", channel_uuid); @@ -1821,7 +1797,7 @@ async fn create_private_channel_ws(client: &mut SproutTestClient, keys: &Keys) - /// Submit a kind:9000 PUT_USER event over WebSocket. async fn add_member_ws( - client: &mut SproutTestClient, + client: &mut BuzzTestClient, channel_id: &str, target_pubkey_hex: &str, signer: &Keys, @@ -1839,7 +1815,7 @@ async fn add_member_ws( /// Submit a kind:9000 PUT_USER event with a role tag over WebSocket. async fn add_member_with_role_ws( - client: &mut SproutTestClient, + client: &mut BuzzTestClient, channel_id: &str, target_pubkey_hex: &str, role: &str, @@ -1870,7 +1846,7 @@ async fn test_private_channel_any_member_can_invite() { let invitee_keys = Keys::generate(); // Connect as owner and create a private channel. - let mut owner_client = SproutTestClient::connect(&url, &owner_keys) + let mut owner_client = BuzzTestClient::connect(&url, &owner_keys) .await .expect("connect as owner"); let channel_id = create_private_channel_ws(&mut owner_client, &owner_keys).await; @@ -1886,7 +1862,7 @@ async fn test_private_channel_any_member_can_invite() { assert!(accepted, "owner should add member, got: {msg}"); // Connect as the regular member. - let mut member_client = SproutTestClient::connect(&url, &member_keys) + let mut member_client = BuzzTestClient::connect(&url, &member_keys) .await .expect("connect as member"); @@ -1917,13 +1893,13 @@ async fn test_private_channel_non_member_cannot_invite() { let target_keys = Keys::generate(); // Owner creates a private channel. - let mut owner_client = SproutTestClient::connect(&url, &owner_keys) + let mut owner_client = BuzzTestClient::connect(&url, &owner_keys) .await .expect("connect as owner"); let channel_id = create_private_channel_ws(&mut owner_client, &owner_keys).await; // Connect as outsider (not a member of the channel). - let mut outsider_client = SproutTestClient::connect(&url, &outsider_keys) + let mut outsider_client = BuzzTestClient::connect(&url, &outsider_keys) .await .expect("connect as outsider"); @@ -1961,7 +1937,7 @@ async fn test_private_channel_member_cannot_grant_admin() { let target_keys = Keys::generate(); // Owner creates a private channel and adds a regular member. - let mut owner_client = SproutTestClient::connect(&url, &owner_keys) + let mut owner_client = BuzzTestClient::connect(&url, &owner_keys) .await .expect("connect as owner"); let channel_id = create_private_channel_ws(&mut owner_client, &owner_keys).await; @@ -1976,7 +1952,7 @@ async fn test_private_channel_member_cannot_grant_admin() { assert!(accepted, "owner should add member, got: {msg}"); // Connect as the regular member. - let mut member_client = SproutTestClient::connect(&url, &member_keys) + let mut member_client = BuzzTestClient::connect(&url, &member_keys) .await .expect("connect as member"); diff --git a/crates/sprout-test-client/tests/e2e_rest_api.rs b/crates/buzz-test-client/tests/e2e_rest_api.rs similarity index 99% rename from crates/sprout-test-client/tests/e2e_rest_api.rs rename to crates/buzz-test-client/tests/e2e_rest_api.rs index cf90003cd..ef9755b45 100644 --- a/crates/sprout-test-client/tests/e2e_rest_api.rs +++ b/crates/buzz-test-client/tests/e2e_rest_api.rs @@ -1,4 +1,4 @@ -//! E2E tests for the Sprout REST API. +//! E2E tests for the Buzz REST API. //! //! These tests require a running relay instance with `require_auth_token=false` //! (dev mode). By default they are marked `#[ignore]` so that `cargo test` @@ -9,7 +9,7 @@ //! Start the relay, then run: //! //! ```text -//! RELAY_URL=ws://localhost:3001 cargo test -p sprout-test-client --test e2e_rest_api -- --ignored +//! RELAY_URL=ws://localhost:3001 cargo test -p buzz-test-client --test e2e_rest_api -- --ignored //! ``` //! //! # Auth @@ -27,9 +27,9 @@ use std::time::Duration; +use buzz_test_client::BuzzTestClient; use nostr::{EventBuilder, Keys, Kind, Tag}; use reqwest::Client; -use sprout_test_client::SproutTestClient; // ── Helpers ─────────────────────────────────────────────────────────────────── @@ -393,7 +393,7 @@ async fn test_search_returns_indexed_event() { let unique_token = format!("e2e-search-{}", uuid::Uuid::new_v4().simple()); let content = format!("E2E REST search test marker: {unique_token}"); - let mut ws_client = SproutTestClient::connect(&ws_url, &keys) + let mut ws_client = BuzzTestClient::connect(&ws_url, &keys) .await .expect("WebSocket connect failed"); @@ -483,7 +483,7 @@ async fn test_presence_set_and_query() { let pubkey_hex = keys.public_key().to_hex(); let ws_url = relay_ws_url(); - let mut ws_client = SproutTestClient::connect(&ws_url, &keys) + let mut ws_client = BuzzTestClient::connect(&ws_url, &keys) .await .expect("WebSocket connect failed"); @@ -889,7 +889,7 @@ async fn test_feed_returns_activity() { let unique_token = format!("e2e-feed-{}", uuid::Uuid::new_v4().simple()); let content = format!("E2E feed test: {unique_token}"); - let mut ws_client = SproutTestClient::connect(&ws_url, &keys) + let mut ws_client = BuzzTestClient::connect(&ws_url, &keys) .await .expect("WebSocket connect failed"); diff --git a/crates/sprout-test-client/tests/e2e_tokens.rs b/crates/buzz-test-client/tests/e2e_tokens.rs similarity index 98% rename from crates/sprout-test-client/tests/e2e_tokens.rs rename to crates/buzz-test-client/tests/e2e_tokens.rs index 7e24cd9cc..5808e0ee2 100644 --- a/crates/sprout-test-client/tests/e2e_tokens.rs +++ b/crates/buzz-test-client/tests/e2e_tokens.rs @@ -9,7 +9,7 @@ //! Start the relay, then run: //! //! ```text -//! RELAY_URL=ws://localhost:3000 cargo test -p sprout-test-client --test e2e_tokens -- --ignored +//! RELAY_URL=ws://localhost:3000 cargo test -p buzz-test-client --test e2e_tokens -- --ignored //! ``` //! //! # Auth @@ -52,7 +52,7 @@ fn http_client() -> Client { /// Build a `Authorization: Nostr ` header value for NIP-98 HTTP Auth. /// /// Uses kind 27235 (`Kind::HttpAuth`) with `u`, `method`, and `payload` tags -/// following the pattern in `sprout-auth/src/nip98.rs`. +/// following the pattern in `buzz-auth/src/nip98.rs`. fn build_nip98_header(keys: &Keys, url: &str, method: &str, body: &[u8]) -> String { let payload_hash = hex::encode(Sha256::digest(body)); @@ -656,25 +656,25 @@ async fn test_bearer_token_scope_escalation_blocked() { /// Exhaust the per-pubkey rate limit, then verify the next mint returns 429. /// -/// The relay reads `SPROUT_MINT_RATE_LIMIT` (default 50). This test reads the +/// The relay reads `BUZZ_MINT_RATE_LIMIT` (default 50). This test reads the /// same env var so it stays in sync. For fast test runs, start the relay with -/// `SPROUT_MINT_RATE_LIMIT=5`. +/// `BUZZ_MINT_RATE_LIMIT=5`. /// -/// **Requires `SPROUT_MINT_RATE_LIMIT ≤ 10`** — the DB enforces a hard 10-token +/// **Requires `BUZZ_MINT_RATE_LIMIT ≤ 10`** — the DB enforces a hard 10-token /// cap that fires before the rate limiter when the limit is higher. The test /// skips gracefully if the limit is too high. #[tokio::test] #[ignore] async fn test_rate_limit() { - let limit: usize = std::env::var("SPROUT_MINT_RATE_LIMIT") + let limit: usize = std::env::var("BUZZ_MINT_RATE_LIMIT") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(50); if limit > 10 { eprintln!( - "SKIP test_rate_limit: SPROUT_MINT_RATE_LIMIT={limit} exceeds the 10-token DB cap. \ - Set SPROUT_MINT_RATE_LIMIT=5 (or ≤10) on both relay and test runner to exercise this." + "SKIP test_rate_limit: BUZZ_MINT_RATE_LIMIT={limit} exceeds the 10-token DB cap. \ + Set BUZZ_MINT_RATE_LIMIT=5 (or ≤10) on both relay and test runner to exercise this." ); return; } diff --git a/crates/sprout-test-client/tests/e2e_user_status.rs b/crates/buzz-test-client/tests/e2e_user_status.rs similarity index 94% rename from crates/sprout-test-client/tests/e2e_user_status.rs rename to crates/buzz-test-client/tests/e2e_user_status.rs index 40ce363d7..76437c0b2 100644 --- a/crates/sprout-test-client/tests/e2e_user_status.rs +++ b/crates/buzz-test-client/tests/e2e_user_status.rs @@ -20,8 +20,8 @@ use std::time::Duration; +use buzz_test_client::BuzzTestClient; use nostr::{Alphabet, EventBuilder, Filter, Keys, Kind, SingleLetterTag, Tag, Timestamp}; -use sprout_test_client::SproutTestClient; const KIND_USER_STATUS: u16 = 30315; @@ -56,9 +56,7 @@ fn build_user_status_event( async fn test_user_status_accepted() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let event = build_user_status_event(&keys, "general", "Working on NIP-38 support", vec![]); @@ -78,9 +76,7 @@ async fn test_user_status_accepted() { async fn test_user_status_retrievable() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let d_tag = format!("retrieve-{}", uuid::Uuid::new_v4().simple()); let event = build_user_status_event(&keys, &d_tag, "Currently online", vec![]); @@ -118,9 +114,7 @@ async fn test_user_status_retrievable() { async fn test_user_status_nip33_replacement() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let d_tag = format!("replace-{}", uuid::Uuid::new_v4().simple()); @@ -171,9 +165,7 @@ async fn test_user_status_nip33_replacement() { async fn test_user_status_multiple_d_tags_coexist() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let general_d = format!("general-{}", uuid::Uuid::new_v4().simple()); let music_d = format!("music-{}", uuid::Uuid::new_v4().simple()); @@ -223,9 +215,7 @@ async fn test_user_status_multiple_d_tags_coexist() { async fn test_user_status_stale_write_rejected() { let url = relay_url(); let keys = Keys::generate(); - let mut client = SproutTestClient::connect(&url, &keys) - .await - .expect("connect"); + let mut client = BuzzTestClient::connect(&url, &keys).await.expect("connect"); let d_tag = format!("stale-{}", uuid::Uuid::new_v4().simple()); diff --git a/crates/sprout-test-client/tests/e2e_workflows.rs b/crates/buzz-test-client/tests/e2e_workflows.rs similarity index 97% rename from crates/sprout-test-client/tests/e2e_workflows.rs rename to crates/buzz-test-client/tests/e2e_workflows.rs index 1115b8af9..c69b5a249 100644 --- a/crates/sprout-test-client/tests/e2e_workflows.rs +++ b/crates/buzz-test-client/tests/e2e_workflows.rs @@ -1,4 +1,4 @@ -//! E2E tests for the Sprout workflow engine. +//! E2E tests for the Buzz workflow engine. //! //! These tests require a running relay instance with `require_auth_token=false` //! (dev mode). By default they are marked `#[ignore]` so that `cargo test` @@ -9,7 +9,7 @@ //! Start the relay, then run: //! //! ```text -//! RELAY_URL=ws://localhost:3001 cargo test -p sprout-test-client --test e2e_workflows -- --ignored +//! RELAY_URL=ws://localhost:3001 cargo test -p buzz-test-client --test e2e_workflows -- --ignored //! ``` //! //! # Auth @@ -55,13 +55,13 @@ const CHANNEL_GENERAL: &str = "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50"; /// /// Workflow creation requires the owner pubkey to exist in `users` (FK constraint). /// The relay does not auto-create users on first auth — users are created via -/// `sprout-admin mint-token` or WebSocket metadata events. This pubkey is present +/// `buzz-admin mint-token` or WebSocket metadata events. This pubkey is present /// in the dev database after the initial seed. /// /// If tests fail with 500 "FK constraint fails", run: /// ``` -/// DATABASE_URL=postgres://sprout:sprout_dev@localhost:5432/sprout \ -/// cargo run -p sprout-admin -- mint-token --name e2e-test --scopes messages:read \ +/// DATABASE_URL=postgres://buzz:buzz_dev@localhost:5432/buzz \ +/// cargo run -p buzz-admin -- mint-token --name e2e-test --scopes messages:read \ /// --pubkey 0b5c83782cf123e698131ac976179f8366224e03db932c9da0074512aed2388d /// ``` const SEEDED_PUBKEY: &str = "0b5c83782cf123e698131ac976179f8366224e03db932c9da0074512aed2388d"; @@ -318,8 +318,8 @@ async fn test_trigger_workflow_and_check_run() { #[tokio::test] #[ignore = "requires running relay"] async fn test_event_driven_workflow_execution() { + use buzz_test_client::BuzzTestClient; use nostr::{Kind, Tag}; - use sprout_test_client::SproutTestClient; let client = http_client(); let pubkey_hex: &str = SEEDED_PUBKEY; @@ -345,7 +345,7 @@ steps: // ── Step 2: Connect via WebSocket and send a kind:9 message ─────────── // Use fresh keys for the sender (channel is open, no auth required to post). let sender_keys = Keys::generate(); - let mut ws_client = SproutTestClient::connect(&relay_ws_url(), &sender_keys) + let mut ws_client = BuzzTestClient::connect(&relay_ws_url(), &sender_keys) .await .expect("ws connect failed"); @@ -413,8 +413,8 @@ steps: #[tokio::test] #[ignore = "requires running relay with WF-07 filter evaluation"] async fn test_event_driven_workflow_with_filter() { + use buzz_test_client::BuzzTestClient; use nostr::{Kind, Tag}; - use sprout_test_client::SproutTestClient; let client = http_client(); let pubkey_hex: &str = SEEDED_PUBKEY; @@ -439,7 +439,7 @@ steps: .to_string(); let sender_keys = Keys::generate(); - let mut ws_client = SproutTestClient::connect(&relay_ws_url(), &sender_keys) + let mut ws_client = BuzzTestClient::connect(&relay_ws_url(), &sender_keys) .await .expect("ws connect failed"); diff --git a/crates/sprout-workflow/Cargo.toml b/crates/buzz-workflow/Cargo.toml similarity index 82% rename from crates/sprout-workflow/Cargo.toml rename to crates/buzz-workflow/Cargo.toml index 0307eb670..4e985f9f2 100644 --- a/crates/sprout-workflow/Cargo.toml +++ b/crates/buzz-workflow/Cargo.toml @@ -1,15 +1,15 @@ [package] -name = "sprout-workflow" +name = "buzz-workflow" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true repository.workspace = true -description = "YAML-as-code workflow engine for Sprout" +description = "YAML-as-code workflow engine for Buzz" [dependencies] -sprout-core = { workspace = true } -sprout-db = { workspace = true } +buzz-core = { workspace = true } +buzz-db = { workspace = true } hex = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/sprout-workflow/src/action_sink.rs b/crates/buzz-workflow/src/action_sink.rs similarity index 100% rename from crates/sprout-workflow/src/action_sink.rs rename to crates/buzz-workflow/src/action_sink.rs diff --git a/crates/sprout-workflow/src/error.rs b/crates/buzz-workflow/src/error.rs similarity index 94% rename from crates/sprout-workflow/src/error.rs rename to crates/buzz-workflow/src/error.rs index 04cfa770b..34e3d5311 100644 --- a/crates/sprout-workflow/src/error.rs +++ b/crates/buzz-workflow/src/error.rs @@ -59,8 +59,8 @@ pub enum WorkflowError { NotImplemented(String), } -impl From for WorkflowError { - fn from(e: sprout_db::error::DbError) -> Self { +impl From for WorkflowError { + fn from(e: buzz_db::error::DbError) -> Self { WorkflowError::Database(e.to_string()) } } diff --git a/crates/sprout-workflow/src/executor.rs b/crates/buzz-workflow/src/executor.rs similarity index 99% rename from crates/sprout-workflow/src/executor.rs rename to crates/buzz-workflow/src/executor.rs index 09de4a4df..342ba2d7f 100644 --- a/crates/sprout-workflow/src/executor.rs +++ b/crates/buzz-workflow/src/executor.rs @@ -755,7 +755,7 @@ pub(crate) fn parse_duration_secs(duration: &str) -> Result } // ── SSRF protection ─────────────────────────────────────────────────────────── -// is_private_ip is provided by sprout_core::network::is_private_ip +// is_private_ip is provided by buzz_core::network::is_private_ip /// Resolve `host` to IP addresses and reject if any are private/reserved. /// @@ -786,7 +786,7 @@ async fn check_ssrf(host: &str, port: u16) -> Result &'static reqwest::Client { /// POST `{"emoji": emoji}` to `POST /api/messages/{message_id}/reactions`. #[cfg(feature = "reqwest")] async fn add_reaction_impl(message_id: &str, emoji: &str) -> Result { - let base_url = std::env::var("SPROUT_RELAY_BASE_URL") - .unwrap_or_else(|_| "http://localhost:3000".to_owned()); + let base_url = + std::env::var("BUZZ_RELAY_BASE_URL").unwrap_or_else(|_| "http://localhost:3000".to_owned()); let url = format!("{base_url}/api/messages/{message_id}/reactions"); @@ -925,9 +925,9 @@ async fn add_reaction_impl(message_id: &str, emoji: &str) -> Result, - event: &sprout_core::StoredEvent, + event: &buzz_core::StoredEvent, ) -> Result<(), WorkflowError> { let Some(channel_id) = event.channel_id else { tracing::debug!( @@ -616,7 +616,7 @@ async fn should_fire_workflow( // ── Trigger context builder ─────────────────────────────────────────────────── -/// Build a [`executor::TriggerContext`] from a [`sprout_core::StoredEvent`]. +/// Build a [`executor::TriggerContext`] from a [`buzz_core::StoredEvent`]. /// /// - `text` — event content (message body or reaction emoji character) /// - `author` — pubkey hex string @@ -625,7 +625,7 @@ async fn should_fire_workflow( /// - `emoji` — for `KIND_REACTION` events, the content is the emoji; otherwise empty /// - `message_id` — for reactions, the target message's event ID (from `e` tag); /// for all other events, the event's own ID -pub fn build_trigger_context(event: &sprout_core::StoredEvent) -> executor::TriggerContext { +pub fn build_trigger_context(event: &buzz_core::StoredEvent) -> executor::TriggerContext { let kind_u32 = event_kind_u32(&event.event); let content = event.event.content.clone(); @@ -699,7 +699,7 @@ pub fn build_trigger_context(event: &sprout_core::StoredEvent) -> executor::Trig /// Returns `true` if the trigger type matches the given event kind. fn trigger_matches_event(trigger: &TriggerDef, kind_u32: u32) -> bool { - use sprout_core::kind::{KIND_REACTION, KIND_STREAM_MESSAGE, KIND_STREAM_MESSAGE_DIFF}; + use buzz_core::kind::{KIND_REACTION, KIND_STREAM_MESSAGE, KIND_STREAM_MESSAGE_DIFF}; match trigger { TriggerDef::MessagePosted { .. } => kind_u32 == KIND_STREAM_MESSAGE, TriggerDef::ReactionAdded { .. } => kind_u32 == KIND_REACTION, @@ -886,11 +886,11 @@ steps: let trigger = TriggerDef::MessagePosted { filter: None }; assert!(trigger_matches_event( &trigger, - sprout_core::kind::KIND_STREAM_MESSAGE + buzz_core::kind::KIND_STREAM_MESSAGE )); assert!(!trigger_matches_event( &trigger, - sprout_core::kind::KIND_REACTION + buzz_core::kind::KIND_REACTION )); } @@ -899,11 +899,11 @@ steps: let trigger = TriggerDef::ReactionAdded { emoji: None }; assert!(trigger_matches_event( &trigger, - sprout_core::kind::KIND_REACTION + buzz_core::kind::KIND_REACTION )); assert!(!trigger_matches_event( &trigger, - sprout_core::kind::KIND_STREAM_MESSAGE + buzz_core::kind::KIND_STREAM_MESSAGE )); } @@ -916,15 +916,15 @@ steps: // Schedule triggers are fired by the cron loop, not by events. assert!(!trigger_matches_event( &trigger, - sprout_core::kind::KIND_STREAM_MESSAGE + buzz_core::kind::KIND_STREAM_MESSAGE )); assert!(!trigger_matches_event( &trigger, - sprout_core::kind::KIND_REACTION + buzz_core::kind::KIND_REACTION )); assert!(!trigger_matches_event( &trigger, - sprout_core::kind::KIND_WORKFLOW_TRIGGERED + buzz_core::kind::KIND_WORKFLOW_TRIGGERED )); } @@ -933,7 +933,7 @@ steps: let trigger = TriggerDef::Webhook; assert!(!trigger_matches_event( &trigger, - sprout_core::kind::KIND_STREAM_MESSAGE + buzz_core::kind::KIND_STREAM_MESSAGE )); assert!(!trigger_matches_event(&trigger, 0)); } @@ -994,8 +994,8 @@ steps: let msg_trigger = TriggerDef::MessagePosted { filter: None }; let react_trigger = TriggerDef::ReactionAdded { emoji: None }; - for kind in sprout_core::kind::KIND_WORKFLOW_TRIGGERED - ..=sprout_core::kind::KIND_WORKFLOW_APPROVAL_DENIED + for kind in buzz_core::kind::KIND_WORKFLOW_TRIGGERED + ..=buzz_core::kind::KIND_WORKFLOW_APPROVAL_DENIED { assert!( !trigger_matches_event(&msg_trigger, kind), @@ -1052,7 +1052,7 @@ steps: // ── build_trigger_context ───────────────────────────────────────────────── - fn make_message_event() -> sprout_core::StoredEvent { + fn make_message_event() -> buzz_core::StoredEvent { use nostr::{EventBuilder, Keys, Kind}; use uuid::Uuid; let keys = Keys::generate(); @@ -1060,11 +1060,11 @@ steps: .tags([]) .sign_with_keys(&keys) .expect("sign"); - sprout_core::StoredEvent::new(event, Some(Uuid::new_v4())) + buzz_core::StoredEvent::new(event, Some(Uuid::new_v4())) } /// Create a reaction event with an `e` tag pointing to a target message. - fn make_reaction_event() -> (sprout_core::StoredEvent, String) { + fn make_reaction_event() -> (buzz_core::StoredEvent, String) { use nostr::{EventBuilder, Keys, Kind, Tag}; use uuid::Uuid; let keys = Keys::generate(); @@ -1082,7 +1082,7 @@ steps: .sign_with_keys(&keys) .expect("sign"); ( - sprout_core::StoredEvent::new(event, Some(Uuid::new_v4())), + buzz_core::StoredEvent::new(event, Some(Uuid::new_v4())), target_id_hex, ) } @@ -1126,7 +1126,7 @@ steps: .sign_with_keys(&keys) .expect("sign"); // channel_id = None (global/DM event) - let stored = sprout_core::StoredEvent::new(event, None); + let stored = buzz_core::StoredEvent::new(event, None); let ctx = build_trigger_context(&stored); assert_eq!(ctx.channel_id, ""); @@ -1179,7 +1179,7 @@ steps: .sign_with_keys(&keys) .expect("sign"); - let stored = sprout_core::StoredEvent::new(event, Some(Uuid::new_v4())); + let stored = buzz_core::StoredEvent::new(event, Some(Uuid::new_v4())); let ctx = build_trigger_context(&stored); // Should pick the LAST e tag (direct target), not the first (thread root) diff --git a/crates/sprout-workflow/src/schema.rs b/crates/buzz-workflow/src/schema.rs similarity index 100% rename from crates/sprout-workflow/src/schema.rs rename to crates/buzz-workflow/src/schema.rs diff --git a/crates/sprout-ws-client/Cargo.toml b/crates/buzz-ws-client/Cargo.toml similarity index 94% rename from crates/sprout-ws-client/Cargo.toml rename to crates/buzz-ws-client/Cargo.toml index f20a5dfa4..5cec92567 100644 --- a/crates/sprout-ws-client/Cargo.toml +++ b/crates/buzz-ws-client/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "sprout-ws-client" +name = "buzz-ws-client" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/crates/sprout-ws-client/src/connection.rs b/crates/buzz-ws-client/src/connection.rs similarity index 100% rename from crates/sprout-ws-client/src/connection.rs rename to crates/buzz-ws-client/src/connection.rs diff --git a/crates/sprout-ws-client/src/error.rs b/crates/buzz-ws-client/src/error.rs similarity index 100% rename from crates/sprout-ws-client/src/error.rs rename to crates/buzz-ws-client/src/error.rs diff --git a/crates/sprout-ws-client/src/lib.rs b/crates/buzz-ws-client/src/lib.rs similarity index 100% rename from crates/sprout-ws-client/src/lib.rs rename to crates/buzz-ws-client/src/lib.rs diff --git a/crates/sprout-ws-client/src/message.rs b/crates/buzz-ws-client/src/message.rs similarity index 100% rename from crates/sprout-ws-client/src/message.rs rename to crates/buzz-ws-client/src/message.rs diff --git a/crates/git-credential-nostr/Cargo.toml b/crates/git-credential-nostr/Cargo.toml index b9b78a4c9..dcd433264 100644 --- a/crates/git-credential-nostr/Cargo.toml +++ b/crates/git-credential-nostr/Cargo.toml @@ -4,7 +4,7 @@ version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true -description = "Git credential helper that produces NIP-98 auth headers for Sprout's git server" +description = "Git credential helper that produces NIP-98 auth headers for Buzz's git server" [lib] name = "git_credential_nostr" diff --git a/crates/git-credential-nostr/README.md b/crates/git-credential-nostr/README.md index 31ee04074..827f8d55a 100644 --- a/crates/git-credential-nostr/README.md +++ b/crates/git-credential-nostr/README.md @@ -1,6 +1,6 @@ # git-credential-nostr -NIP-98 credential helper for git — signs HTTP auth events with your Nostr key so git can push/pull from Sprout's git server without passwords. +NIP-98 credential helper for git — signs HTTP auth events with your Nostr key so git can push/pull from Buzz's git server without passwords. ## Requirements @@ -40,7 +40,7 @@ git clone https://relay.example.com/git/owner/repo.git ## How It Works -When a Sprout git server returns `HTTP 401` with a +When a Buzz git server returns `HTTP 401` with a `WWW-Authenticate: Nostr realm="...", method="GET"` header, git calls this helper with the request details on stdin. The helper loads your Nostr private key, builds a [NIP-98](https://github.com/nostr-protocol/nips/blob/master/98.md) @@ -63,7 +63,7 @@ git ──stdin──▶ git-credential-nostr ──stdout──▶ git |-------|-------|-----| | `no nostr key configured` | Neither `$NOSTR_PRIVATE_KEY` nor `nostr.keyfile` is set | Follow the Setup steps above | | `insecure permissions` | Key file is readable by group/others | `chmod 600 ~/.nostr/key` | -| `method hint` | Server's `WWW-Authenticate` header is missing `method="..."` | Upgrade the Sprout server | +| `method hint` | Server's `WWW-Authenticate` header is missing `method="..."` | Upgrade the Buzz server | | `useHttpPath` | `credential.useHttpPath` is not set | `git config --global credential.useHttpPath true` | | Empty output / no auth | git version is older than 2.46 | Upgrade git | | `clock skew` / auth rejected | System clock is off by more than 60 s | Sync your system clock (`ntpdate`, `timedatectl`) | diff --git a/crates/git-credential-nostr/src/lib.rs b/crates/git-credential-nostr/src/lib.rs index 44986b38a..2534f00de 100644 --- a/crates/git-credential-nostr/src/lib.rs +++ b/crates/git-credential-nostr/src/lib.rs @@ -1,4 +1,4 @@ -//! git-credential-nostr — NIP-98 git credential helper for Sprout. +//! git-credential-nostr — NIP-98 git credential helper for Buzz. //! //! Git calls this via the credential helper protocol (stdin/stdout). //! We read the request, sign a kind:27235 event, and return the base64-encoded @@ -114,8 +114,8 @@ fn parse_stdin() -> CredRequest { fn parse_method(wwwauth: &str) -> Option { // Strip the scheme prefix ("Nostr ") if present, then split on commas. - // Handles variations: `Nostr method="GET", realm="sprout"` and - // `Nostr method="GET",realm="sprout"` (with or without space after comma). + // Handles variations: `Nostr method="GET", realm="buzz"` and + // `Nostr method="GET",realm="buzz"` (with or without space after comma). let params = wwwauth.strip_prefix("Nostr ").unwrap_or(wwwauth); for param in params.split(',') { let param = param.trim(); @@ -157,9 +157,9 @@ pub fn run() -> i32 { }; } - // No Nostr challenge from the server — this isn't a Sprout remote. + // No Nostr challenge from the server — this isn't a Buzz remote. // Exit silently so git falls through to the next credential helper. - // This check comes FIRST so non-Sprout remotes never hit validation errors. + // This check comes FIRST so non-Buzz remotes never hit validation errors. let wwwauth = match req.wwwauth.as_deref() { Some(v) => v, None => return 0, diff --git a/crates/git-credential-nostr/tests/integration.rs b/crates/git-credential-nostr/tests/integration.rs index a04d61830..4a24ba520 100644 --- a/crates/git-credential-nostr/tests/integration.rs +++ b/crates/git-credential-nostr/tests/integration.rs @@ -50,7 +50,7 @@ fn valid_input() -> String { protocol=https\n\ host=relay.example.com\n\ path=git/owner/repo.git/info/refs\n\ - wwwauth[]=Nostr realm=\"sprout\", method=\"GET\"\n\ + wwwauth[]=Nostr realm=\"buzz\", method=\"GET\"\n\ \n" .to_string() } @@ -180,7 +180,7 @@ fn missing_method_hint() { protocol=https\n\ host=relay.example.com\n\ path=git/owner/repo.git/info/refs\n\ - wwwauth[]=Nostr realm=\"sprout\"\n\ + wwwauth[]=Nostr realm=\"buzz\"\n\ \n"; let nsec = fresh_nsec(); @@ -208,7 +208,7 @@ fn missing_path() { capability[]=state\n\ protocol=https\n\ host=relay.example.com\n\ - wwwauth[]=Nostr realm=\"sprout\", method=\"GET\"\n\ + wwwauth[]=Nostr realm=\"buzz\", method=\"GET\"\n\ \n"; let nsec = fresh_nsec(); diff --git a/crates/git-sign-nostr/Cargo.toml b/crates/git-sign-nostr/Cargo.toml index b3e9dcf8b..4a9aef0d9 100644 --- a/crates/git-sign-nostr/Cargo.toml +++ b/crates/git-sign-nostr/Cargo.toml @@ -20,7 +20,7 @@ path = "src/main.rs" [dependencies] # Base64 armor encoding/decoding for NIP-GS signature envelopes. # Not in workspace deps — each crate pins independently (same pattern as -# sprout-relay, sprout-cli, git-credential-nostr). +# buzz-relay, buzz-cli, git-credential-nostr). base64 = "0.22" # Hex encoding for BIP-340 signatures and public keys. diff --git a/crates/git-sign-nostr/README.md b/crates/git-sign-nostr/README.md index 26e4b5ca0..908682fd7 100644 --- a/crates/git-sign-nostr/README.md +++ b/crates/git-sign-nostr/README.md @@ -17,7 +17,7 @@ git config user.signingkey export NOSTR_PRIVATE_KEY= # Optional: NIP-OA owner attestation -export SPROUT_AUTH_TAG='["auth","","",""]' +export BUZZ_AUTH_TAG='["auth","","",""]' # Commits are now automatically signed git commit -m "signed with nostr" @@ -29,7 +29,7 @@ git verify-commit HEAD ## Key Loading Priority 1. `NOSTR_PRIVATE_KEY` environment variable -2. `SPROUT_PRIVATE_KEY` environment variable +2. `BUZZ_PRIVATE_KEY` environment variable 3. Keyfile at path from `git config nostr.keyfile` Keys may be hex (64 chars) or NIP-19 bech32 (`nsec1...`). diff --git a/crates/git-sign-nostr/src/lib.rs b/crates/git-sign-nostr/src/lib.rs index 632594aa1..7d76b1594 100644 --- a/crates/git-sign-nostr/src/lib.rs +++ b/crates/git-sign-nostr/src/lib.rs @@ -396,7 +396,7 @@ macro_rules! status_or_fail { /// Load the private key from env vars or git config keyfile. /// -/// Priority: NOSTR_PRIVATE_KEY > SPROUT_PRIVATE_KEY > git config nostr.keyfile +/// Priority: NOSTR_PRIVATE_KEY > BUZZ_PRIVATE_KEY > git config nostr.keyfile /// /// Returns a zeroize-on-drop string containing the raw key material. fn load_key() -> Result, Error> { @@ -420,21 +420,21 @@ fn load_key() -> Result, Error> { } } - // 2. SPROUT_PRIVATE_KEY - if let Ok(mut val) = std::env::var("SPROUT_PRIVATE_KEY") { + // 2. BUZZ_PRIVATE_KEY + if let Ok(mut val) = std::env::var("BUZZ_PRIVATE_KEY") { // Cap at 128 bytes: nsec1 bech32 is ~63 chars, hex is 64 chars. // 128 bytes is generous headroom; anything larger is malformed input. if val.len() > 128 { val.zeroize(); - std::env::remove_var("SPROUT_PRIVATE_KEY"); + std::env::remove_var("BUZZ_PRIVATE_KEY"); return Err(Error::Fatal( - "SPROUT_PRIVATE_KEY exceeds 128-byte size limit".to_string(), + "BUZZ_PRIVATE_KEY exceeds 128-byte size limit".to_string(), )); } let trimmed = val.trim().to_string(); val.zeroize(); // Remove from process environment to minimize exposure window - std::env::remove_var("SPROUT_PRIVATE_KEY"); + std::env::remove_var("BUZZ_PRIVATE_KEY"); if !trimmed.is_empty() { return Ok(zeroize::Zeroizing::new(trimmed)); } @@ -443,7 +443,7 @@ fn load_key() -> Result, Error> { // 3. nostr.keyfile git config let path = git_config("nostr.keyfile").ok_or_else(|| { Error::Fatal( - "no key available: set NOSTR_PRIVATE_KEY, SPROUT_PRIVATE_KEY, \ + "no key available: set NOSTR_PRIVATE_KEY, BUZZ_PRIVATE_KEY, \ or git config nostr.keyfile" .to_string(), ) @@ -456,7 +456,7 @@ fn load_key() -> Result, Error> { /// Load the NIP-OA auth tag from env or git config. /// -/// Priority per NIP-GS spec: `SPROUT_AUTH_TAG` env var > `nostr.authtag` git config. +/// Priority per NIP-GS spec: `BUZZ_AUTH_TAG` env var > `nostr.authtag` git config. /// The env var takes precedence so that CI/CD pipelines and agent harnesses can /// inject auth tags without modifying repo config. /// @@ -469,7 +469,7 @@ fn load_auth_tag() -> Result, Error> { // NIP-GS spec: check env var first, then git config. // Use git_config_strict for auth tag to fail closed on read errors — // a configured-but-unreadable auth tag must not be silently omitted. - let json_str = match std::env::var("SPROUT_AUTH_TAG") + let json_str = match std::env::var("BUZZ_AUTH_TAG") .ok() .filter(|s| !s.is_empty()) { @@ -494,39 +494,39 @@ fn load_auth_tag() -> Result, Error> { // Parse: ["auth", "", "", ""] let arr: serde_json::Value = serde_json::from_str(&json_str) - .map_err(|e| Error::Fatal(format!("SPROUT_AUTH_TAG is not valid JSON: {e}")))?; + .map_err(|e| Error::Fatal(format!("BUZZ_AUTH_TAG is not valid JSON: {e}")))?; let arr = arr .as_array() - .ok_or_else(|| Error::Fatal("SPROUT_AUTH_TAG must be a JSON array".to_string()))?; + .ok_or_else(|| Error::Fatal("BUZZ_AUTH_TAG must be a JSON array".to_string()))?; if arr.len() != 4 { return Err(Error::Fatal( - "SPROUT_AUTH_TAG must have exactly 4 elements".to_string(), + "BUZZ_AUTH_TAG must have exactly 4 elements".to_string(), )); } if arr[0].as_str() != Some("auth") { return Err(Error::Fatal( - "SPROUT_AUTH_TAG[0] must be \"auth\"".to_string(), + "BUZZ_AUTH_TAG[0] must be \"auth\"".to_string(), )); } let owner = arr[1] .as_str() - .ok_or_else(|| Error::Fatal("SPROUT_AUTH_TAG[1] must be a string".to_string()))? + .ok_or_else(|| Error::Fatal("BUZZ_AUTH_TAG[1] must be a string".to_string()))? .to_string(); let conditions = arr[2] .as_str() - .ok_or_else(|| Error::Fatal("SPROUT_AUTH_TAG[2] must be a string".to_string()))? + .ok_or_else(|| Error::Fatal("BUZZ_AUTH_TAG[2] must be a string".to_string()))? .to_string(); let sig = arr[3] .as_str() - .ok_or_else(|| Error::Fatal("SPROUT_AUTH_TAG[3] must be a string".to_string()))? + .ok_or_else(|| Error::Fatal("BUZZ_AUTH_TAG[3] must be a string".to_string()))? .to_string(); // Validate conditions character class per NIP-OA: empty string is valid, // otherwise only ASCII alphanumeric + '_' + '=' + '<' + '>' + '&' allowed. if !validate_conditions(&conditions) { return Err(Error::Fatal( - "SPROUT_AUTH_TAG conditions contain invalid characters".to_string(), + "BUZZ_AUTH_TAG conditions contain invalid characters".to_string(), )); } @@ -537,7 +537,7 @@ fn load_auth_tag() -> Result, Error> { .all(|b| b.is_ascii_hexdigit() && !b.is_ascii_uppercase()) { return Err(Error::Fatal( - "SPROUT_AUTH_TAG owner must be 64 lowercase hex chars".to_string(), + "BUZZ_AUTH_TAG owner must be 64 lowercase hex chars".to_string(), )); } if sig.len() != 128 @@ -546,7 +546,7 @@ fn load_auth_tag() -> Result, Error> { .all(|b| b.is_ascii_hexdigit() && !b.is_ascii_uppercase()) { return Err(Error::Fatal( - "SPROUT_AUTH_TAG signature must be 128 lowercase hex chars".to_string(), + "BUZZ_AUTH_TAG signature must be 128 lowercase hex chars".to_string(), )); } @@ -669,7 +669,7 @@ fn git_config(key: &str) -> Option { let mut child = process::Command::new("git") .args(["config", "--get", key]) .env_remove("NOSTR_PRIVATE_KEY") - .env_remove("SPROUT_PRIVATE_KEY") + .env_remove("BUZZ_PRIVATE_KEY") .stdout(process::Stdio::piped()) .stderr(process::Stdio::null()) .spawn() @@ -723,7 +723,7 @@ fn git_config_strict(key: &str) -> Result, String> { let mut child = process::Command::new("git") .args(["config", "--get", key]) .env_remove("NOSTR_PRIVATE_KEY") - .env_remove("SPROUT_PRIVATE_KEY") + .env_remove("BUZZ_PRIVATE_KEY") .stdout(process::Stdio::piped()) .stderr(process::Stdio::null()) .spawn() @@ -1043,7 +1043,7 @@ fn do_sign(key_id: &str, status: &mut StatusWriter) -> Result<(), Error> { if !verify_oa(&pk_hex, oa_val) { return Err(Error::Fatal( "auth tag owner signature (oa[2]) verification failed — \ - the configured SPROUT_AUTH_TAG is invalid or stale" + the configured BUZZ_AUTH_TAG is invalid or stale" .to_string(), )); } @@ -1971,7 +1971,7 @@ Initial commit" fn test_load_auth_tag_rejects_bad_conditions() { // Valid conditions → Ok(Some(...)) std::env::set_var( - "SPROUT_AUTH_TAG", + "BUZZ_AUTH_TAG", format!( r#"["auth","{}","kind=9&created_at<1700000000","{}"]"#, "a".repeat(64), @@ -1986,7 +1986,7 @@ Initial commit" // Empty conditions (valid) → Ok(Some(...)) std::env::set_var( - "SPROUT_AUTH_TAG", + "BUZZ_AUTH_TAG", format!(r#"["auth","{}","","{}"]"#, "a".repeat(64), "b".repeat(128)), ); let result = load_auth_tag(); @@ -1997,7 +1997,7 @@ Initial commit" // Conditions with spaces → Err (fail closed) std::env::set_var( - "SPROUT_AUTH_TAG", + "BUZZ_AUTH_TAG", format!( r#"["auth","{}","kind = 9","{}"]"#, "a".repeat(64), @@ -2012,7 +2012,7 @@ Initial commit" // Conditions with special chars → Err (fail closed) std::env::set_var( - "SPROUT_AUTH_TAG", + "BUZZ_AUTH_TAG", format!( r#"["auth","{}","kind=9;rm -rf /","{}"]"#, "a".repeat(64), @@ -2026,7 +2026,7 @@ Initial commit" ); // No auth tag set → Ok(None) - std::env::remove_var("SPROUT_AUTH_TAG"); + std::env::remove_var("BUZZ_AUTH_TAG"); let result = load_auth_tag(); assert!( matches!(result, Ok(None)), diff --git a/crates/sprig/Cargo.toml b/crates/sprig/Cargo.toml index 1c3cd7a95..f0bf9a0e4 100644 --- a/crates/sprig/Cargo.toml +++ b/crates/sprig/Cargo.toml @@ -5,13 +5,13 @@ edition.workspace = true rust-version.workspace = true license.workspace = true repository.workspace = true -description = "All-in-one Sprout ACP harness, agent, and developer MCP" +description = "All-in-one Buzz ACP harness, agent, and developer MCP" [[bin]] name = "sprig" path = "src/main.rs" [dependencies] -sprout-acp = { path = "../sprout-acp" } -sprout-agent = { path = "../sprout-agent" } -sprout-dev-mcp = { path = "../sprout-dev-mcp" } +buzz-acp = { path = "../buzz-acp" } +buzz-agent = { path = "../buzz-agent" } +buzz-dev-mcp = { path = "../buzz-dev-mcp" } diff --git a/crates/sprig/src/main.rs b/crates/sprig/src/main.rs index 251f787fd..672a5a5f3 100644 --- a/crates/sprig/src/main.rs +++ b/crates/sprig/src/main.rs @@ -14,8 +14,8 @@ fn dispatch() -> Result<(), String> { .to_ascii_lowercase(); match cmd.as_str() { - "sprout-acp" => sprout_acp::run().map_err(|e| e.to_string()), - "sprout-agent" => sprout_agent::run().map_err(|e| e.to_string()), + "buzz-acp" => buzz_acp::run().map_err(|e| e.to_string()), + "buzz-agent" => buzz_agent::run().map_err(|e| e.to_string()), "sprig" => match std::env::args().nth(1).as_deref() { Some("-V") | Some("--version") => { println!("sprig {}", env!("CARGO_PKG_VERSION")); @@ -36,18 +36,18 @@ fn dispatch() -> Result<(), String> { )) } }, - // sprout-dev-mcp also handles its own multicall names: rg, tree, - // sprout, git-credential-nostr, and git-sign-nostr. - _ => sprout_dev_mcp::run().map_err(|e| e.to_string()), + // buzz-dev-mcp also handles its own multicall names: rg, tree, + // buzz, git-credential-nostr, and git-sign-nostr. + _ => buzz_dev_mcp::run().map_err(|e| e.to_string()), } } fn print_usage() { println!( - "Sprig — all-in-one Sprout ACP harness, agent, and developer MCP\n\n\ + "Sprig — all-in-one Buzz ACP harness, agent, and developer MCP\n\n\ Sprig is a multicall binary. Invoke it through one of the personality names:\n\n\ - sprout-acp ACP harness\n sprout-agent ACP-compliant agent\n sprout-dev-mcp Developer MCP server\n\n\ -Developer MCP helper names are also supported: rg, tree, sprout, git-credential-nostr, git-sign-nostr.\n\n\ -Installers can create links with:\n ln -s sprig sprout-acp\n ln -s sprig sprout-agent\n ln -s sprig sprout-dev-mcp" + buzz-acp ACP harness\n buzz-agent ACP-compliant agent\n buzz-dev-mcp Developer MCP server\n\n\ +Developer MCP helper names are also supported: rg, tree, buzz, git-credential-nostr, git-sign-nostr.\n\n\ +Installers can create links with:\n ln -s sprig buzz-acp\n ln -s sprig buzz-agent\n ln -s sprig buzz-dev-mcp" ); } diff --git a/crates/sprout-cli/src/main.rs b/crates/sprout-cli/src/main.rs deleted file mode 100644 index e98f4715a..000000000 --- a/crates/sprout-cli/src/main.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[tokio::main] -async fn main() { - std::process::exit(sprout_cli::run_from_args(std::env::args()).await); -} diff --git a/deny.toml b/deny.toml index 8f86438da..149b39bdf 100644 --- a/deny.toml +++ b/deny.toml @@ -68,7 +68,7 @@ expression = "MIT OR Apache-2.0" license-files = [] [[licenses.clarify]] -crate = "sprout-desktop" +crate = "buzz-desktop" expression = "Apache-2.0" license-files = [] diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock index a2b79670b..cf7753b23 100644 --- a/desktop/src-tauri/Cargo.lock +++ b/desktop/src-tauri/Cargo.lock @@ -824,6 +824,48 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "buzz-core" +version = "0.1.0" +dependencies = [ + "chrono", + "hex", + "hmac 0.13.0", + "nostr", + "percent-encoding", + "rand 0.10.1", + "serde", + "serde_json", + "sha2 0.11.0", + "subtle", + "thiserror 2.0.18", + "url", + "uuid", + "zeroize", +] + +[[package]] +name = "buzz-persona" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "serde_yaml", + "thiserror 2.0.18", +] + +[[package]] +name = "buzz-sdk" +version = "0.1.0" +dependencies = [ + "buzz-core", + "nostr", + "serde", + "serde_json", + "thiserror 2.0.18", + "uuid", +] + [[package]] name = "bytemuck" version = "1.25.0" @@ -8178,26 +8220,6 @@ dependencies = [ "der", ] -[[package]] -name = "sprout-core" -version = "0.1.0" -dependencies = [ - "chrono", - "hex", - "hmac 0.13.0", - "nostr", - "percent-encoding", - "rand 0.10.1", - "serde", - "serde_json", - "sha2 0.11.0", - "subtle", - "thiserror 2.0.18", - "url", - "uuid", - "zeroize", -] - [[package]] name = "sprout-desktop" version = "0.3.16" @@ -8207,6 +8229,9 @@ dependencies = [ "audioadapter-buffers", "axum", "base64 0.22.1", + "buzz-core", + "buzz-persona", + "buzz-sdk", "bzip2 0.6.1", "chrono", "ctrlc", @@ -8232,9 +8257,6 @@ dependencies = [ "serde_yaml", "sha2 0.11.0", "sherpa-onnx", - "sprout-core", - "sprout-persona", - "sprout-sdk", "strip-ansi-escapes", "tar", "tauri", @@ -8260,28 +8282,6 @@ dependencies = [ "zip 8.6.0", ] -[[package]] -name = "sprout-persona" -version = "0.1.0" -dependencies = [ - "serde", - "serde_json", - "serde_yaml", - "thiserror 2.0.18", -] - -[[package]] -name = "sprout-sdk" -version = "0.1.0" -dependencies = [ - "nostr", - "serde", - "serde_json", - "sprout-core", - "thiserror 2.0.18", - "uuid", -] - [[package]] name = "sse-stream" version = "0.2.3" diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index ef481f079..0e2d63bfe 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -61,9 +61,9 @@ nostr = { version = "0.44", features = ["nip44"] } zeroize = "1" reqwest = { version = "0.13", features = ["json", "query", "stream"] } url = "2" -sprout-core = { path = "../../crates/sprout-core" } -sprout-persona = { path = "../../crates/sprout-persona" } -sprout-sdk = { path = "../../crates/sprout-sdk" } +sprout-core = { package = "buzz-core", path = "../../crates/buzz-core" } +sprout-persona = { package = "buzz-persona", path = "../../crates/buzz-persona" } +sprout-sdk = { package = "buzz-sdk", path = "../../crates/buzz-sdk" } mesh-llm-sdk = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82", package = "mesh-llm-sdk", default-features = false, features = ["client", "serving"], optional = true } mesh-llm-host-runtime = { git = "https://github.com/Mesh-LLM/mesh-llm.git", rev = "ebc3ab4c4b5f4a45d5bc942c66c18583800c3f82", package = "mesh-llm-host-runtime", default-features = false, features = ["dynamic-native-runtime"], optional = true } base64 = "0.22" diff --git a/docker-compose.yml b/docker-compose.yml index dbc6e34df..c22ac2ad3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,22 +1,22 @@ -name: sprout +name: buzz services: postgres: image: postgres:18-alpine - container_name: sprout-postgres + container_name: buzz-postgres environment: - POSTGRES_USER: sprout - POSTGRES_PASSWORD: sprout_dev - POSTGRES_DB: sprout + POSTGRES_USER: buzz + POSTGRES_PASSWORD: buzz_dev + POSTGRES_DB: buzz PGDATA: /var/lib/postgresql/data ports: - "5432:5432" volumes: - postgres-data:/var/lib/postgresql/data networks: - - sprout-net + - buzz-net healthcheck: - test: ["CMD-SHELL", "pg_isready -U sprout"] + test: ["CMD-SHELL", "pg_isready -U buzz"] interval: 5s timeout: 5s retries: 10 @@ -26,17 +26,17 @@ services: limits: memory: 512m labels: - com.sprout.service: "postgres" - com.sprout.env: "dev" + com.buzz.service: "postgres" + com.buzz.env: "dev" restart: unless-stopped redis: image: redis:8-alpine - container_name: sprout-redis + container_name: buzz-redis ports: - "6379:6379" networks: - - sprout-net + - buzz-net healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s @@ -48,23 +48,23 @@ services: limits: memory: 128m labels: - com.sprout.service: "redis" - com.sprout.env: "dev" + com.buzz.service: "redis" + com.buzz.env: "dev" restart: unless-stopped typesense: image: typesense/typesense:30.2 - container_name: sprout-typesense + container_name: buzz-typesense environment: TYPESENSE_DATA_DIR: /data - TYPESENSE_API_KEY: sprout_dev_key + TYPESENSE_API_KEY: buzz_dev_key TYPESENSE_ENABLE_CORS: "true" ports: - "8108:8108" volumes: - typesense-data:/data networks: - - sprout-net + - buzz-net healthcheck: test: ["CMD-SHELL", "ls /proc/1/exe > /dev/null 2>&1 && kill -0 1 2>/dev/null || exit 1"] interval: 10s @@ -76,17 +76,17 @@ services: limits: memory: 256m labels: - com.sprout.service: "typesense" - com.sprout.env: "dev" + com.buzz.service: "typesense" + com.buzz.env: "dev" restart: unless-stopped adminer: image: adminer:latest - container_name: sprout-adminer + container_name: buzz-adminer ports: - "8082:8080" networks: - - sprout-net + - buzz-net depends_on: postgres: condition: service_healthy @@ -97,13 +97,13 @@ services: limits: memory: 64m labels: - com.sprout.service: "adminer" - com.sprout.env: "dev" + com.buzz.service: "adminer" + com.buzz.env: "dev" restart: unless-stopped keycloak: image: quay.io/keycloak/keycloak:26.0 - container_name: sprout-keycloak + container_name: buzz-keycloak command: start-dev --http-port=8080 environment: KC_DB: dev-mem @@ -112,7 +112,7 @@ services: ports: - "8180:8080" networks: - - sprout-net + - buzz-net healthcheck: test: ["CMD-SHELL", "exec 3<>/dev/tcp/localhost/8080 && echo -e 'GET /health/ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3 && cat <&3 | grep -q '200 OK'"] interval: 10s @@ -124,24 +124,24 @@ services: limits: memory: 512m labels: - com.sprout.service: "keycloak" - com.sprout.env: "dev" + com.buzz.service: "keycloak" + com.buzz.env: "dev" restart: unless-stopped minio: image: minio/minio:latest - container_name: sprout-minio + container_name: buzz-minio command: server /data --console-address ":9001" environment: - MINIO_ROOT_USER: sprout_dev - MINIO_ROOT_PASSWORD: sprout_dev_secret + MINIO_ROOT_USER: buzz_dev + MINIO_ROOT_PASSWORD: buzz_dev_secret ports: - "9000:9000" - "9001:9001" volumes: - minio-data:/data networks: - - sprout-net + - buzz-net healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] interval: 5s @@ -153,39 +153,39 @@ services: limits: memory: 256m labels: - com.sprout.service: "minio" - com.sprout.env: "dev" + com.buzz.service: "minio" + com.buzz.env: "dev" restart: unless-stopped minio-init: image: minio/mc:latest - container_name: sprout-minio-init + container_name: buzz-minio-init depends_on: minio: condition: service_healthy networks: - - sprout-net + - buzz-net entrypoint: > /bin/sh -c " - mc alias set local http://minio:9000 sprout_dev sprout_dev_secret && - mc mb --ignore-existing local/sprout-media && - mc anonymous set none local/sprout-media + mc alias set local http://minio:9000 buzz_dev buzz_dev_secret && + mc mb --ignore-existing local/buzz-media && + mc anonymous set none local/buzz-media " labels: - com.sprout.service: "minio-init" - com.sprout.env: "dev" + com.buzz.service: "minio-init" + com.buzz.env: "dev" restart: "no" prometheus: image: prom/prometheus:latest - container_name: sprout-prometheus + container_name: buzz-prometheus ports: - "9090:9090" volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro - prometheus-data:/prometheus networks: - - sprout-net + - buzz-net extra_hosts: - "host.docker.internal:host-gateway" deploy: @@ -193,31 +193,31 @@ services: limits: memory: 128m labels: - com.sprout.service: "prometheus" - com.sprout.env: "dev" + com.buzz.service: "prometheus" + com.buzz.env: "dev" restart: unless-stopped volumes: postgres-data: - name: sprout-postgres-data + name: buzz-postgres-data labels: - com.sprout.volume: "postgres" + com.buzz.volume: "postgres" typesense-data: - name: sprout-typesense-data + name: buzz-typesense-data labels: - com.sprout.volume: "typesense" + com.buzz.volume: "typesense" minio-data: - name: sprout-minio-data + name: buzz-minio-data labels: - com.sprout.volume: "minio" + com.buzz.volume: "minio" prometheus-data: - name: sprout-prometheus-data + name: buzz-prometheus-data labels: - com.sprout.volume: "prometheus" + com.buzz.volume: "prometheus" networks: - sprout-net: - name: sprout-net + buzz-net: + name: buzz-net driver: bridge labels: - com.sprout.network: "dev" + com.buzz.network: "dev" diff --git a/docs/MCP_DRIVEN_HOOKS.md b/docs/MCP_DRIVEN_HOOKS.md index b4faabeaf..ee9424ba4 100644 --- a/docs/MCP_DRIVEN_HOOKS.md +++ b/docs/MCP_DRIVEN_HOOKS.md @@ -2,7 +2,7 @@ ## Overview -Sprout-agent supports lifecycle hooks — MCP tools that the agent calls at +Buzz-agent supports lifecycle hooks — MCP tools that the agent calls at defined points in its execution loop. Any MCP server can participate by exposing tools with the `_` prefix. Hooks are invisible to the LLM, advisory to the agent, and operator-configured. @@ -60,8 +60,8 @@ These constraints ensure a buggy or malicious hook cannot trap the agent. | Env Var | Default | Description | |---|---|---| | `MCP_HOOK_SERVERS` | (unset = no hooks) | Allowlist: `*` for all servers, or comma-separated names | -| `SPROUT_AGENT_HOOK_TIMEOUT_MS` | 2500 | Per-hook call timeout in milliseconds | -| `SPROUT_AGENT_STOP_MAX_REJECTIONS` | 3 | Session-wide `_Stop` budget (0 = disable) | +| `BUZZ_AGENT_HOOK_TIMEOUT_MS` | 2500 | Per-hook call timeout in milliseconds | +| `BUZZ_AGENT_STOP_MAX_REJECTIONS` | 3 | Session-wide `_Stop` budget (0 = disable) | Hooks are **off by default**. The operator must explicitly opt in via `MCP_HOOK_SERVERS`. diff --git a/docs/git-on-object-storage.md b/docs/git-on-object-storage.md index 901cb13d8..59599b34c 100644 --- a/docs/git-on-object-storage.md +++ b/docs/git-on-object-storage.md @@ -113,7 +113,7 @@ Two operations act on `R`: - **Push(R, Δ)** — receive-pack. Δ is a set of requested ref updates `refname ↦ (old_id, new_id)`. -**The manifest pointer is the sole source of truth.** In Sprout, a successful +**The manifest pointer is the sole source of truth.** In Buzz, a successful push may also publish a relay event (kind:30618) so subscribers learn refs moved. That event is a *derived notification*, never the commit point: a push has happened iff `M_R` was CAS-swapped (A3), regardless of whether — or when — any @@ -447,7 +447,7 @@ transfer: `manifest_event::build_ref_state_event` from `CasSuccess.manifest` — the values that *physically landed* via CAS, by `Inv_RefEffectApplied`. The event is relay-signed (the relay is authoritative for ref state of repos it hosts); - the pusher's pubkey rides in a `p` tag (sprout extension; NIP-34 does not + the pusher's pubkey rides in a `p` tag (buzz extension; NIP-34 does not define one). 30618 emission happens after `cas_publish` returns `Ok` and before the success `Response` is constructed — so 30618 is a strict consequence of a committed CAS, never the commit itself. A failed 30618 @@ -461,7 +461,7 @@ transfer: accepted v1 tradeoff named in §Scope). **Current code status (verified provenance).** The full S3-CAS implementation -exists in code at PR #726's tip (`crates/sprout-relay/src/api/git/`), with the +exists in code at PR #726's tip (`crates/buzz-relay/src/api/git/`), with the relay lib green, clippy `--tests -D warnings` clean, fmt clean, and the live MinIO e2e — clone/push/fetch/force-push roundtrip + N-way concurrent-push no-fork — green on the assembled tip. (Line numbers below are pinned at diff --git a/docs/mesh-llm-local-build.md b/docs/mesh-llm-local-build.md index 6e56d2e06..c35ce221b 100644 --- a/docs/mesh-llm-local-build.md +++ b/docs/mesh-llm-local-build.md @@ -1,12 +1,12 @@ # Mesh LLM local build prerequisites -Sprout embeds mesh-llm through the Rust SDK pinned in Cargo. mesh-llm's native +Buzz embeds mesh-llm through the Rust SDK pinned in Cargo. mesh-llm's native skippy/llama layer is linked into the relay and desktop binaries. ## Local Mac demo path For the first local milestone, use mesh-llm's default native build path. On macOS -this compiles patched llama.cpp/ggml with Metal support the first time a Sprout +this compiles patched llama.cpp/ggml with Metal support the first time a Buzz binary that depends on mesh is built. The result is cached under Cargo's git checkout of mesh-llm, so subsequent builds are much faster. @@ -20,7 +20,7 @@ brew install cmake # if cmake is not already available Then build normally: ```bash -cargo build -p sprout-relay --bin sprout-relay +cargo build -p buzz-relay --bin buzz-relay cargo check --manifest-path desktop/src-tauri/Cargo.toml ``` @@ -52,7 +52,7 @@ supported local path for M1. ## Connectivity model: public iroh relays off, raw STUN on (WAN) -Sprout Desktop starts the embedded mesh node with `disable_iroh_relays(true)` +Buzz Desktop starts the embedded mesh node with `disable_iroh_relays(true)` (mesh-llm fork rev `bc2f1106`, `RelayPolicy::ExplicitlyDisabled`): **no public iroh relays** (no `*.iroh.link` traffic, no relay transport), so there is no empty-relay fallback to public infrastructure. @@ -62,7 +62,7 @@ Raw STUN **remains on** under this policy: the node discovers its public address token / `EndpointAddr`. That address rides the relay-signed `24621`/`24622` call-me-now exchange, and peers hole-punch directly over UDP — so this works over **WAN**, not just LAN. STUN is a "what's my public IP" lookup, not a relay or a -data path; the Sprout relay performs the address-exchange coordination. +data path; the Buzz relay performs the address-exchange coordination. Residual limit (intentional v1): with iroh relays off there is **no relay transport fallback**, so two peers both behind **symmetric NATs** may fail to @@ -80,13 +80,13 @@ coverage into three layers and are explicit about what is real vs mocked. | # | What it proves | Where | Real / Mocked | Runs in CI? | How to run | |---|----------------|-------|---------------|-------------|------------| -| 1 | serve node + client node + mesh routing + **real inference** | `crates/sprout-relay/examples/mesh_serve_client_smoke.rs` | **REAL** (loads a model, runs inference, joins a real mesh) | No — hardware-gated | `just mesh-e2e-hardware` (or `cargo run -p sprout-relay --example mesh_serve_client_smoke`) | -| 2 | admission **invariant**: relay membership is the only factor | `crates/sprout-relay/src/handlers/mesh_signaling.rs` (`*_admitted` / `denied_is_not_admitted` tests) | REAL policy logic, no I/O | **Yes** | `cargo test -p sprout-relay mesh_signaling` | -| 2b | live db-membership admission + member/non-member status reads | `crates/sprout-test-client/tests/e2e_mesh_llm.rs` (`trust_*`) | REAL relay over ws | No — env-gated (`MEMBER_NSEC`/`STRANGER_NSEC`, live relay) | see that file's module docs | +| 1 | serve node + client node + mesh routing + **real inference** | `crates/buzz-relay/examples/mesh_serve_client_smoke.rs` | **REAL** (loads a model, runs inference, joins a real mesh) | No — hardware-gated | `just mesh-e2e-hardware` (or `cargo run -p buzz-relay --example mesh_serve_client_smoke`) | +| 2 | admission **invariant**: relay membership is the only factor | `crates/buzz-relay/src/handlers/mesh_signaling.rs` (`*_admitted` / `denied_is_not_admitted` tests) | REAL policy logic, no I/O | **Yes** | `cargo test -p buzz-relay mesh_signaling` | +| 2b | live db-membership admission + member/non-member status reads | `crates/buzz-test-client/tests/e2e_mesh_llm.rs` (`trust_*`) | REAL relay over ws | No — env-gated (`MEMBER_NSEC`/`STRANGER_NSEC`, live relay) | see that file's module docs | | 3 | desktop UI contract: Share-compute start/stop, Run-on-relay-mesh preset, **ensure-before-spawn** order, membership-gated toggle | `desktop/tests/e2e/mesh-compute.spec.ts` | UI REAL, Tauri mesh commands MOCKED via the e2e bridge | **Yes** | `cd desktop && pnpm test:e2e:integration -- mesh-compute.spec.ts` | `just mesh-e2e` runs the two CI-safe layers (2 + 3). Layer 1 is run on hardware. -`just mesh-e2e-hardware` prepares a matching MeshLLM native runtime in a Sprout-controlled local cache before starting the real serve/client smoke, so it does not depend on a manually preseeded `MESH_LLM_NATIVE_RUNTIME_CACHE_DIR`. +`just mesh-e2e-hardware` prepares a matching MeshLLM native runtime in a Buzz-controlled local cache before starting the real serve/client smoke, so it does not depend on a manually preseeded `MESH_LLM_NATIVE_RUNTIME_CACHE_DIR`. ### What "real" means per layer diff --git a/docs/nips/NIP-DV.md b/docs/nips/NIP-DV.md index c2a882d02..2efba0ba6 100644 --- a/docs/nips/NIP-DV.md +++ b/docs/nips/NIP-DV.md @@ -20,7 +20,7 @@ There is no user-signed request kind. The hide/unhide intent is already carried ## Motivation -Sprout DMs are surfaced to clients as NIP-29-style group membership (`kind:39002`), where the viewer appears as a `#p` participant. Hiding a DM is presentation state, not membership: the viewer stays in the member list because they can still receive messages and re-open the conversation. So `kind:39002` correctly continues to list the viewer, and a client that rebuilds its DM list from `kind:39002` alone cannot tell which DMs the viewer has hidden. +Buzz DMs are surfaced to clients as NIP-29-style group membership (`kind:39002`), where the viewer appears as a `#p` participant. Hiding a DM is presentation state, not membership: the viewer stays in the member list because they can still receive messages and re-open the conversation. So `kind:39002` correctly continues to list the viewer, and a client that rebuilds its DM list from `kind:39002` alone cannot tell which DMs the viewer has hidden. The relay does know — it records `hidden_at` per (viewer, channel) — but never emits that fact as a queryable Nostr event. A thin client is therefore flying blind on a piece of state only the relay holds. The result is the visible bug: a hidden DM is optimistically removed, then reappears on the next conversation-list refetch because the refetch is rebuilt from `kind:39002`, which never carried the hide. diff --git a/docs/nips/NIP-GS.md b/docs/nips/NIP-GS.md index 8e231cd4f..7ef37bcc2 100644 --- a/docs/nips/NIP-GS.md +++ b/docs/nips/NIP-GS.md @@ -338,7 +338,7 @@ The signing program MUST load the signer's secret key from one of the following sources, checked in order: 1. `NOSTR_PRIVATE_KEY` environment variable. -2. `SPROUT_PRIVATE_KEY` environment variable. +2. `BUZZ_PRIVATE_KEY` environment variable. 3. A keyfile at the path specified by `nostr.keyfile` git config key. Each source accepts the key in either `nsec1...` (NIP-19 bech32) or 64-character @@ -468,7 +468,7 @@ include `oa`. The `oa` field cannot be added after the fact. The signing program SHOULD load the NIP-OA auth tag from one of the following sources, checked in order: -1. `SPROUT_AUTH_TAG` environment variable — a JSON-encoded array of 4 strings +1. `BUZZ_AUTH_TAG` environment variable — a JSON-encoded array of 4 strings (`["auth", "", "", ""]`). The program extracts elements 1–3 for the `oa` field. 2. `nostr.authtag` git config key — same JSON format. @@ -760,7 +760,7 @@ signer's secret key is compromised: ### Key Exposure via Environment Variables The signing program reads secret keys from environment variables -(`NOSTR_PRIVATE_KEY`, `SPROUT_PRIVATE_KEY`). Environment variables are visible +(`NOSTR_PRIVATE_KEY`, `BUZZ_PRIVATE_KEY`). Environment variables are visible to: - The process itself and its children. diff --git a/examples/README.md b/examples/README.md index 1a0f3bbc7..be87191b2 100644 --- a/examples/README.md +++ b/examples/README.md @@ -9,7 +9,7 @@ A small non-AI bot that connects directly to the Sprout relay over WebSocket, au It demonstrates two identity paths: 1. **Standalone bot identity** — the bot authenticates with its own key and must be explicitly admitted to closed/allowlisted relays. -2. **Owner-attested / agent OAuth path** — the bot authenticates with its own key while presenting the same `SPROUT_AUTH_TAG` NIP-OA credential that Sprout agents receive from the owner/agent OAuth flow, so a relay can admit it because its owner is already a relay member. +2. **Owner-attested / agent OAuth path** — the bot authenticates with its own key while presenting the same `BUZZ_AUTH_TAG` NIP-OA credential that Sprout agents receive from the owner/agent OAuth flow, so a relay can admit it because its owner is already a relay member. See [`countdown-bot/README.md`](countdown-bot/README.md) for usage. diff --git a/examples/countdown-bot/Cargo.toml b/examples/countdown-bot/Cargo.toml index 30780e2f5..cf8ad5497 100644 --- a/examples/countdown-bot/Cargo.toml +++ b/examples/countdown-bot/Cargo.toml @@ -9,7 +9,7 @@ anyhow = "1" futures-util = { workspace = true } nostr = { workspace = true } serde_json = { workspace = true } -sprout-sdk = { path = "../../crates/sprout-sdk" } +buzz-sdk = { path = "../../crates/buzz-sdk" } tokio = { workspace = true, features = ["rt-multi-thread", "macros", "time", "signal"] } tokio-tungstenite = { workspace = true } url = { workspace = true } diff --git a/examples/countdown-bot/README.md b/examples/countdown-bot/README.md index a4329bda9..fba78b2b8 100644 --- a/examples/countdown-bot/README.md +++ b/examples/countdown-bot/README.md @@ -27,10 +27,10 @@ The bot authenticates with its own key only. Use this when the bot should be admitted as its own independent relay identity. ```bash -SPROUT_RELAY_URL=ws://localhost:3000 \ -SPROUT_CHANNEL_ID= \ -SPROUT_BOT_PRIVATE_KEY= \ -SPROUT_BOT_AUTH_MODE=standalone \ +BUZZ_RELAY_URL=ws://localhost:3000 \ +BUZZ_CHANNEL_ID= \ +BUZZ_BOT_PRIVATE_KEY= \ +BUZZ_BOT_AUTH_MODE=standalone \ cargo run --manifest-path examples/countdown-bot/Cargo.toml ``` @@ -50,27 +50,27 @@ relay member. Generate the auth tag on the fly: ```bash -SPROUT_RELAY_URL=ws://localhost:3000 \ -SPROUT_CHANNEL_ID= \ -SPROUT_BOT_PRIVATE_KEY= \ -SPROUT_OWNER_PRIVATE_KEY= \ -SPROUT_BOT_AUTH_MODE=owner-attested \ +BUZZ_RELAY_URL=ws://localhost:3000 \ +BUZZ_CHANNEL_ID= \ +BUZZ_BOT_PRIVATE_KEY= \ +BUZZ_OWNER_PRIVATE_KEY= \ +BUZZ_BOT_AUTH_MODE=owner-attested \ cargo run --manifest-path examples/countdown-bot/Cargo.toml ``` Or precompute and pass the tag explicitly: ```bash -SPROUT_AUTH_TAG='["auth","","",""]' \ -SPROUT_BOT_AUTH_MODE=owner-attested \ -# plus SPROUT_RELAY_URL, SPROUT_CHANNEL_ID, SPROUT_BOT_PRIVATE_KEY +BUZZ_AUTH_TAG='["auth","","",""]' \ +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: -- `SPROUT_REQUIRE_RELAY_MEMBERSHIP=true` on closed relays. -- `SPROUT_ALLOW_NIP_OA_AUTH=true` so owner-attested non-member bot keys can be +- `BUZZ_REQUIRE_RELAY_MEMBERSHIP=true` on closed relays. +- `BUZZ_ALLOW_NIP_OA_AUTH=true` so owner-attested non-member bot keys can be admitted. - The owner pubkey must be an active relay member. diff --git a/examples/countdown-bot/src/main.rs b/examples/countdown-bot/src/main.rs index 14bcebe2e..ed0621215 100644 --- a/examples/countdown-bot/src/main.rs +++ b/examples/countdown-bot/src/main.rs @@ -1,4 +1,4 @@ -//! A tiny non-AI Sprout bot. +//! A tiny non-AI Buzz bot. //! //! The bot listens to one channel and replies to messages that contain commands: //! - `!countdown 5` → `5 4 3 2 1 🚀` @@ -28,7 +28,7 @@ const SUBSCRIPTION_ID: &str = "countdown-bot"; const BOT_NAME: &str = "countdown-bot"; const BOT_DISPLAY_NAME: &str = "Countdown Bot"; const BOT_ABOUT: &str = - "A tiny non-AI Sprout reference bot that replies to !countdown and countdown-style !fib."; + "A tiny non-AI Buzz reference bot that replies to !countdown and countdown-style !fib."; const BOT_ICON_DATA_URL: &str = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'%3E%3Crect width='128' height='128' rx='28' fill='%23131622'/%3E%3Ccircle cx='64' cy='64' r='42' fill='none' stroke='%237dd3fc' stroke-width='10'/%3E%3Cpath d='M64 32v32l22 14' fill='none' stroke='%23facc15' stroke-width='10' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M42 96h44' stroke='%23a78bfa' stroke-width='8' stroke-linecap='round'/%3E%3C/svg%3E"; #[tokio::main] @@ -82,39 +82,33 @@ struct Config { impl Config { fn from_env() -> Result { let relay_url = - std::env::var("SPROUT_RELAY_URL").unwrap_or_else(|_| DEFAULT_RELAY_URL.to_string()); - let channel_id = required_env("SPROUT_CHANNEL_ID")?; - let bot_keys = Keys::parse(&required_env("SPROUT_BOT_PRIVATE_KEY")?) - .context("SPROUT_BOT_PRIVATE_KEY must be an nsec or hex private key")?; + std::env::var("BUZZ_RELAY_URL").unwrap_or_else(|_| DEFAULT_RELAY_URL.to_string()); + let channel_id = required_env("BUZZ_CHANNEL_ID")?; + let bot_keys = Keys::parse(&required_env("BUZZ_BOT_PRIVATE_KEY")?) + .context("BUZZ_BOT_PRIVATE_KEY must be an nsec or hex private key")?; let auth_mode = - std::env::var("SPROUT_BOT_AUTH_MODE").unwrap_or_else(|_| "standalone".to_string()); + std::env::var("BUZZ_BOT_AUTH_MODE").unwrap_or_else(|_| "standalone".to_string()); let owner_auth_tag = match auth_mode.as_str() { "standalone" => None, "owner-attested" => { - let tag_json = match std::env::var("SPROUT_AUTH_TAG") { + let tag_json = match std::env::var("BUZZ_AUTH_TAG") { Ok(value) if !value.trim().is_empty() => value, _ => { - let owner_keys = Keys::parse(&required_env("SPROUT_OWNER_PRIVATE_KEY")?) - .context( - "SPROUT_OWNER_PRIVATE_KEY must be an nsec or hex private key", - )?; - sprout_sdk::nip_oa::compute_auth_tag( - &owner_keys, - &bot_keys.public_key(), - "", - )? + let owner_keys = Keys::parse(&required_env("BUZZ_OWNER_PRIVATE_KEY")?) + .context("BUZZ_OWNER_PRIVATE_KEY must be an nsec or hex private key")?; + buzz_sdk::nip_oa::compute_auth_tag(&owner_keys, &bot_keys.public_key(), "")? } }; - let owner = sprout_sdk::nip_oa::verify_auth_tag(&tag_json, &bot_keys.public_key()) - .context("SPROUT_AUTH_TAG is not valid for SPROUT_BOT_PRIVATE_KEY")?; + let owner = buzz_sdk::nip_oa::verify_auth_tag(&tag_json, &bot_keys.public_key()) + .context("BUZZ_AUTH_TAG is not valid for BUZZ_BOT_PRIVATE_KEY")?; eprintln!("owner-attested auth tag verified; owner={}", owner.to_hex()); - Some(sprout_sdk::nip_oa::parse_auth_tag(&tag_json)?) + Some(buzz_sdk::nip_oa::parse_auth_tag(&tag_json)?) + } + other => { + bail!("BUZZ_BOT_AUTH_MODE must be 'standalone' or 'owner-attested', got {other:?}") } - other => bail!( - "SPROUT_BOT_AUTH_MODE must be 'standalone' or 'owner-attested', got {other:?}" - ), }; Ok(Self { @@ -159,7 +153,7 @@ fn build_auth_event(config: &Config, challenge: &str) -> Result { } async fn publish_profile(ws: &mut Ws, config: &Config) -> Result<()> { - let builder = sprout_sdk::builders::build_profile( + let builder = buzz_sdk::builders::build_profile( Some(BOT_DISPLAY_NAME), Some(BOT_NAME), Some(BOT_ICON_DATA_URL), @@ -239,7 +233,7 @@ async fn maybe_reply( return Ok(()); }; - let builder = sprout_sdk::builders::build_message( + let builder = buzz_sdk::builders::build_message( config.channel_id.parse()?, &reply, None, diff --git a/justfile b/justfile index a89778f40..bc9a43a4b 100644 --- a/justfile +++ b/justfile @@ -1,4 +1,4 @@ -# Sprout — development task runner +# Buzz — development task runner set dotenv-load := true @@ -53,7 +53,7 @@ build-release: # for searchability; new/updated profiles are indexed correctly automatically. # Safe to run repeatedly — Typesense upserts. reindex-kind0: - cargo run --release -p sprout-relay --bin sprout-reindex-kind0 + cargo run --release -p buzz-relay --bin buzz-reindex-kind0 # Run repo lint and formatting checks check: fmt-check clippy desktop-check desktop-tauri-fmt-check desktop-tauri-clippy web-check mobile-check @@ -118,7 +118,7 @@ _ensure-sidecar-stubs: set -euo pipefail TARGET=$(rustc -vV | sed -n 's|host: ||p') mkdir -p desktop/src-tauri/binaries - for bin in sprout-acp sprout-agent sprout-dev-mcp git-credential-nostr sprout; do + for bin in buzz-acp buzz-agent buzz-dev-mcp git-credential-nostr buzz; do touch "desktop/src-tauri/binaries/${bin}-${TARGET}" done @@ -126,8 +126,8 @@ _ensure-sidecar-stubs: _ensure-services: #!/usr/bin/env bash set -euo pipefail - pg=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' sprout-postgres 2>/dev/null || echo "not_found") - redis=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' sprout-redis 2>/dev/null || echo "not_found") + pg=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' buzz-postgres 2>/dev/null || echo "not_found") + redis=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' buzz-redis 2>/dev/null || echo "not_found") if [[ "$pg" == "healthy" && "$redis" == "healthy" ]]; then echo "Services already healthy" exit 0 @@ -136,8 +136,8 @@ _ensure-services: docker compose up -d || true echo -n "Waiting for services" for i in $(seq 1 40); do - pg=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' sprout-postgres 2>/dev/null || echo "not_found") - redis=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' sprout-redis 2>/dev/null || echo "not_found") + pg=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' buzz-postgres 2>/dev/null || echo "not_found") + redis=$(docker inspect --format '{{"{{"}}.State.Health.Status{{"}}"}}' buzz-redis 2>/dev/null || echo "not_found") if [[ "$pg" == "healthy" && "$redis" == "healthy" ]]; then echo " ready" exit 0 @@ -212,7 +212,7 @@ desktop-screenshot *ARGS: # Mesh-compute e2e: the CI-safe layers (relay mesh signaling invariants + Playwright UI) mesh-e2e: - cargo test -p sprout-relay mesh_signaling + cargo test -p buzz-relay mesh_signaling cd {{desktop_dir}} && pnpm test:e2e:integration -- mesh-compute.spec.ts # Mesh-compute Layer 1: REAL serve->client->inference on this machine (not CI) @@ -220,7 +220,7 @@ mesh-e2e-hardware: #!/usr/bin/env bash set -euo pipefail export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)" - cargo run -p sprout-relay --example mesh_serve_client_smoke + cargo run -p buzz-relay --example mesh_serve_client_smoke # Run all checks suitable for CI / pre-push (no infra needed) ci: check test-unit desktop-test desktop-build desktop-tauri-check desktop-tauri-test web-build mobile-test @@ -235,7 +235,7 @@ test: test-unit: #!/usr/bin/env bash if command -v cargo-nextest &>/dev/null; then - cargo nextest run -p sprout-core -p sprout-auth --lib + cargo nextest run -p buzz-core -p buzz-auth --lib else ./scripts/run-tests.sh unit fi @@ -248,7 +248,7 @@ test-integration: # Start the relay server (auto-starts Docker services if needed) relay: _ensure-migrations - cargo run -p sprout-relay + cargo run -p buzz-relay # Start the relay with the built web UI served from it relay-web: _ensure-migrations @@ -256,19 +256,19 @@ relay-web: _ensure-migrations set -euo pipefail [[ -d node_modules ]] || pnpm install pnpm -C web build - SPROUT_WEB_DIR=./web/dist cargo run -p sprout-relay + BUZZ_WEB_DIR=./web/dist cargo run -p buzz-relay # Start the relay server in release mode relay-release: _ensure-migrations - cargo run -p sprout-relay --release + cargo run -p buzz-relay --release -# Start sprout-proxy (dev mode) +# Start buzz-proxy (dev mode) proxy: - cargo run -p sprout-proxy + cargo run -p buzz-proxy -# Start sprout-proxy (release mode) +# Start buzz-proxy (release mode) proxy-release: - cargo run -p sprout-proxy --release + cargo run -p buzz-proxy --release # Run the desktop Tauri app in dev mode (ports and identity derived from worktree) dev *ARGS: _ensure-sidecar-stubs @@ -289,11 +289,11 @@ staging *ARGS: _ensure-sidecar-stubs #!/usr/bin/env bash set -euo pipefail pnpm install - cargo build --release -p sprout-acp -p sprout-agent -p sprout-dev-mcp -p sprout-cli + cargo build --release -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p buzz-cli export MESH_LLM_NATIVE_RUNTIME_CACHE_DIR="$(./scripts/ensure-mesh-native-runtime.sh)" # Replace the 0-byte sidecar stub with the real CLI binary so tauri dev picks it up. TARGET=$(rustc -vV | sed -n 's|host: ||p') - cp target/release/sprout "desktop/src-tauri/binaries/sprout-${TARGET}" + cp target/release/buzz "desktop/src-tauri/binaries/sprout-${TARGET}" chmod +x "desktop/src-tauri/binaries/sprout-${TARGET}" cd {{desktop_dir}} source ../scripts/instance-env.sh @@ -312,8 +312,8 @@ desktop-dev: cd {{desktop_dir}} [[ -d node_modules ]] || pnpm install source ../scripts/instance-env.sh - echo "Starting frontend dev server on Vite port ${SPROUT_VITE_PORT}, relay ${SPROUT_RELAY_URL}" - pnpm exec vite --port "${SPROUT_VITE_PORT}" --strictPort + echo "Starting frontend dev server on Vite port ${BUZZ_VITE_PORT}, relay ${BUZZ_RELAY_URL}" + pnpm exec vite --port "${BUZZ_VITE_PORT}" --strictPort # ─── Web ───────────────────────────────────────────────────────────────────── @@ -323,9 +323,9 @@ web: set -euo pipefail [[ -d node_modules ]] || pnpm install source scripts/instance-env.sh - export VITE_PORT=$((SPROUT_VITE_PORT + 100)) - export VITE_RELAY_URL="${SPROUT_RELAY_URL}" - echo "Starting web dev server on port ${VITE_PORT}, relay ${SPROUT_RELAY_URL}" + export VITE_PORT=$((BUZZ_VITE_PORT + 100)) + export VITE_RELAY_URL="${BUZZ_RELAY_URL}" + echo "Starting web dev server on port ${VITE_PORT}, relay ${BUZZ_RELAY_URL}" cd {{web_dir}} pnpm exec vite --port "${VITE_PORT}" --strictPort @@ -449,7 +449,7 @@ bump-version version: sed -i '' "s/^version: .*/version: {{ version }}+1/" mobile/pubspec.yaml # Regenerate lockfiles pnpm install --lockfile-only - cargo update -p sprout-desktop --manifest-path desktop/src-tauri/Cargo.toml + cargo update -p buzz-desktop --manifest-path desktop/src-tauri/Cargo.toml (unset GIT_DIR GIT_WORK_TREE; cd mobile && flutter pub get) echo "Bumped all manifests to {{ version }} and regenerated lockfiles" @@ -567,41 +567,41 @@ release *ARGS: # ─── Agent Harness ──────────────────────────────────────────────────────────── -# Run a goose agent connected to a Sprout relay (foreground) -goose relay="ws://localhost:3000" agents="1" heartbeat="0" prompt="" key="$SPROUT_PRIVATE_KEY": +# Run a goose agent connected to a Buzz relay (foreground) +goose relay="ws://localhost:3000" agents="1" heartbeat="0" prompt="" key="$BUZZ_PRIVATE_KEY": #!/usr/bin/env bash set -euo pipefail - cargo build --release -p sprout-acp -p sprout-cli + cargo build --release -p buzz-acp -p buzz-cli env_args=( - SPROUT_RELAY_URL="{{relay}}" - SPROUT_PRIVATE_KEY="{{key}}" - SPROUT_ACP_AGENT_COMMAND=goose - SPROUT_ACP_AGENT_ARGS=acp - SPROUT_ACP_AGENTS="{{agents}}" + BUZZ_RELAY_URL="{{relay}}" + BUZZ_PRIVATE_KEY="{{key}}" + BUZZ_ACP_AGENT_COMMAND=goose + BUZZ_ACP_AGENT_ARGS=acp + BUZZ_ACP_AGENTS="{{agents}}" GOOSE_MODE=auto ) - [[ -n "{{prompt}}" ]] && env_args+=(SPROUT_ACP_SYSTEM_PROMPT="{{prompt}}") + [[ -n "{{prompt}}" ]] && env_args+=(BUZZ_ACP_SYSTEM_PROMPT="{{prompt}}") if [[ "{{heartbeat}}" != "0" ]]; then - env_args+=(SPROUT_ACP_HEARTBEAT_INTERVAL={{heartbeat}}) + env_args+=(BUZZ_ACP_HEARTBEAT_INTERVAL={{heartbeat}}) fi - exec env "${env_args[@]}" ./target/release/sprout-acp + exec env "${env_args[@]}" ./target/release/buzz-acp # Run a goose agent in the background (screen session named 'goose-agent-N') -goose-bg relay="ws://localhost:3000" agents="1" heartbeat="0" prompt="" key="$SPROUT_PRIVATE_KEY": +goose-bg relay="ws://localhost:3000" agents="1" heartbeat="0" prompt="" key="$BUZZ_PRIVATE_KEY": #!/usr/bin/env bash set -euo pipefail - cargo build --release -p sprout-acp -p sprout-cli + cargo build --release -p buzz-acp -p buzz-cli env_args=( - SPROUT_RELAY_URL="{{relay}}" - SPROUT_PRIVATE_KEY="{{key}}" - SPROUT_ACP_AGENT_COMMAND=goose - SPROUT_ACP_AGENT_ARGS=acp - SPROUT_ACP_AGENTS="{{agents}}" + BUZZ_RELAY_URL="{{relay}}" + BUZZ_PRIVATE_KEY="{{key}}" + BUZZ_ACP_AGENT_COMMAND=goose + BUZZ_ACP_AGENT_ARGS=acp + BUZZ_ACP_AGENTS="{{agents}}" GOOSE_MODE=auto ) - [[ -n "{{prompt}}" ]] && env_args+=(SPROUT_ACP_SYSTEM_PROMPT="{{prompt}}") + [[ -n "{{prompt}}" ]] && env_args+=(BUZZ_ACP_SYSTEM_PROMPT="{{prompt}}") if [[ "{{heartbeat}}" != "0" ]]; then - env_args+=(SPROUT_ACP_HEARTBEAT_INTERVAL={{heartbeat}}) + env_args+=(BUZZ_ACP_HEARTBEAT_INTERVAL={{heartbeat}}) fi - screen -dmS goose-agent-{{agents}} bash -c "$(printf '%q ' env "${env_args[@]}") ./target/release/sprout-acp" + screen -dmS goose-agent-{{agents}} bash -c "$(printf '%q ' env "${env_args[@]}") ./target/release/buzz-acp" echo "Agent running in screen session 'goose-agent-{{agents}}'. Attach with: screen -r goose-agent-{{agents}}" diff --git a/prometheus.yml b/prometheus.yml index 770329912..073cc6b5e 100644 --- a/prometheus.yml +++ b/prometheus.yml @@ -1,10 +1,10 @@ -# Local dev Prometheus config — scrapes sprout-relay metrics. +# Local dev Prometheus config — scrapes buzz-relay metrics. # The relay runs on the host (not in Docker), so we use host.docker.internal. -# Default metrics port is 9102; override with SPROUT_METRICS_PORT. +# Default metrics port is 9102; override with BUZZ_METRICS_PORT. global: scrape_interval: 5s scrape_configs: - - job_name: sprout-relay + - job_name: buzz-relay static_configs: - targets: ["host.docker.internal:9102"] diff --git a/schema/schema.sql b/schema/schema.sql index a44e14ee6..8224c0cae 100644 --- a/schema/schema.sql +++ b/schema/schema.sql @@ -1,4 +1,4 @@ --- Sprout — Declarative Postgres schema (managed by pgschema) +-- Buzz — Declarative Postgres schema (managed by pgschema) -- -- This file represents the desired state of the database schema. -- Use `pgschema apply --file schema/schema.sql` to bring the database up to date. diff --git a/script/start b/script/start index d4b91df2f..b50812cf5 100755 --- a/script/start +++ b/script/start @@ -2,9 +2,9 @@ # CAKE entrypoint — bridge Istio abstract socket to file socket, then exec relay. # # Traffic flow: -# Client → Envoy → @istio-proxy.sock (abstract) → socat → /tmp/local.sock → Sprout -# K8s probes → 0.0.0.0:8080 → Sprout (direct, no Istio) -# Prometheus → 0.0.0.0:9102/metrics → Sprout +# Client → Envoy → @istio-proxy.sock (abstract) → socat → /tmp/local.sock → Buzz +# K8s probes → 0.0.0.0:8080 → Buzz (direct, no Istio) +# Prometheus → 0.0.0.0:9102/metrics → Buzz set -eu SIDECAR_SOCKET="${SIDECAR_LISTENER_BIND:-@istio-proxy.sock}" @@ -17,10 +17,10 @@ rm -f "${LOCAL_SOCKET}" # Only start if SIDECAR_LISTENER_BIND is set (i.e., running in CAKE). if [ -n "${SIDECAR_LISTENER_BIND:-}" ]; then socat "ABSTRACT-LISTEN:${SIDECAR_SOCKET#@},fork" "UNIX-CONNECT:${LOCAL_SOCKET}" & - export SPROUT_UDS_PATH="${LOCAL_SOCKET}" + export BUZZ_UDS_PATH="${LOCAL_SOCKET}" fi -export SPROUT_HEALTH_PORT="${SPROUT_HEALTH_PORT:-8080}" -export SPROUT_METRICS_PORT="${SPROUT_METRICS_PORT:-9102}" +export BUZZ_HEALTH_PORT="${BUZZ_HEALTH_PORT:-8080}" +export BUZZ_METRICS_PORT="${BUZZ_METRICS_PORT:-9102}" -exec /code/sprout-relay +exec /code/buzz-relay diff --git a/scripts/build-sprig.sh b/scripts/build-sprig.sh index 861ad8114..77ee6e583 100755 --- a/scripts/build-sprig.sh +++ b/scripts/build-sprig.sh @@ -1,12 +1,12 @@ #!/usr/bin/env bash -# Build Sprig — one deploy-anywhere multicall binary for the Sprout ACP +# Build Sprig — one deploy-anywhere multicall binary for the Buzz ACP # harness, agent, and developer MCP. The archive exposes these command names: # # sprig implementation binary -# sprout-acp link to sprig (ACP harness) -# sprout-agent link to sprig (ACP-compliant agent) -# sprout-dev-mcp link to sprig (developer MCP server; also dispatches -# rg/tree/sprout/git-credential-nostr/git-sign-nostr) +# buzz-acp link to sprig (ACP harness) +# buzz-agent link to sprig (ACP-compliant agent) +# buzz-dev-mcp link to sprig (developer MCP server; also dispatches +# rg/tree/buzz/git-credential-nostr/git-sign-nostr) # # Usage: # ./scripts/build-sprig.sh [version] [target] @@ -32,9 +32,9 @@ # # The tarball contains: # sprig -# sprout-acp -# sprout-agent -# sprout-dev-mcp +# buzz-acp +# buzz-agent +# buzz-dev-mcp # README.md # sprig.json { version, git_sha, target, binaries: [{name, sha256, size}] } @@ -59,7 +59,7 @@ else fi BUNDLE_BIN="sprig" -COMMANDS=(sprout-acp sprout-agent sprout-dev-mcp) +COMMANDS=(buzz-acp buzz-agent buzz-dev-mcp) echo "==> Building Sprig v${VERSION} for ${TARGET}" echo " git_sha=${GIT_SHA}" @@ -133,18 +133,18 @@ JSON cat > "${STAGING}/README.md" <<'README' # Sprig -Sprig is the all-in-one Sprout agent binary for deploy-anywhere environments. +Sprig is the all-in-one Buzz agent binary for deploy-anywhere environments. It exposes the ACP harness, ACP agent, and developer MCP command names as symlinks to one multicall binary so shared Rust runtime/TLS code is stored only once. Commands: - `sprig` — prints usage/version. Invoke a personality by one of the links below. -- `sprout-acp` — ACP harness that bridges Sprout channel events to an +- `buzz-acp` — ACP harness that bridges Buzz channel events to an ACP-compliant agent over stdio. -- `sprout-agent` — ACP-compliant agent (spawns MCP servers, calls LLMs). -- `sprout-dev-mcp` — Developer MCP server (shell, str_replace, todo) and - multicall entrypoint for `rg`, `tree`, `sprout`, `git-credential-nostr`, +- `buzz-agent` — ACP-compliant agent (spawns MCP servers, calls LLMs). +- `buzz-dev-mcp` — Developer MCP server (shell, str_replace, todo) and + multicall entrypoint for `rg`, `tree`, `buzz`, `git-credential-nostr`, `git-sign-nostr`. See `sprig.json` for SHA-256s, sizes, target, and source git SHA. @@ -160,14 +160,14 @@ export PATH="/opt/sprig:$PATH" ```bash # Agent provider -export SPROUT_AGENT_PROVIDER=anthropic # or openai +export BUZZ_AGENT_PROVIDER=anthropic # or openai export ANTHROPIC_API_KEY=sk-... export ANTHROPIC_MODEL=claude-sonnet-4-20250514 -# Nostr identity (shared by sprout-acp, git auth, signing, and sprout CLI) +# Nostr identity (shared by buzz-acp, git auth, signing, and buzz CLI) export NOSTR_PRIVATE_KEY=nsec1... -export SPROUT_PRIVATE_KEY="$NOSTR_PRIVATE_KEY" -export SPROUT_RELAY_URL=https://your-relay.example.com +export BUZZ_PRIVATE_KEY="$NOSTR_PRIVATE_KEY" +export BUZZ_RELAY_URL=https://your-relay.example.com ``` README diff --git a/scripts/bundle-sidecars.sh b/scripts/bundle-sidecars.sh index 191aae115..e2fdcc82d 100755 --- a/scripts/bundle-sidecars.sh +++ b/scripts/bundle-sidecars.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -SIDECARS=(sprout-acp sprout-agent sprout-dev-mcp git-credential-nostr sprout) +SIDECARS=(buzz-acp buzz-agent buzz-dev-mcp git-credential-nostr buzz) HOST=$(rustc -vV | sed -n 's|host: ||p') TARGET=${1:-$HOST} BINARIES_DIR="desktop/src-tauri/binaries" @@ -20,7 +20,7 @@ for bin in "${SIDECARS[@]}"; do done if [[ ${#missing[@]} -gt 0 ]]; then echo "Error: missing release binaries in $SRC_DIR: ${missing[*]}" >&2 - echo "Run 'cargo build --release -p sprout-acp -p sprout-agent -p sprout-dev-mcp -p git-credential-nostr -p sprout-cli' first." >&2 + echo "Run 'cargo build --release -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli' first." >&2 exit 1 fi diff --git a/scripts/dev-setup.sh b/scripts/dev-setup.sh index a6ea6277a..b60601ab3 100755 --- a/scripts/dev-setup.sh +++ b/scripts/dev-setup.sh @@ -49,19 +49,19 @@ load_env() { set +o allexport fi - export DATABASE_URL="${DATABASE_URL:-postgres://sprout:sprout_dev@localhost:5432/sprout}" + export DATABASE_URL="${DATABASE_URL:-postgres://buzz:buzz_dev@localhost:5432/buzz}" export PGHOST="${PGHOST:-localhost}" export PGPORT="${PGPORT:-5432}" - export PGUSER="${PGUSER:-sprout}" - export PGPASSWORD="${PGPASSWORD:-sprout_dev}" - export PGDATABASE="${PGDATABASE:-sprout}" + export PGUSER="${PGUSER:-buzz}" + export PGPASSWORD="${PGPASSWORD:-buzz_dev}" + export PGDATABASE="${PGDATABASE:-buzz}" export REDIS_URL="${REDIS_URL:-redis://localhost:6379}" - export TYPESENSE_API_KEY="${TYPESENSE_API_KEY:-sprout_dev_key}" + export TYPESENSE_API_KEY="${TYPESENSE_API_KEY:-buzz_dev_key}" export TYPESENSE_URL="${TYPESENSE_URL:-http://localhost:8108}" } postgres_accepting_connections() { - docker exec sprout-postgres \ + docker exec buzz-postgres \ pg_isready -h localhost -p 5432 -U "${PGUSER}" -d "${PGDATABASE}" \ >/dev/null 2>&1 } @@ -88,7 +88,7 @@ else # does not support on partitioned tables. Pre-create any such indexes here # so pgschema sees them as already existing and skips the CONCURRENTLY path. log "Pre-creating indexes on partitioned tables (if needed)..." - docker exec sprout-postgres psql -U "${PGUSER}" -d "${PGDATABASE}" -q -c \ + docker exec buzz-postgres psql -U "${PGUSER}" -d "${PGDATABASE}" -q -c \ "CREATE INDEX IF NOT EXISTS idx_events_parameterized ON events (kind, pubkey, d_tag, deleted_at) WHERE d_tag IS NOT NULL;" \ 2>/dev/null || true @@ -175,7 +175,7 @@ success "Git hooks installed" echo "" echo -e "${GREEN}=======================================================${NC}" -echo -e "${GREEN} Sprout dev environment is ready!${NC}" +echo -e "${GREEN} Buzz dev environment is ready!${NC}" echo -e "${GREEN}=======================================================${NC}" echo "" echo -e " ${BLUE}Postgres${NC} ${DATABASE_URL}" diff --git a/scripts/e2e-git-perms.sh b/scripts/e2e-git-perms.sh index 2b4a69c9d..c55d9173e 100755 --- a/scripts/e2e-git-perms.sh +++ b/scripts/e2e-git-perms.sh @@ -2,11 +2,11 @@ # ============================================================================= # e2e-git-perms.sh — End-to-end test for git transport, permissions, and signing # ============================================================================= -# Two bots collaborate on a simple web page via the Sprout relay's git server. +# Two bots collaborate on a simple web page via the Buzz relay's git server. # # Prerequisites: # - Docker services running (postgres, redis, typesense) -# - Relay built: cargo build --release --bin sprout-relay +# - Relay built: cargo build --release --bin buzz-relay # - Credential helper built: cargo build --release --bin git-credential-nostr # - Signing program built: cargo build --release --bin git-sign-nostr # - Python 3 with websocket-client: pip install websocket-client @@ -89,8 +89,8 @@ trap cleanup EXIT check_deps() { local missing=() - if [[ ! -x "${REPO_ROOT}/target/release/sprout-relay" ]]; then - missing+=("sprout-relay (cargo build --release --bin sprout-relay)") + if [[ ! -x "${REPO_ROOT}/target/release/buzz-relay" ]]; then + missing+=("buzz-relay (cargo build --release --bin buzz-relay)") fi if [[ ! -x "${REPO_ROOT}/target/release/git-credential-nostr" ]]; then missing+=("git-credential-nostr (cargo build --release --bin git-credential-nostr)") @@ -331,27 +331,27 @@ if [[ -f .env ]]; then set +o allexport fi -export SPROUT_GIT_REPO_PATH="${REPO_ROOT}/repos" -export SPROUT_GIT_HOOK_HMAC_SECRET="${HMAC_SECRET}" -export SPROUT_BIND_ADDR="${RELAY_HOST}:${RELAY_PORT}" +export BUZZ_GIT_REPO_PATH="${REPO_ROOT}/repos" +export BUZZ_GIT_HOOK_HMAC_SECRET="${HMAC_SECRET}" +export BUZZ_BIND_ADDR="${RELAY_HOST}:${RELAY_PORT}" export RELAY_URL="${RELAY_WS}" -export RUST_LOG="sprout_relay=warn" -export SPROUT_REQUIRE_AUTH_TOKEN=false +export RUST_LOG="buzz_relay=warn" +export BUZZ_REQUIRE_AUTH_TOKEN=false # Clean repos dir (isolated test state) rm -rf "${REPO_ROOT}/repos" mkdir -p "${REPO_ROOT}/repos" -./target/release/sprout-relay > /tmp/sprout-relay-e2e.log 2>&1 & +./target/release/buzz-relay > /tmp/buzz-relay-e2e.log 2>&1 & RELAY_PID=$! # Wait for relay to be ready (poll, not sleep) for i in $(seq 1 "$RELAY_STARTUP_TIMEOUT"); do - if curl -sf --max-time 2 "${RELAY_HTTP}/" -H "Accept: application/nostr+json" | grep -q "Sprout"; then + if curl -sf --max-time 2 "${RELAY_HTTP}/" -H "Accept: application/nostr+json" | grep -q "Buzz"; then break fi if [[ $i -eq "$RELAY_STARTUP_TIMEOUT" ]]; then - fail "Relay did not start within ${RELAY_STARTUP_TIMEOUT}s. Check /tmp/sprout-relay-e2e.log" + fail "Relay did not start within ${RELAY_STARTUP_TIMEOUT}s. Check /tmp/buzz-relay-e2e.log" fi sleep 1 done @@ -403,7 +403,7 @@ log " Add bot2: $ADD_BOT2" log "Creating repo: $REPO_NAME..." CREATE_REPO=$(send_event "$OWNER_PRIVKEY" "$KIND_CREATE_REPO" "" \ - "[\"d\", \"$REPO_NAME\"], [\"sprout-channel\", \"$CHANNEL_ID\"]") + "[\"d\", \"$REPO_NAME\"], [\"buzz-channel\", \"$CHANNEL_ID\"]") log " Create repo: $CREATE_REPO" # Wait for repo creation side effect (bare repo on disk) @@ -448,7 +448,7 @@ cat > "$BOT1_DIR/index.html" << 'HTML' - Sprout E2E Test Page + Buzz E2E Test Page -

🌱 Sprout Collaborative Page

-

This page was created by two bots collaborating via Sprout's git server.

+

🌱 Buzz Collaborative Page

+

This page was created by two bots collaborating via Buzz's git server.

Bot 1 — Created the initial page structure
@@ -466,14 +466,14 @@ cat > "$BOT1_DIR/index.html" << 'HTML' HTML git -C "$BOT1_DIR" add -A -git -C "$BOT1_DIR" -c user.name="Bot1" -c user.email="bot1@sprout.test" \ +git -C "$BOT1_DIR" -c user.name="Bot1" -c user.email="bot1@buzz.test" \ -c init.defaultBranch=main commit -m "Initial page structure" log "Bot1: pushing..." if git_push "$BOT1_PRIVKEY" "$BOT1_DIR" -u origin main; then success "Bot1 push succeeded (member can push)" else - tail -20 /tmp/sprout-relay-e2e.log + tail -20 /tmp/buzz-relay-e2e.log fail "Bot1 push failed (member should be able to push)" fi @@ -491,19 +491,19 @@ sed -i.bak '/<\/body>/i\ Bot 2 — Added this section (pushing as bot role → promoted to member)\ \
\ -

Built with Sprout sovereign git hosting

\ +

Built with Buzz sovereign git hosting

\
' "$BOT2_DIR/index.html" rm -f "$BOT2_DIR/index.html.bak" git -C "$BOT2_DIR" add -A -git -C "$BOT2_DIR" -c user.name="Bot2" -c user.email="bot2@sprout.test" \ +git -C "$BOT2_DIR" -c user.name="Bot2" -c user.email="bot2@buzz.test" \ commit -m "Add bot2 section and footer" log "Bot2: pushing..." if git_push "$BOT2_PRIVKEY" "$BOT2_DIR"; then success "Bot2 push succeeded (bot promoted to member)" else - tail -20 /tmp/sprout-relay-e2e.log + tail -20 /tmp/buzz-relay-e2e.log fail "Bot2 push failed (bot should be promoted to member)" fi @@ -576,7 +576,7 @@ git_clone "$BOT1_PRIVKEY" "${RELAY_HTTP}/git/${OWNER_PUBKEY}/${REPO_NAME}" "$UNS echo "" >> "$UNSIGNED_DIR/index.html" git -C "$UNSIGNED_DIR" add -A -git -C "$UNSIGNED_DIR" -c user.name="Bot1" -c user.email="bot1@sprout.test" \ +git -C "$UNSIGNED_DIR" -c user.name="Bot1" -c user.email="bot1@buzz.test" \ commit -m "Unsigned commit (no gpgsign)" if git_push "$BOT1_PRIVKEY" "$UNSIGNED_DIR"; then @@ -599,7 +599,7 @@ git -C "$SIGNED_DIR" add -A NOSTR_PRIVATE_KEY="$BOT1_PRIVKEY" \ git -C "$SIGNED_DIR" \ -c user.name="Bot1" \ - -c user.email="bot1@sprout.test" \ + -c user.email="bot1@buzz.test" \ -c gpg.format=x509 \ -c "gpg.x509.program=$SIGNER" \ -c commit.gpgsign=true \ @@ -629,7 +629,7 @@ fi # ── Test: Signed commit with owner attestation (NIP-OA) ────────────────────── -log "Signing with owner attestation (SPROUT_AUTH_TAG)..." +log "Signing with owner attestation (BUZZ_AUTH_TAG)..." OA_DIR="$WORK_DIR/oa-signed" git_clone "$BOT1_PRIVKEY" "${RELAY_HTTP}/git/${OWNER_PUBKEY}/${REPO_NAME}" "$OA_DIR" \ @@ -699,10 +699,10 @@ print(json.dumps(["auth", owner_pubkey, "", sig])) PYEOF ) -NOSTR_PRIVATE_KEY="$BOT1_PRIVKEY" SPROUT_AUTH_TAG="$OA_TAG" \ +NOSTR_PRIVATE_KEY="$BOT1_PRIVKEY" BUZZ_AUTH_TAG="$OA_TAG" \ git -C "$OA_DIR" \ -c user.name="Bot1" \ - -c user.email="bot1@sprout.test" \ + -c user.email="bot1@buzz.test" \ -c gpg.format=x509 \ -c "gpg.x509.program=$SIGNER" \ -c commit.gpgsign=true \ @@ -715,7 +715,7 @@ DECODED_SIG=$(echo "$COMMIT_SIG" | base64 -d 2>/dev/null || echo "$COMMIT_SIG" | if echo "$DECODED_SIG" | grep -q '"oa"'; then success "Owner attestation (oa field) present in signature" else - fail "Owner attestation missing from signature — SPROUT_AUTH_TAG not picked up" + fail "Owner attestation missing from signature — BUZZ_AUTH_TAG not picked up" fi # Push it @@ -829,7 +829,7 @@ log "Hook integrity: testing symlink hook rejection..." echo "" >> "$SYMLINK_DIR/index.html" git -C "$SYMLINK_DIR" add -A - git -C "$SYMLINK_DIR" -c user.name="Bot1" -c user.email="bot1@sprout.test" \ + git -C "$SYMLINK_DIR" -c user.name="Bot1" -c user.email="bot1@buzz.test" \ commit -m "Symlink hook test" if git_push "$BOT1_PRIVKEY" "$SYMLINK_DIR" 2>&1; then @@ -862,7 +862,7 @@ log "Hook integrity: testing missing hook rejection..." echo "" >> "$MISSING_DIR/index.html" git -C "$MISSING_DIR" add -A - git -C "$MISSING_DIR" -c user.name="Bot1" -c user.email="bot1@sprout.test" \ + git -C "$MISSING_DIR" -c user.name="Bot1" -c user.email="bot1@buzz.test" \ commit -m "Missing hook test" if git_push "$BOT1_PRIVKEY" "$MISSING_DIR" 2>&1; then diff --git a/scripts/e2e-relay-membership.sh b/scripts/e2e-relay-membership.sh index 174c91994..6830af919 100755 --- a/scripts/e2e-relay-membership.sh +++ b/scripts/e2e-relay-membership.sh @@ -6,7 +6,7 @@ # # Prerequisites: # - Docker services running (postgres, redis, typesense) -# - Relay built: cargo build --release --bin sprout-relay +# - Relay built: cargo build --release --bin buzz-relay # - nak available on PATH (for event signing) # # What it tests: @@ -138,7 +138,7 @@ send_event() { # ── Helper: REST call with X-Pubkey header (dev mode) ──────────────────────── # -# When SPROUT_REQUIRE_AUTH_TOKEN=false the relay accepts an X-Pubkey header +# When BUZZ_REQUIRE_AUTH_TOKEN=false the relay accepts an X-Pubkey header # containing the caller's hex pubkey — no token minting required. # This is the correct pattern for dev-mode E2E tests. # @@ -218,11 +218,11 @@ if [[ -f .env ]]; then set +o allexport fi -export SPROUT_BIND_ADDR="0.0.0.0:3000" +export BUZZ_BIND_ADDR="0.0.0.0:3000" export RELAY_URL="ws://localhost:3000" -export RUST_LOG="sprout_relay=warn" -export SPROUT_REQUIRE_AUTH_TOKEN=false -export SPROUT_REQUIRE_RELAY_MEMBERSHIP=true +export RUST_LOG="buzz_relay=warn" +export BUZZ_REQUIRE_AUTH_TOKEN=false +export BUZZ_REQUIRE_RELAY_MEMBERSHIP=true # Generate owner keypair BEFORE relay start — main.rs requires RELAY_OWNER_PUBKEY log "Generating owner keypair..." @@ -233,22 +233,22 @@ log "Owner pubkey: $OWNER_PUBKEY" # Generate a stable relay signing key for NIP-43 self-signed events (kind:13534, etc.) RELAY_SK=$(generate_keypair) -export SPROUT_RELAY_PRIVATE_KEY="$RELAY_SK" +export BUZZ_RELAY_PRIVATE_KEY="$RELAY_SK" log "Relay signing key set (NIP-43 self-signed events enabled)" # Kill any existing relay -pkill -f "sprout-relay" 2>/dev/null || true +pkill -f "buzz-relay" 2>/dev/null || true sleep 1 -./target/release/sprout-relay > /tmp/sprout-relay-membership-e2e.log 2>&1 & +./target/release/buzz-relay > /tmp/buzz-relay-membership-e2e.log 2>&1 & RELAY_PID=$! for i in $(seq 1 15); do - if curl -s http://localhost:3000/ -H "Accept: application/nostr+json" | grep -q "Sprout"; then + if curl -s http://localhost:3000/ -H "Accept: application/nostr+json" | grep -q "Buzz"; then break fi if [[ $i -eq 15 ]]; then - fatal "Relay did not start. Check /tmp/sprout-relay-membership-e2e.log" + fatal "Relay did not start. Check /tmp/buzz-relay-membership-e2e.log" fi sleep 1 done diff --git a/scripts/instance-env.sh b/scripts/instance-env.sh index c972b92cd..4d1cc2e55 100755 --- a/scripts/instance-env.sh +++ b/scripts/instance-env.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bash # Computes the full multi-instance desktop dev environment. # Source this file from desktop dev commands; it exports: -# SPROUT_VITE_PORT, SPROUT_HMR_PORT, VITE_PORT, VITE_HMR_PORT -# SPROUT_RELAY_PORT, SPROUT_RELAY_URL -# SPROUT_INSTANCE_SLUG, SPROUT_WORKTREE_LABEL, VITE_DEV_BRANCH (worktrees only) -# SPROUT_TAURI_CONFIG -# SPROUT_PRIVATE_KEY (worktrees only, when SPROUT_SHARE_IDENTITY=1) +# BUZZ_VITE_PORT, BUZZ_HMR_PORT, VITE_PORT, VITE_HMR_PORT +# BUZZ_RELAY_PORT, BUZZ_RELAY_URL +# BUZZ_INSTANCE_SLUG, BUZZ_WORKTREE_LABEL, VITE_DEV_BRANCH (worktrees only) +# BUZZ_TAURI_CONFIG +# BUZZ_PRIVATE_KEY (worktrees only, when BUZZ_SHARE_IDENTITY=1) WORKTREE_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) @@ -13,14 +13,14 @@ WORKTREE_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) # gets the same ports. This keeps the Tauri dev config stable between runs and # preserves Cargo's build cache. BASE_PORT=$(python3 -c "import hashlib,sys; h=int(hashlib.sha256(sys.argv[1].encode()).hexdigest(), 16); print(10000 + h % 55000)" "$WORKTREE_ROOT") -export SPROUT_VITE_PORT=$BASE_PORT -export SPROUT_HMR_PORT=$((BASE_PORT + 1)) -export SPROUT_RELAY_PORT=3000 -export VITE_PORT="$SPROUT_VITE_PORT" -export VITE_HMR_PORT="$SPROUT_HMR_PORT" -export SPROUT_RELAY_URL="${SPROUT_RELAY_URL:-ws://localhost:3000}" +export BUZZ_VITE_PORT=$BASE_PORT +export BUZZ_HMR_PORT=$((BASE_PORT + 1)) +export BUZZ_RELAY_PORT=3000 +export VITE_PORT="$BUZZ_VITE_PORT" +export VITE_HMR_PORT="$BUZZ_HMR_PORT" +export BUZZ_RELAY_URL="${BUZZ_RELAY_URL:-ws://localhost:3000}" -SPROUT_TAURI_CONFIG="{\"build\":{\"devUrl\":\"http://localhost:${SPROUT_VITE_PORT}\",\"beforeDevCommand\":\"exec ./node_modules/.bin/vite --port ${SPROUT_VITE_PORT} --strictPort\"},\"identifier\":\"xyz.block.sprout.app.dev\",\"productName\":\"Sprout Dev\"}" +BUZZ_TAURI_CONFIG="{\"build\":{\"devUrl\":\"http://localhost:${BUZZ_VITE_PORT}\",\"beforeDevCommand\":\"exec ./node_modules/.bin/vite --port ${BUZZ_VITE_PORT} --strictPort\"},\"identifier\":\"xyz.block.buzz.app.dev\",\"productName\":\"Buzz Dev\"}" unset VITE_DEV_BRANCH # In worktrees, extract a label from the branch name and derive a unique app @@ -34,19 +34,19 @@ if git rev-parse --is-inside-work-tree &>/dev/null; then GIT_COMMON_DIR=$(git rev-parse --git-common-dir 2>/dev/null) if [[ -n "$GIT_COMMON_DIR" && "$GIT_DIR" != "$GIT_COMMON_DIR" ]]; then BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) - export SPROUT_WORKTREE_LABEL="${BRANCH_NAME##*/}" - export SPROUT_INSTANCE_SLUG=$(echo "$BRANCH_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//') + export BUZZ_WORKTREE_LABEL="${BRANCH_NAME##*/}" + export BUZZ_INSTANCE_SLUG=$(echo "$BRANCH_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//') - # SPROUT_SHARE_IDENTITY=1: reuse the main dev checkout's Nostr key so + # BUZZ_SHARE_IDENTITY=1: reuse the main dev checkout's Nostr key so # worktrees skip onboarding and share the same identity. The per-worktree # identifier is kept so concurrent instances don't collide on # tauri-plugin-single-instance or the app data directory. - if [[ "${SPROUT_SHARE_IDENTITY:-0}" == "1" ]]; then - CANONICAL_KEY="$HOME/Library/Application Support/xyz.block.sprout.app.dev/identity.key" + if [[ "${BUZZ_SHARE_IDENTITY:-0}" == "1" ]]; then + CANONICAL_KEY="$HOME/Library/Application Support/xyz.block.buzz.app.dev/identity.key" if [[ -f "$CANONICAL_KEY" ]]; then - export SPROUT_PRIVATE_KEY="$(cat "$CANONICAL_KEY")" + export BUZZ_PRIVATE_KEY="$(cat "$CANONICAL_KEY")" else - echo "⚠ SPROUT_SHARE_IDENTITY=1 but no identity found at $CANONICAL_KEY — run Sprout from repo root first" >&2 + echo "⚠ BUZZ_SHARE_IDENTITY=1 but no identity found at $CANONICAL_KEY — run Buzz from repo root first" >&2 fi fi @@ -54,12 +54,12 @@ if git rev-parse --is-inside-work-tree &>/dev/null; then mkdir -p "$ICON_DIR" DEV_ICON="$ICON_DIR/icon.icns" - if swift ../scripts/generate-dev-icon.swift src-tauri/icons/icon.icns "$DEV_ICON" "$SPROUT_WORKTREE_LABEL"; then - echo "🌳 Worktree: ${SPROUT_WORKTREE_LABEL}" - export VITE_DEV_BRANCH="$SPROUT_WORKTREE_LABEL" - SPROUT_TAURI_CONFIG="{\"build\":{\"devUrl\":\"http://localhost:${SPROUT_VITE_PORT}\",\"beforeDevCommand\":\"exec ./node_modules/.bin/vite --port ${SPROUT_VITE_PORT} --strictPort\"},\"identifier\":\"xyz.block.sprout.app.dev.${SPROUT_INSTANCE_SLUG}\",\"productName\":\"Sprout Dev (${SPROUT_WORKTREE_LABEL})\",\"bundle\":{\"icon\":[\"$DEV_ICON\"]}}" + if swift ../scripts/generate-dev-icon.swift src-tauri/icons/icon.icns "$DEV_ICON" "$BUZZ_WORKTREE_LABEL"; then + echo "🌳 Worktree: ${BUZZ_WORKTREE_LABEL}" + export VITE_DEV_BRANCH="$BUZZ_WORKTREE_LABEL" + BUZZ_TAURI_CONFIG="{\"build\":{\"devUrl\":\"http://localhost:${BUZZ_VITE_PORT}\",\"beforeDevCommand\":\"exec ./node_modules/.bin/vite --port ${BUZZ_VITE_PORT} --strictPort\"},\"identifier\":\"xyz.block.buzz.app.dev.${BUZZ_INSTANCE_SLUG}\",\"productName\":\"Buzz Dev (${BUZZ_WORKTREE_LABEL})\",\"bundle\":{\"icon\":[\"$DEV_ICON\"]}}" fi fi fi -export SPROUT_TAURI_CONFIG +export BUZZ_TAURI_CONFIG diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh index 31ae61f05..3405410e8 100755 --- a/scripts/run-tests.sh +++ b/scripts/run-tests.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # ============================================================================= -# run-tests.sh — Run Sprout test suite +# run-tests.sh — Run Buzz test suite # ============================================================================= # Usage: # ./scripts/run-tests.sh # run all tests (default) @@ -40,14 +40,14 @@ if [[ -f ".env" ]]; then set +o allexport else # Use defaults matching docker-compose.yml - export DATABASE_URL="postgres://sprout:sprout_dev@localhost:5432/sprout" + export DATABASE_URL="postgres://buzz:buzz_dev@localhost:5432/buzz" export PGHOST=localhost export PGPORT=5432 - export PGUSER=sprout - export PGPASSWORD=sprout_dev - export PGDATABASE=sprout + export PGUSER=buzz + export PGPASSWORD=buzz_dev + export PGDATABASE=buzz export REDIS_URL="redis://localhost:6379" - export TYPESENSE_API_KEY="sprout_dev_key" + export TYPESENSE_API_KEY="buzz_dev_key" export TYPESENSE_URL="http://localhost:8108" fi @@ -80,11 +80,11 @@ ensure_infra() { run_unit_tests() { section "Unit Tests (no infra required)" - run_test_step "sprout-core tests" \ - cargo test -p sprout-core --lib -- --nocapture + run_test_step "buzz-core tests" \ + cargo test -p buzz-core --lib -- --nocapture - run_test_step "sprout-auth unit tests" \ - cargo test -p sprout-auth --lib -- --nocapture + run_test_step "buzz-auth unit tests" \ + cargo test -p buzz-auth --lib -- --nocapture } # ---- DB / integration tests (infra required) -------------------------------- @@ -94,12 +94,12 @@ run_integration_tests() { ensure_infra - run_test_step "sprout-db tests" \ - cargo test -p sprout-db -- --nocapture + run_test_step "buzz-db tests" \ + cargo test -p buzz-db -- --nocapture - run_test_step "sprout-auth integration tests" \ - cargo test -p sprout-auth --test '*' -- --nocapture 2>/dev/null || \ - run_test_step "sprout-auth (no integration tests found)" true + run_test_step "buzz-auth integration tests" \ + cargo test -p buzz-auth --test '*' -- --nocapture 2>/dev/null || \ + run_test_step "buzz-auth (no integration tests found)" true run_test_step "workspace integration tests" \ cargo test --test '*' -- --nocapture 2>/dev/null || \ diff --git a/scripts/setup-desktop-test-data.sh b/scripts/setup-desktop-test-data.sh index df432d013..61d4ba9c1 100755 --- a/scripts/setup-desktop-test-data.sh +++ b/scripts/setup-desktop-test-data.sh @@ -2,11 +2,11 @@ set -euo pipefail -DB_HOST="${SPROUT_DB_HOST:-127.0.0.1}" -DB_PORT="${SPROUT_DB_PORT:-5432}" -DB_USER="${SPROUT_DB_USER:-sprout}" -DB_PASS="${SPROUT_DB_PASS:-sprout_dev}" -DB_NAME="${SPROUT_DB_NAME:-sprout}" +DB_HOST="${BUZZ_DB_HOST:-127.0.0.1}" +DB_PORT="${BUZZ_DB_PORT:-5432}" +DB_USER="${BUZZ_DB_USER:-buzz}" +DB_PASS="${BUZZ_DB_PASS:-buzz_dev}" +DB_NAME="${BUZZ_DB_NAME:-buzz}" SYSTEM_PUBKEY="0000000000000000000000000000000000000000000000000000000000000000" ALICE_PUBKEY="953d3363262e86b770419834c53d2446409db6d918a57f8f339d495d54ab001f" @@ -17,9 +17,9 @@ AGENT_PUBKEY="db0b028cd36f4d3e36c8300cce87252c1f7fc9495ffecc53f393fcac341ffd36" if command -v psql >/dev/null 2>&1; then run_psql() { PGPASSWORD="$DB_PASS" psql -h"$DB_HOST" -p"$DB_PORT" -U"$DB_USER" -d"$DB_NAME" -qtA "$@"; } -elif docker exec sprout-postgres psql --version >/dev/null 2>&1; then +elif docker exec buzz-postgres psql --version >/dev/null 2>&1; then run_psql() { - docker exec -e PGPASSWORD="$DB_PASS" sprout-postgres \ + docker exec -e PGPASSWORD="$DB_PASS" buzz-postgres \ psql -U"$DB_USER" -d"$DB_NAME" -qtA "$@" } else @@ -43,15 +43,15 @@ PYEOF echo "Checking database connection..." run_sql "SELECT 1" >/dev/null -UUID_GENERAL=$(uuid5_hex "sprout.channel.general") -UUID_RANDOM=$(uuid5_hex "sprout.channel.random") -UUID_ENGINEERING=$(uuid5_hex "sprout.channel.engineering") -UUID_AGENTS=$(uuid5_hex "sprout.channel.agents") -UUID_WATERCOOLER=$(uuid5_hex "sprout.channel.watercooler") -UUID_ANNOUNCEMENTS=$(uuid5_hex "sprout.channel.announcements") -UUID_DM_ALICE_TYLER=$(uuid5_hex "sprout.channel.dm.alice-tyler") -UUID_DM_BOB_TYLER=$(uuid5_hex "sprout.channel.dm.bob-tyler") -UUID_DM_BOB_CHARLIE_TYLER=$(uuid5_hex "sprout.channel.dm.bob-charlie-tyler") +UUID_GENERAL=$(uuid5_hex "buzz.channel.general") +UUID_RANDOM=$(uuid5_hex "buzz.channel.random") +UUID_ENGINEERING=$(uuid5_hex "buzz.channel.engineering") +UUID_AGENTS=$(uuid5_hex "buzz.channel.agents") +UUID_WATERCOOLER=$(uuid5_hex "buzz.channel.watercooler") +UUID_ANNOUNCEMENTS=$(uuid5_hex "buzz.channel.announcements") +UUID_DM_ALICE_TYLER=$(uuid5_hex "buzz.channel.dm.alice-tyler") +UUID_DM_BOB_TYLER=$(uuid5_hex "buzz.channel.dm.bob-tyler") +UUID_DM_BOB_CHARLIE_TYLER=$(uuid5_hex "buzz.channel.dm.bob-charlie-tyler") run_sql " INSERT INTO channels diff --git a/scripts/test-proxy-e2e-live.sh b/scripts/test-proxy-e2e-live.sh index aab2e8063..f42328a3d 100755 --- a/scripts/test-proxy-e2e-live.sh +++ b/scripts/test-proxy-e2e-live.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Live e2e test for sprout-proxy — requires relay on :3000 and proxy on :4869 +# Live e2e test for buzz-proxy — requires relay on :3000 and proxy on :4869 set -euo pipefail PROXY_URL="ws://0.0.0.0:4869" @@ -12,13 +12,13 @@ FAIL=0 ok() { PASS=$((PASS+1)); echo " ✅ $1"; } fail() { FAIL=$((FAIL+1)); echo " ❌ $1"; } -echo "═══ sprout-proxy e2e tests ═══" +echo "═══ buzz-proxy e2e tests ═══" echo "" # ── Test 1: NIP-11 ────────────────────────────────────────────────────── echo "1. NIP-11 relay info" NIP11=$(curl -sf "$PROXY_HTTP" -H "Accept: application/nostr+json") -if echo "$NIP11" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['name']=='sprout-proxy'" 2>/dev/null; then +if echo "$NIP11" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['name']=='buzz-proxy'" 2>/dev/null; then ok "NIP-11 returns valid relay info" else fail "NIP-11 check failed" diff --git a/scripts/test-proxy-e2e.sh b/scripts/test-proxy-e2e.sh index ff298fb57..3ed1e69cc 100755 --- a/scripts/test-proxy-e2e.sh +++ b/scripts/test-proxy-e2e.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash -# End-to-end test for sprout-proxy +# End-to-end test for buzz-proxy # Prerequisites: -# - Sprout relay running on :3000 (just relay) -# - Sprout proxy running on :4869 (just proxy) +# - Buzz relay running on :3000 (just relay) +# - Buzz proxy running on :4869 (just proxy) # - websocat installed (cargo install websocat) # - curl installed # - jq installed @@ -12,7 +12,7 @@ PROXY_URL="${PROXY_URL:-ws://localhost:4869}" PROXY_HTTP="${PROXY_HTTP:-http://localhost:4869}" RELAY_HTTP="${RELAY_HTTP:-http://localhost:3000}" -echo "=== Sprout Proxy E2E Test ===" +echo "=== Buzz Proxy E2E Test ===" echo "Proxy: $PROXY_URL" echo "Relay: $RELAY_HTTP" diff --git a/scripts/test-proxy-nostr-sdk-python.py b/scripts/test-proxy-nostr-sdk-python.py index 0c2501b8a..4b0a11edf 100644 --- a/scripts/test-proxy-nostr-sdk-python.py +++ b/scripts/test-proxy-nostr-sdk-python.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -sprout-proxy E2E test using nostr-sdk v0.44 (Python bindings for rust-nostr) +buzz-proxy E2E test using nostr-sdk v0.44 (Python bindings for rust-nostr) nostr-sdk is the official Rust Nostr SDK with Python/Swift/Flutter bindings. Second most popular Nostr SDK after nostr-tools, powers native clients. @@ -37,7 +37,7 @@ def fail(name, detail=""): async def main(): print("=" * 64) - print(" nostr-sdk v0.44 (Python/Rust) E2E TEST against sprout-proxy") + print(" nostr-sdk v0.44 (Python/Rust) E2E TEST against buzz-proxy") print("=" * 64) keys = nostr_sdk.Keys.parse(PRIVKEY_HEX) @@ -143,7 +143,7 @@ async def main(): all_pass = False print("=" * 64) if all_pass: - print(" 🎉 ALL TESTS PASSED — nostr-sdk Python works with sprout-proxy!") + print(" 🎉 ALL TESTS PASSED — nostr-sdk Python works with buzz-proxy!") else: print(" ⚠️ Some tests failed") print("=" * 64) diff --git a/scripts/test-proxy-nostr-tools.mjs b/scripts/test-proxy-nostr-tools.mjs index 887e9ff0c..3cdc2891d 100644 --- a/scripts/test-proxy-nostr-tools.mjs +++ b/scripts/test-proxy-nostr-tools.mjs @@ -1,10 +1,10 @@ /** - * sprout-proxy E2E test using nostr-tools v2.23.3 + * buzz-proxy E2E test using nostr-tools v2.23.3 * * nostr-tools is the most widely-used Nostr library in the ecosystem, * powering Coracle, Snort, Damus Web, and hundreds of other clients. * - * Tests the full NIP-28 channel flow through sprout-proxy: + * Tests the full NIP-28 channel flow through buzz-proxy: * 1. NIP-42 authentication (automatic via onauth callback) * 2. Channel discovery (kind:40) * 3. Channel metadata (kind:41) @@ -26,7 +26,7 @@ const GUEST_PRIVKEY_HEX = process.env.GUEST_PRIVKEY const CHANNEL_EVENT_ID = process.env.CHANNEL_EVENT_ID if (!GUEST_PRIVKEY_HEX || !CHANNEL_EVENT_ID) { - console.error('Usage: GUEST_PRIVKEY= CHANNEL_EVENT_ID= node test-sprout-proxy.mjs') + console.error('Usage: GUEST_PRIVKEY= CHANNEL_EVENT_ID= node test-buzz-proxy.mjs') process.exit(1) } @@ -69,7 +69,7 @@ function list(relay, filters, timeoutMs = 8000) { async function main() { console.log('═'.repeat(64)) - console.log(' nostr-tools v2.23 E2E TEST against sprout-proxy') + console.log(' nostr-tools v2.23 E2E TEST against buzz-proxy') console.log('═'.repeat(64)) console.log(` Relay: ${RELAY_URL}`) console.log(` Pubkey: ${pubkey.slice(0, 24)}...`) @@ -251,7 +251,7 @@ async function main() { } console.log('═'.repeat(64)) console.log(allPass - ? ' 🎉 ALL TESTS PASSED — nostr-tools works with sprout-proxy!' + ? ' 🎉 ALL TESTS PASSED — nostr-tools works with buzz-proxy!' : ' ⚠️ Some tests failed') console.log('═'.repeat(64)) process.exit(allPass ? 0 : 1) diff --git a/scripts/test-video-upload.sh b/scripts/test-video-upload.sh index 642a92440..09c4ffed9 100755 --- a/scripts/test-video-upload.sh +++ b/scripts/test-video-upload.sh @@ -3,7 +3,7 @@ # # Prerequisites: # - Relay running at $RELAY_URL (default: http://localhost:3000) -# - Dev mode (SPROUT_REQUIRE_AUTH_TOKEN=false) or valid API token +# - Dev mode (BUZZ_REQUIRE_AUTH_TOKEN=false) or valid API token # - ffmpeg, nak, curl, jq, shasum on PATH # # Usage: