diff --git a/.env.example b/.env.example index 3d1c9a96d..e18070f01 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,5 @@ # ============================================================================= -# Sprout Backend — Local Development Environment +# Buzz Backend — Local Development Environment # ============================================================================= # Copy this file to .env and adjust as needed: # cp .env.example .env @@ -18,12 +18,12 @@ # ----------------------------------------------------------------------------- # Database (Postgres 17) # ----------------------------------------------------------------------------- -DATABASE_URL=postgres://sprout:sprout_dev@localhost:5432/sprout +DATABASE_URL=postgres://buzz:buzz_dev@localhost:5432/buzz PGHOST=localhost PGPORT=5432 -PGUSER=sprout -PGPASSWORD=sprout_dev -PGDATABASE=sprout +PGUSER=buzz +PGPASSWORD=buzz_dev +PGDATABASE=buzz # ----------------------------------------------------------------------------- # Redis 7 @@ -33,23 +33,23 @@ REDIS_URL=redis://localhost:6379 # ----------------------------------------------------------------------------- # Typesense (search) # ----------------------------------------------------------------------------- -TYPESENSE_API_KEY=sprout_dev_key +TYPESENSE_API_KEY=buzz_dev_key TYPESENSE_URL=http://localhost:8108 # ----------------------------------------------------------------------------- # Relay (WebSocket server) # ----------------------------------------------------------------------------- # Bind address for the relay (host:port) -SPROUT_BIND_ADDR=0.0.0.0:3000 +BUZZ_BIND_ADDR=0.0.0.0:3000 # Public WebSocket URL — used in NIP-42 auth challenges RELAY_URL=ws://localhost:3000 # Stable relay signing key. Set this in dev if you want REST-created forum posts # to keep resolving to the original author across relay restarts. -# SPROUT_RELAY_PRIVATE_KEY=<32-byte hex private key> +# BUZZ_RELAY_PRIVATE_KEY=<32-byte hex private key> # Optional: path to the web UI dist directory. When set, the relay serves # the web frontend at / for browser requests. Leave unset for local dev # (use `just web` for Vite HMR instead). -# SPROUT_WEB_DIR=./web/dist +# BUZZ_WEB_DIR=./web/dist # ----------------------------------------------------------------------------- # Git (NIP-34 bare repositories) @@ -57,7 +57,7 @@ RELAY_URL=ws://localhost:3000 # Root directory for bare git repos. Repos are stored at # {path}/{owner_hex}/{repo_id}.git/. Default: ./repos (relative to CWD). # Set an absolute path to keep repos stable across worktrees. -# SPROUT_GIT_REPO_PATH=./repos +# BUZZ_GIT_REPO_PATH=./repos # ----------------------------------------------------------------------------- # Ephemeral Channels (TTL testing) @@ -65,125 +65,125 @@ RELAY_URL=ws://localhost:3000 # Override the TTL for all ephemeral channels (in seconds). When set, any # channel created with a TTL tag will use this value instead of the # client-provided one. Unset to use the client-provided TTL. -# SPROUT_EPHEMERAL_TTL_OVERRIDE=60 +# BUZZ_EPHEMERAL_TTL_OVERRIDE=60 # How often the reaper checks for expired ephemeral channels (default: 60s). -# SPROUT_REAPER_INTERVAL_SECS=5 +# BUZZ_REAPER_INTERVAL_SECS=5 # ----------------------------------------------------------------------------- # Logging / Tracing # ----------------------------------------------------------------------------- -RUST_LOG=sprout_relay=debug,sprout_db=debug,sprout_auth=debug,sprout_pubsub=debug,tower_http=debug +RUST_LOG=buzz_relay=debug,buzz_db=debug,buzz_auth=debug,buzz_pubsub=debug,tower_http=debug # OTLP tracing endpoint (optional — leave unset to disable) # OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 # ----------------------------------------------------------------------------- -# ACP (Agent Communication Protocol — sprout-acp harness) +# ACP (Agent Communication Protocol — buzz-acp harness) # ----------------------------------------------------------------------------- -# The ACP harness bridges Sprout events to AI agents. Each env var below maps +# The ACP harness bridges Buzz events to AI agents. Each env var below maps # to a CLI flag of the same name (lowercase, hyphens → underscores). All values # are optional unless noted; defaults are shown in comments. # # Quick start: -# SPROUT_PRIVATE_KEY= SPROUT_RELAY_URL=ws://localhost:3000 sprout-acp +# BUZZ_PRIVATE_KEY= BUZZ_RELAY_URL=ws://localhost:3000 buzz-acp # ── Identity & auth ────────────────────────────────────────────────────────── # Nostr private key (hex or bech32). REQUIRED — identifies the agent on the relay. -# SPROUT_PRIVATE_KEY=<32-byte hex or nsec1… private key> +# BUZZ_PRIVATE_KEY=<32-byte hex or nsec1… private key> # Relay WebSocket URL the harness connects to. # Note: the relay itself uses RELAY_URL (above); this is the ACP harness's # connection target — they happen to point at the same place in local dev. -# SPROUT_RELAY_URL=ws://localhost:3000 +# BUZZ_RELAY_URL=ws://localhost:3000 # ── Agent subprocess ───────────────────────────────────────────────────────── # Binary to spawn as the AI agent (e.g. "goose", "codex-acp", "claude-code"). -# SPROUT_ACP_AGENT_COMMAND=goose +# BUZZ_ACP_AGENT_COMMAND=goose # Comma-separated arguments passed to the agent binary. # Goose default: "acp". Codex/Claude default: "" (empty). -# SPROUT_ACP_AGENT_ARGS=acp +# BUZZ_ACP_AGENT_ARGS=acp -# Binary for an optional MCP server sidecar (e.g. sprout-dev-mcp for sprout-agent). -# SPROUT_ACP_MCP_COMMAND= +# Binary for an optional MCP server sidecar (e.g. buzz-dev-mcp for buzz-agent). +# BUZZ_ACP_MCP_COMMAND= # Number of parallel agent subprocesses (1–32). -# SPROUT_ACP_AGENTS=1 +# BUZZ_ACP_AGENTS=1 # Desired LLM model ID. Applied to every new ACP session. -# Use `sprout-acp models` to discover available model IDs. -# SPROUT_ACP_MODEL= +# Use `buzz-acp models` to discover available model IDs. +# BUZZ_ACP_MODEL= # ── Timeouts & sessions ────────────────────────────────────────────────────── # Max seconds per agent turn before timeout (default 320 = ~5 min). -# SPROUT_ACP_TURN_TIMEOUT=320 +# BUZZ_ACP_TURN_TIMEOUT=320 # Max turns per session before proactive rotation. 0 = disabled (rotate only # on MaxTokens / MaxTurnRequests). Recommended: 50 for long-running agents. -# SPROUT_ACP_MAX_TURNS_PER_SESSION=0 +# BUZZ_ACP_MAX_TURNS_PER_SESSION=0 # ── Prompts ────────────────────────────────────────────────────────────────── # System prompt injected into every agent session (inline text). -# SPROUT_ACP_SYSTEM_PROMPT= +# BUZZ_ACP_SYSTEM_PROMPT= # Path to a file containing the system prompt (mutually exclusive with above). -# SPROUT_ACP_SYSTEM_PROMPT_FILE= +# BUZZ_ACP_SYSTEM_PROMPT_FILE= # Message sent to the agent immediately after session creation. -# SPROUT_ACP_INITIAL_MESSAGE= +# BUZZ_ACP_INITIAL_MESSAGE= # ── Heartbeat ──────────────────────────────────────────────────────────────── # Seconds between heartbeat prompts. 0 = disabled. Must be 0 or ≥10. # Recommended: 60 for long-running agents to prevent idle session timeouts. -# SPROUT_ACP_HEARTBEAT_INTERVAL=0 +# BUZZ_ACP_HEARTBEAT_INTERVAL=0 # Heartbeat prompt text (inline). Mutually exclusive with file variant. -# SPROUT_ACP_HEARTBEAT_PROMPT= +# BUZZ_ACP_HEARTBEAT_PROMPT= # Path to a file containing the heartbeat prompt. -# SPROUT_ACP_HEARTBEAT_PROMPT_FILE= +# BUZZ_ACP_HEARTBEAT_PROMPT_FILE= # ── Subscription & filtering ───────────────────────────────────────────────── # Subscribe mode: "mentions" (default), "all", or "config" (rule-based). -# SPROUT_ACP_SUBSCRIBE=mentions +# BUZZ_ACP_SUBSCRIBE=mentions # Comma-separated event kind numbers to subscribe to (overrides mode defaults). -# SPROUT_ACP_KINDS= +# BUZZ_ACP_KINDS= # Comma-separated channel UUIDs to limit subscription scope. -# SPROUT_ACP_CHANNELS= +# BUZZ_ACP_CHANNELS= # Set to true to disable the @-mention filter in mentions mode. -# SPROUT_ACP_NO_MENTION_FILTER=false +# BUZZ_ACP_NO_MENTION_FILTER=false # Path to TOML config file for rule-based subscriptions (config mode). -# SPROUT_ACP_CONFIG=./sprout-acp.toml +# BUZZ_ACP_CONFIG=./buzz-acp.toml # ── Dedup & self-ignore ────────────────────────────────────────────────────── # How to handle duplicate events: "queue" (default) or "drop". -# SPROUT_ACP_DEDUP=queue +# BUZZ_ACP_DEDUP=queue # Set to true to process the agent's own messages (default: ignore self). -# SPROUT_ACP_NO_IGNORE_SELF=false +# BUZZ_ACP_NO_IGNORE_SELF=false # ── Context ────────────────────────────────────────────────────────────────── # Max context messages fetched for thread replies and DMs (0–100). 0 = disabled. -# SPROUT_ACP_CONTEXT_MESSAGE_LIMIT=12 +# BUZZ_ACP_CONTEXT_MESSAGE_LIMIT=12 # ── Presence & typing ──────────────────────────────────────────────────────── # Set to true to disable automatic online/offline presence status. -# SPROUT_ACP_NO_PRESENCE=false +# BUZZ_ACP_NO_PRESENCE=false # Set to true to disable typing indicators while the agent is processing. -# SPROUT_ACP_NO_TYPING=false +# BUZZ_ACP_NO_TYPING=false # ── Advanced tuning ────────────────────────────────────────────────────────── # Event channel buffer capacity (WebSocket → harness). Increase for # high-throughput agents. Minimum 1. -# SPROUT_ACP_EVENT_BUFFER=256 +# BUZZ_ACP_EVENT_BUFFER=256 # ── Legacy aliases ─────────────────────────────────────────────────────────── # These are accepted for backward compatibility but the canonical names above # are preferred: -# SPROUT_ACP_PRIVATE_KEY → SPROUT_PRIVATE_KEY +# BUZZ_ACP_PRIVATE_KEY → BUZZ_PRIVATE_KEY diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 17a418868..b2956dbf1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -169,28 +169,28 @@ jobs: EXTRACT_DIR="${RUNNER_TEMP}/signed-app-extract" rm -rf "$EXTRACT_DIR" && mkdir -p "$EXTRACT_DIR" ditto -x -k "$SIGNED_APP_ZIP" "$EXTRACT_DIR" - rm -rf "${APP_DIR}/Sprout.app" - cp -R "${EXTRACT_DIR}/Sprout.app" "${APP_DIR}/Sprout.app" + rm -rf "${APP_DIR}/Buzz.app" + cp -R "${EXTRACT_DIR}/Buzz.app" "${APP_DIR}/Buzz.app" # Rebuild the updater archive from the signed .app and re-sign it with the Tauri updater key. - rm -f "${APP_DIR}/Sprout.app.tar.gz" "${APP_DIR}/Sprout.app.tar.gz.sig" - (cd "$APP_DIR" && tar -czf Sprout.app.tar.gz Sprout.app) - TARBALL_ABS="$(pwd)/${APP_DIR}/Sprout.app.tar.gz" + rm -f "${APP_DIR}/Buzz.app.tar.gz" "${APP_DIR}/Buzz.app.tar.gz.sig" + (cd "$APP_DIR" && tar -czf Buzz.app.tar.gz Buzz.app) + TARBALL_ABS="$(pwd)/${APP_DIR}/Buzz.app.tar.gz" (cd desktop && pnpm tauri signer sign "$TARBALL_ABS") - name: Verify code signature run: | codesign --verify --deep --strict --verbose=2 \ - desktop/src-tauri/target/release/bundle/macos/Sprout.app + desktop/src-tauri/target/release/bundle/macos/Buzz.app spctl --assess --type execute --verbose=4 \ - desktop/src-tauri/target/release/bundle/macos/Sprout.app + desktop/src-tauri/target/release/bundle/macos/Buzz.app - name: Locate build artifacts id: artifacts run: | BUNDLE_DIR="desktop/src-tauri/target/release/bundle" - # Find the DMG (Tauri names it Sprout__.dmg) + # Find the DMG (Tauri names it Buzz__.dmg) DMG=$(find "$BUNDLE_DIR/dmg" -name '*.dmg' -type f | head -1) if [[ -z "$DMG" ]]; then echo "::error::No DMG found in $BUNDLE_DIR/dmg" @@ -234,11 +234,11 @@ jobs: NOTES=$(awk "/^## v${VERSION}$/,/^## v/" CHANGELOG.md | sed '$d') fi if [[ -z "$NOTES" ]]; then - NOTES="Sprout Desktop v${VERSION}" + NOTES="Buzz Desktop v${VERSION}" fi gh release create "v${VERSION}" \ --target "$RELEASE_SHA" \ - --title "Sprout Desktop v${VERSION}" \ + --title "Buzz Desktop v${VERSION}" \ --notes "$NOTES" \ "$DMG_PATH" @@ -246,7 +246,7 @@ jobs: run: | gh release create buzz-desktop-latest \ --prerelease \ - --title "Sprout Desktop Auto-Update" \ + --title "Buzz 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 buzz-desktop-latest \ @@ -349,12 +349,12 @@ jobs: EXTRACT_DIR="${RUNNER_TEMP}/signed-app-extract-x64" rm -rf "$EXTRACT_DIR" && mkdir -p "$EXTRACT_DIR" ditto -x -k "$SIGNED_APP_ZIP" "$EXTRACT_DIR" - rm -rf "${APP_DIR}/Sprout.app" - cp -R "${EXTRACT_DIR}/Sprout.app" "${APP_DIR}/Sprout.app" + rm -rf "${APP_DIR}/Buzz.app" + cp -R "${EXTRACT_DIR}/Buzz.app" "${APP_DIR}/Buzz.app" - name: Verify code signature run: | - APP_DIR="desktop/src-tauri/target/${TARGET}/release/bundle/macos/Sprout.app" + APP_DIR="desktop/src-tauri/target/${TARGET}/release/bundle/macos/Buzz.app" codesign --verify --deep --strict --verbose=2 "$APP_DIR" spctl --assess --type execute --verbose=4 "$APP_DIR" diff --git a/RELEASING.md b/RELEASING.md index 0d9db50e3..4697e9838 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,4 +1,4 @@ -# Releasing Sprout Desktop +# Releasing Buzz Desktop ## Quick Start diff --git a/desktop/README.md b/desktop/README.md index dd5700a29..d53698a9d 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -1,4 +1,4 @@ -# Sprout +# Buzz Desktop chat shell with: diff --git a/desktop/index.html b/desktop/index.html index 603ec5976..a2073d47f 100644 --- a/desktop/index.html +++ b/desktop/index.html @@ -2,7 +2,7 @@ - + diff --git a/desktop/package.json b/desktop/package.json index e5bf264e8..365e8f8d1 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,5 +1,5 @@ { - "name": "sprout", + "name": "buzz", "private": true, "version": "0.3.16", "type": "module", diff --git a/desktop/public/buzz.svg b/desktop/public/buzz.svg new file mode 100644 index 000000000..8eaee9320 --- /dev/null +++ b/desktop/public/buzz.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/desktop/public/sprout.svg b/desktop/public/sprout.svg deleted file mode 100644 index 079a16ddf..000000000 --- a/desktop/public/sprout.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/desktop/scripts/build-release-config.mjs b/desktop/scripts/build-release-config.mjs index 9c642d0f5..389d18aec 100644 --- a/desktop/scripts/build-release-config.mjs +++ b/desktop/scripts/build-release-config.mjs @@ -12,7 +12,7 @@ import { resolve } from "node:path"; // 2. bundle.createUpdaterArtifacts = true so Tauri produces the .tar.gz // archive and .sig signature during the build. // 3. plugins.updater with the public key and endpoint from env vars. -// Both SPROUT_UPDATER_PUBLIC_KEY and SPROUT_UPDATER_ENDPOINT are required - +// Both BUZZ_UPDATER_PUBLIC_KEY and BUZZ_UPDATER_ENDPOINT are required - // the script fails if either is missing (OSS builds always ship with updater). // // Apple code signing and notarization happen post-build via @@ -24,12 +24,12 @@ const outputConfigPath = resolve( "src-tauri/tauri.release.conf.json", ); -const updaterPubkey = process.env.SPROUT_UPDATER_PUBLIC_KEY; -const updaterEndpoint = process.env.SPROUT_UPDATER_ENDPOINT; +const updaterPubkey = process.env.BUZZ_UPDATER_PUBLIC_KEY; +const updaterEndpoint = process.env.BUZZ_UPDATER_ENDPOINT; const missing = []; -if (!updaterPubkey) missing.push("SPROUT_UPDATER_PUBLIC_KEY"); -if (!updaterEndpoint) missing.push("SPROUT_UPDATER_ENDPOINT"); +if (!updaterPubkey) missing.push("BUZZ_UPDATER_PUBLIC_KEY"); +if (!updaterEndpoint) missing.push("BUZZ_UPDATER_ENDPOINT"); if (missing.length > 0) { console.error( `Error: required environment variable(s) missing: ${missing.join(", ")}`, diff --git a/desktop/scripts/generate-oss-latest-json.sh b/desktop/scripts/generate-oss-latest-json.sh index cedde5dcf..da0f615e7 100755 --- a/desktop/scripts/generate-oss-latest-json.sh +++ b/desktop/scripts/generate-oss-latest-json.sh @@ -14,7 +14,7 @@ ARCHIVE_URL="$3" # only. Supporting Intel Macs (darwin-x86_64) would require a matrix build. jq -n \ --arg version "$VERSION" \ - --arg notes "Sprout v$VERSION" \ + --arg notes "Buzz v$VERSION" \ --arg pub_date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ --arg signature "$(cat "$SIG_FILE")" \ --arg url "$ARCHIVE_URL" \ diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock index cf7753b23..03cba426b 100644 --- a/desktop/src-tauri/Cargo.lock +++ b/desktop/src-tauri/Cargo.lock @@ -844,6 +844,68 @@ dependencies = [ "zeroize", ] +[[package]] +name = "buzz-desktop" +version = "0.3.16" +dependencies = [ + "anyhow", + "atomic-write-file", + "audioadapter-buffers", + "axum", + "base64 0.22.1", + "buzz-core", + "buzz-persona", + "buzz-sdk", + "bzip2 0.6.1", + "chrono", + "ctrlc", + "dirs", + "earshot", + "futures-util", + "hex", + "infer", + "libc", + "mesh-llm-host-runtime", + "mesh-llm-sdk", + "neteq", + "nostr", + "objc2-app-kit", + "opus", + "png 0.18.1", + "regex", + "reqwest 0.13.4", + "rodio", + "rubato", + "serde", + "serde_json", + "serde_yaml", + "sha2 0.11.0", + "sherpa-onnx", + "strip-ansi-escapes", + "tar", + "tauri", + "tauri-build", + "tauri-plugin-deep-link", + "tauri-plugin-dialog", + "tauri-plugin-global-shortcut", + "tauri-plugin-notification", + "tauri-plugin-opener", + "tauri-plugin-process", + "tauri-plugin-single-instance", + "tauri-plugin-updater", + "tauri-plugin-websocket", + "tauri-plugin-window-state", + "tempfile", + "tokio", + "tokio-tungstenite 0.29.0", + "tokio-util", + "url", + "uuid", + "windows-sys 0.61.2", + "zeroize", + "zip 8.6.0", +] + [[package]] name = "buzz-persona" version = "0.1.0" @@ -8220,68 +8282,6 @@ dependencies = [ "der", ] -[[package]] -name = "sprout-desktop" -version = "0.3.16" -dependencies = [ - "anyhow", - "atomic-write-file", - "audioadapter-buffers", - "axum", - "base64 0.22.1", - "buzz-core", - "buzz-persona", - "buzz-sdk", - "bzip2 0.6.1", - "chrono", - "ctrlc", - "dirs", - "earshot", - "futures-util", - "hex", - "infer", - "libc", - "mesh-llm-host-runtime", - "mesh-llm-sdk", - "neteq", - "nostr", - "objc2-app-kit", - "opus", - "png 0.18.1", - "regex", - "reqwest 0.13.4", - "rodio", - "rubato", - "serde", - "serde_json", - "serde_yaml", - "sha2 0.11.0", - "sherpa-onnx", - "strip-ansi-escapes", - "tar", - "tauri", - "tauri-build", - "tauri-plugin-deep-link", - "tauri-plugin-dialog", - "tauri-plugin-global-shortcut", - "tauri-plugin-notification", - "tauri-plugin-opener", - "tauri-plugin-process", - "tauri-plugin-single-instance", - "tauri-plugin-updater", - "tauri-plugin-websocket", - "tauri-plugin-window-state", - "tempfile", - "tokio", - "tokio-tungstenite 0.29.0", - "tokio-util", - "url", - "uuid", - "windows-sys 0.61.2", - "zeroize", - "zip 8.6.0", -] - [[package]] name = "sse-stream" version = "0.2.3" diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index 0e2d63bfe..8ce2ada77 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -1,9 +1,9 @@ [workspace] [package] -name = "sprout-desktop" +name = "buzz-desktop" version = "0.3.16" -description = "Sprout desktop app" +description = "Buzz desktop app" authors = ["you"] edition = "2021" @@ -13,7 +13,7 @@ edition = "2021" # The `_lib` suffix may seem redundant but it is necessary # to make the lib name unique and wouldn't conflict with the bin name. # This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 -name = "sprout_lib" +name = "buzz_lib" crate-type = ["staticlib", "cdylib", "rlib"] [features] @@ -61,9 +61,9 @@ nostr = { version = "0.44", features = ["nip44"] } zeroize = "1" reqwest = { version = "0.13", features = ["json", "query", "stream"] } url = "2" -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" } +buzz_core_pkg = { package = "buzz-core", path = "../../crates/buzz-core" } +buzz_persona_pkg = { package = "buzz-persona", path = "../../crates/buzz-persona" } +buzz_sdk_pkg = { 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/desktop/src-tauri/Info.plist b/desktop/src-tauri/Info.plist index b46d11a26..61604f062 100644 --- a/desktop/src-tauri/Info.plist +++ b/desktop/src-tauri/Info.plist @@ -3,10 +3,10 @@ CFBundleDisplayName - Sprout + Buzz CFBundleName - Sprout + Buzz NSMicrophoneUsageDescription - Sprout needs microphone access for voice huddles. + Buzz needs microphone access for voice huddles. diff --git a/desktop/src-tauri/build.rs b/desktop/src-tauri/build.rs index aaef37143..cb5fcb48d 100644 --- a/desktop/src-tauri/build.rs +++ b/desktop/src-tauri/build.rs @@ -1,11 +1,11 @@ fn main() { println!("cargo:rerun-if-env-changed=BUZZ_RELAY_URL"); println!("cargo:rerun-if-env-changed=BUZZ_RELAY_HTTP"); - println!("cargo:rerun-if-env-changed=SPROUT_UPDATER_PUBLIC_KEY"); - println!("cargo:rerun-if-env-changed=SPROUT_UPDATER_ENDPOINT"); - println!("cargo:rerun-if-env-changed=SPROUT_BUILD_DATABRICKS_HOST"); - println!("cargo:rerun-if-env-changed=SPROUT_BUILD_DATABRICKS_MODEL"); - println!("cargo:rustc-check-cfg=cfg(sprout_updater_enabled)"); + println!("cargo:rerun-if-env-changed=BUZZ_UPDATER_PUBLIC_KEY"); + println!("cargo:rerun-if-env-changed=BUZZ_UPDATER_ENDPOINT"); + println!("cargo:rerun-if-env-changed=BUZZ_BUILD_DATABRICKS_HOST"); + println!("cargo:rerun-if-env-changed=BUZZ_BUILD_DATABRICKS_MODEL"); + println!("cargo:rustc-check-cfg=cfg(buzz_updater_enabled)"); if let Ok(relay_url) = std::env::var("BUZZ_RELAY_URL") { println!("cargo:rustc-env=BUZZ_DESKTOP_BUILD_RELAY_URL={relay_url}"); @@ -15,25 +15,25 @@ fn main() { println!("cargo:rustc-env=BUZZ_DESKTOP_BUILD_RELAY_HTTP={relay_http}"); } - if let Ok(host) = std::env::var("SPROUT_BUILD_DATABRICKS_HOST") { - println!("cargo:rustc-env=SPROUT_DESKTOP_BUILD_DATABRICKS_HOST={host}"); + if let Ok(host) = std::env::var("BUZZ_BUILD_DATABRICKS_HOST") { + println!("cargo:rustc-env=BUZZ_DESKTOP_BUILD_DATABRICKS_HOST={host}"); } - if let Ok(model) = std::env::var("SPROUT_BUILD_DATABRICKS_MODEL") { - println!("cargo:rustc-env=SPROUT_DESKTOP_BUILD_DATABRICKS_MODEL={model}"); + if let Ok(model) = std::env::var("BUZZ_BUILD_DATABRICKS_MODEL") { + println!("cargo:rustc-env=BUZZ_DESKTOP_BUILD_DATABRICKS_MODEL={model}"); } - let updater_public_key = std::env::var("SPROUT_UPDATER_PUBLIC_KEY") + let updater_public_key = std::env::var("BUZZ_UPDATER_PUBLIC_KEY") .ok() .map(|value| value.trim().to_string()) .filter(|value| !value.is_empty()); - let updater_endpoint = std::env::var("SPROUT_UPDATER_ENDPOINT") + let updater_endpoint = std::env::var("BUZZ_UPDATER_ENDPOINT") .ok() .map(|value| value.trim().to_string()) .filter(|value| !value.is_empty()); if updater_public_key.is_some() && updater_endpoint.is_some() { - println!("cargo:rustc-cfg=sprout_updater_enabled"); + println!("cargo:rustc-cfg=buzz_updater_enabled"); } tauri_build::build() diff --git a/desktop/src-tauri/icons/128x128.png b/desktop/src-tauri/icons/128x128.png index 8ed6c1568..3dc83a86e 100644 Binary files a/desktop/src-tauri/icons/128x128.png and b/desktop/src-tauri/icons/128x128.png differ diff --git a/desktop/src-tauri/icons/128x128@2x.png b/desktop/src-tauri/icons/128x128@2x.png index ba7bd53b9..059434892 100644 Binary files a/desktop/src-tauri/icons/128x128@2x.png and b/desktop/src-tauri/icons/128x128@2x.png differ diff --git a/desktop/src-tauri/icons/32x32.png b/desktop/src-tauri/icons/32x32.png index a63e6d7a2..f6273ef38 100644 Binary files a/desktop/src-tauri/icons/32x32.png and b/desktop/src-tauri/icons/32x32.png differ diff --git a/desktop/src-tauri/icons/64x64.png b/desktop/src-tauri/icons/64x64.png index 6fbecf1d2..c48f2d5c6 100644 Binary files a/desktop/src-tauri/icons/64x64.png and b/desktop/src-tauri/icons/64x64.png differ diff --git a/desktop/src-tauri/icons/Square107x107Logo.png b/desktop/src-tauri/icons/Square107x107Logo.png index 6eb074d09..3ad9aa66c 100644 Binary files a/desktop/src-tauri/icons/Square107x107Logo.png and b/desktop/src-tauri/icons/Square107x107Logo.png differ diff --git a/desktop/src-tauri/icons/Square142x142Logo.png b/desktop/src-tauri/icons/Square142x142Logo.png index 8d8672baa..2b8eab9ee 100644 Binary files a/desktop/src-tauri/icons/Square142x142Logo.png and b/desktop/src-tauri/icons/Square142x142Logo.png differ diff --git a/desktop/src-tauri/icons/Square150x150Logo.png b/desktop/src-tauri/icons/Square150x150Logo.png index 40b116d8d..e46260d29 100644 Binary files a/desktop/src-tauri/icons/Square150x150Logo.png and b/desktop/src-tauri/icons/Square150x150Logo.png differ diff --git a/desktop/src-tauri/icons/Square284x284Logo.png b/desktop/src-tauri/icons/Square284x284Logo.png index 77f479007..2bbbe0d2e 100644 Binary files a/desktop/src-tauri/icons/Square284x284Logo.png and b/desktop/src-tauri/icons/Square284x284Logo.png differ diff --git a/desktop/src-tauri/icons/Square30x30Logo.png b/desktop/src-tauri/icons/Square30x30Logo.png index d0dfdb903..f7a711237 100644 Binary files a/desktop/src-tauri/icons/Square30x30Logo.png and b/desktop/src-tauri/icons/Square30x30Logo.png differ diff --git a/desktop/src-tauri/icons/Square310x310Logo.png b/desktop/src-tauri/icons/Square310x310Logo.png index 868a9a382..2786fde22 100644 Binary files a/desktop/src-tauri/icons/Square310x310Logo.png and b/desktop/src-tauri/icons/Square310x310Logo.png differ diff --git a/desktop/src-tauri/icons/Square44x44Logo.png b/desktop/src-tauri/icons/Square44x44Logo.png index bf4881bc9..783d8e4e3 100644 Binary files a/desktop/src-tauri/icons/Square44x44Logo.png and b/desktop/src-tauri/icons/Square44x44Logo.png differ diff --git a/desktop/src-tauri/icons/Square71x71Logo.png b/desktop/src-tauri/icons/Square71x71Logo.png index 43313b472..b3b37bef3 100644 Binary files a/desktop/src-tauri/icons/Square71x71Logo.png and b/desktop/src-tauri/icons/Square71x71Logo.png differ diff --git a/desktop/src-tauri/icons/Square89x89Logo.png b/desktop/src-tauri/icons/Square89x89Logo.png index e6460683d..436dbea88 100644 Binary files a/desktop/src-tauri/icons/Square89x89Logo.png and b/desktop/src-tauri/icons/Square89x89Logo.png differ diff --git a/desktop/src-tauri/icons/StoreLogo.png b/desktop/src-tauri/icons/StoreLogo.png index a87f16dea..878b90d0a 100644 Binary files a/desktop/src-tauri/icons/StoreLogo.png and b/desktop/src-tauri/icons/StoreLogo.png differ diff --git a/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png index f54dfd401..3cc375095 100644 Binary files a/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png and b/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ diff --git a/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png index 276bc9625..bc5c7153e 100644 Binary files a/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png and b/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png index 9f22a9f0a..84efd0b18 100644 Binary files a/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png and b/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png index 47a91a100..81283d034 100644 Binary files a/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png and b/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ diff --git a/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png index 0ce6fe0da..5c11514c1 100644 Binary files a/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png and b/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png index 26f3ffce8..135fcc884 100644 Binary files a/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png and b/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png index 47a779f44..9e1e5f2e6 100644 Binary files a/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png and b/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png index 8ad065422..c9cec30e2 100644 Binary files a/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png and b/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png index cc8bca4fd..cf2f8832f 100644 Binary files a/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png and b/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png index bad8ea6b3..fc9fd7fa6 100644 Binary files a/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png and b/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png index a194a3f8e..367ec1a18 100644 Binary files a/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png and b/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png index bad8ea6b3..0b0701084 100644 Binary files a/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png and b/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png index 054aeb042..650d42691 100644 Binary files a/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png and b/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png index a7397c296..06a23a314 100644 Binary files a/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png and b/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png index 054aeb042..0abbb8ebb 100644 Binary files a/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png and b/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/desktop/src-tauri/icons/android/values/ic_launcher_background.xml b/desktop/src-tauri/icons/android/values/ic_launcher_background.xml index ea9c223a6..f432ea70d 100644 --- a/desktop/src-tauri/icons/android/values/ic_launcher_background.xml +++ b/desktop/src-tauri/icons/android/values/ic_launcher_background.xml @@ -1,4 +1,4 @@ - #fff + #050506 \ No newline at end of file diff --git a/desktop/src-tauri/icons/buzz-source.png b/desktop/src-tauri/icons/buzz-source.png new file mode 100644 index 000000000..004236528 Binary files /dev/null and b/desktop/src-tauri/icons/buzz-source.png differ diff --git a/desktop/src-tauri/icons/icon.icns b/desktop/src-tauri/icons/icon.icns index 07b2e16d0..12e81456e 100644 Binary files a/desktop/src-tauri/icons/icon.icns and b/desktop/src-tauri/icons/icon.icns differ diff --git a/desktop/src-tauri/icons/icon.ico b/desktop/src-tauri/icons/icon.ico index 138c35c8c..6b9f750de 100644 Binary files a/desktop/src-tauri/icons/icon.ico and b/desktop/src-tauri/icons/icon.ico differ diff --git a/desktop/src-tauri/icons/icon.png b/desktop/src-tauri/icons/icon.png index f116cb4b6..d6cc19e83 100644 Binary files a/desktop/src-tauri/icons/icon.png and b/desktop/src-tauri/icons/icon.png differ diff --git a/desktop/src-tauri/icons/ios/AppIcon-20x20@1x.png b/desktop/src-tauri/icons/ios/AppIcon-20x20@1x.png index 925d67740..95b2cda20 100644 Binary files a/desktop/src-tauri/icons/ios/AppIcon-20x20@1x.png and b/desktop/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/desktop/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/desktop/src-tauri/icons/ios/AppIcon-20x20@2x-1.png index 96701250d..b46df45f0 100644 Binary files a/desktop/src-tauri/icons/ios/AppIcon-20x20@2x-1.png and b/desktop/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/desktop/src-tauri/icons/ios/AppIcon-20x20@2x.png b/desktop/src-tauri/icons/ios/AppIcon-20x20@2x.png index 96701250d..b46df45f0 100644 Binary files a/desktop/src-tauri/icons/ios/AppIcon-20x20@2x.png and b/desktop/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/desktop/src-tauri/icons/ios/AppIcon-20x20@3x.png b/desktop/src-tauri/icons/ios/AppIcon-20x20@3x.png index 2c0e50700..c86a6d9ab 100644 Binary files a/desktop/src-tauri/icons/ios/AppIcon-20x20@3x.png and b/desktop/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/desktop/src-tauri/icons/ios/AppIcon-29x29@1x.png b/desktop/src-tauri/icons/ios/AppIcon-29x29@1x.png index 12d38f5e5..b8faba669 100644 Binary files a/desktop/src-tauri/icons/ios/AppIcon-29x29@1x.png and b/desktop/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/desktop/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/desktop/src-tauri/icons/ios/AppIcon-29x29@2x-1.png index 1760c0fd2..b61ec5b15 100644 Binary files a/desktop/src-tauri/icons/ios/AppIcon-29x29@2x-1.png and b/desktop/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/desktop/src-tauri/icons/ios/AppIcon-29x29@2x.png b/desktop/src-tauri/icons/ios/AppIcon-29x29@2x.png index 1760c0fd2..b61ec5b15 100644 Binary files a/desktop/src-tauri/icons/ios/AppIcon-29x29@2x.png and b/desktop/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/desktop/src-tauri/icons/ios/AppIcon-29x29@3x.png b/desktop/src-tauri/icons/ios/AppIcon-29x29@3x.png index 5ac9160f2..843deddfa 100644 Binary files a/desktop/src-tauri/icons/ios/AppIcon-29x29@3x.png and b/desktop/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/desktop/src-tauri/icons/ios/AppIcon-40x40@1x.png b/desktop/src-tauri/icons/ios/AppIcon-40x40@1x.png index 96701250d..b46df45f0 100644 Binary files a/desktop/src-tauri/icons/ios/AppIcon-40x40@1x.png and b/desktop/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/desktop/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/desktop/src-tauri/icons/ios/AppIcon-40x40@2x-1.png index 9cfc369ee..fec2d3794 100644 Binary files a/desktop/src-tauri/icons/ios/AppIcon-40x40@2x-1.png and b/desktop/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/desktop/src-tauri/icons/ios/AppIcon-40x40@2x.png b/desktop/src-tauri/icons/ios/AppIcon-40x40@2x.png index 2ddae2912..fec2d3794 100644 Binary files a/desktop/src-tauri/icons/ios/AppIcon-40x40@2x.png and b/desktop/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/desktop/src-tauri/icons/ios/AppIcon-40x40@3x.png b/desktop/src-tauri/icons/ios/AppIcon-40x40@3x.png index 4f6a5c552..35bf0a56a 100644 Binary files a/desktop/src-tauri/icons/ios/AppIcon-40x40@3x.png and b/desktop/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/desktop/src-tauri/icons/ios/AppIcon-512@2x.png b/desktop/src-tauri/icons/ios/AppIcon-512@2x.png index 966b452fd..2ee3675d7 100644 Binary files a/desktop/src-tauri/icons/ios/AppIcon-512@2x.png and b/desktop/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/desktop/src-tauri/icons/ios/AppIcon-60x60@2x.png b/desktop/src-tauri/icons/ios/AppIcon-60x60@2x.png index 4f6a5c552..35bf0a56a 100644 Binary files a/desktop/src-tauri/icons/ios/AppIcon-60x60@2x.png and b/desktop/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/desktop/src-tauri/icons/ios/AppIcon-60x60@3x.png b/desktop/src-tauri/icons/ios/AppIcon-60x60@3x.png index 765d8bb13..97ef3b888 100644 Binary files a/desktop/src-tauri/icons/ios/AppIcon-60x60@3x.png and b/desktop/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/desktop/src-tauri/icons/ios/AppIcon-76x76@1x.png b/desktop/src-tauri/icons/ios/AppIcon-76x76@1x.png index 284e5dddb..29852fe45 100644 Binary files a/desktop/src-tauri/icons/ios/AppIcon-76x76@1x.png and b/desktop/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/desktop/src-tauri/icons/ios/AppIcon-76x76@2x.png b/desktop/src-tauri/icons/ios/AppIcon-76x76@2x.png index ed5869dd8..7170dedfc 100644 Binary files a/desktop/src-tauri/icons/ios/AppIcon-76x76@2x.png and b/desktop/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/desktop/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/desktop/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png index dbfbc97ab..3f030d7dd 100644 Binary files a/desktop/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png and b/desktop/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/desktop/src-tauri/icons/sprout-art.png b/desktop/src-tauri/icons/sprout-art.png deleted file mode 100644 index 65bb6059a..000000000 Binary files a/desktop/src-tauri/icons/sprout-art.png and /dev/null differ diff --git a/desktop/src-tauri/icons/sprout-rounded.svg b/desktop/src-tauri/icons/sprout-rounded.svg deleted file mode 100644 index d5c546c91..000000000 --- a/desktop/src-tauri/icons/sprout-rounded.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - diff --git a/desktop/src-tauri/icons/sprout-source.png b/desktop/src-tauri/icons/sprout-source.png deleted file mode 100644 index 22b097412..000000000 Binary files a/desktop/src-tauri/icons/sprout-source.png and /dev/null differ diff --git a/desktop/src-tauri/src/app_state.rs b/desktop/src-tauri/src/app_state.rs index 24512c51b..a20dc815e 100644 --- a/desktop/src-tauri/src/app_state.rs +++ b/desktop/src-tauri/src/app_state.rs @@ -35,7 +35,7 @@ pub struct AppState { pub media_proxy_port: AtomicU16, /// IOKit power assertion state — prevents idle sleep while agents run. pub prevent_sleep: Arc>, - /// In-process mesh-llm node started by Sprout Desktop. + /// In-process mesh-llm node started by Buzz Desktop. #[cfg(feature = "mesh-llm")] pub mesh_llm_runtime: AsyncMutex>, /// Runtime-owned relay-mesh control plane (call-me-now listener + connect @@ -48,16 +48,16 @@ pub struct AppState { pub fn build_app_state() -> AppState { // Env var takes precedence (dev/CI). If absent, resolve_persisted_identity() // in setup() will replace the ephemeral placeholder with a persisted key. - let (keys, source) = match std::env::var("SPROUT_PRIVATE_KEY") { + let (keys, source) = match std::env::var("BUZZ_PRIVATE_KEY") { Ok(nsec) => match Keys::parse(nsec.trim()) { Ok(keys) => (keys, "configured"), Err(error) => { - eprintln!("sprout-desktop: invalid SPROUT_PRIVATE_KEY: {error}"); + eprintln!("buzz-desktop: invalid BUZZ_PRIVATE_KEY: {error}"); (Keys::generate(), "ephemeral") } }, Err(std::env::VarError::NotUnicode(_)) => { - eprintln!("sprout-desktop: SPROUT_PRIVATE_KEY contains invalid UTF-8"); + eprintln!("buzz-desktop: BUZZ_PRIVATE_KEY contains invalid UTF-8"); (Keys::generate(), "ephemeral") } Err(std::env::VarError::NotPresent) => (Keys::generate(), "ephemeral"), @@ -65,7 +65,7 @@ pub fn build_app_state() -> AppState { if source == "configured" { eprintln!( - "sprout-desktop: configured identity pubkey {}", + "buzz-desktop: configured identity pubkey {}", keys.public_key().to_hex() ); } @@ -126,7 +126,7 @@ impl AppState { /// Resolve the user's identity key from the app data directory. /// -/// Priority: `SPROUT_PRIVATE_KEY` env var (already handled in `build_app_state`) +/// Priority: `BUZZ_PRIVATE_KEY` env var (already handled in `build_app_state`) /// → `{app_data_dir}/identity.key` file → generate + save. /// /// Writes use `atomic-write-file` which handles temp file creation, fsync, @@ -135,7 +135,7 @@ pub fn resolve_persisted_identity(app: &AppHandle, state: &AppState) -> Result<( // Only skip file-based resolution if the env var was present AND parsed // successfully. A malformed env var should fall through to the persisted // key rather than leaving the app on an ephemeral identity. - if let Ok(nsec) = std::env::var("SPROUT_PRIVATE_KEY") { + if let Ok(nsec) = std::env::var("BUZZ_PRIVATE_KEY") { if Keys::parse(nsec.trim()).is_ok() { return Ok(()); } @@ -153,7 +153,7 @@ pub fn resolve_persisted_identity(app: &AppHandle, state: &AppState) -> Result<( match load_key_file(&key_path) { Ok(keys) => { eprintln!( - "sprout-desktop: persisted identity pubkey {}", + "buzz-desktop: persisted identity pubkey {}", keys.public_key().to_hex() ); *state.keys.lock().map_err(|e| e.to_string())? = keys; @@ -168,7 +168,7 @@ pub fn resolve_persisted_identity(app: &AppHandle, state: &AppState) -> Result<( .unwrap_or(0); let bad_name = format!("identity.key.bad.{ts}"); eprintln!( - "sprout-desktop: corrupt identity.key ({error}), quarantining to {bad_name}" + "buzz-desktop: corrupt identity.key ({error}), quarantining to {bad_name}" ); let bad_path = data_dir.join(bad_name); if std::fs::rename(&key_path, &bad_path).is_err() { @@ -183,7 +183,7 @@ pub fn resolve_persisted_identity(app: &AppHandle, state: &AppState) -> Result<( save_key_file(&key_path, &keys)?; eprintln!( - "sprout-desktop: generated and saved identity pubkey {}", + "buzz-desktop: generated and saved identity pubkey {}", keys.public_key().to_hex() ); *state.keys.lock().map_err(|e| e.to_string())? = keys; diff --git a/desktop/src-tauri/src/commands/agent_models.rs b/desktop/src-tauri/src/commands/agent_models.rs index a24e1d404..6eee455c7 100644 --- a/desktop/src-tauri/src/commands/agent_models.rs +++ b/desktop/src-tauri/src/commands/agent_models.rs @@ -16,7 +16,7 @@ use crate::{ util::now_iso, }; -/// Query available models from an agent via `sprout-acp models --json`. +/// Query available models from an agent via `buzz-acp models --json`. /// /// Spawns a short-lived subprocess (no relay connection needed). The subprocess /// starts the agent, queries its model catalog, and exits. ~2-5s total. @@ -92,14 +92,14 @@ pub async fn get_agent_models( } } } - // User env layering — written LAST so it overrides any Sprout-set env above. + // User env layering — written LAST so it overrides any Buzz-set env above. for (k, v) in &merged_env { cmd.env(k, v); } cmd.stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .output() - .map_err(|e| format!("failed to spawn sprout-acp models: {e}")) + .map_err(|e| format!("failed to spawn buzz-acp models: {e}")) }) .await .map_err(|e| format!("model discovery task failed: {e}"))? @@ -113,7 +113,7 @@ pub async fn get_agent_models( let stderr_redacted = crate::managed_agents::redact_env_values_in(stderr.as_ref(), &env_for_redaction); return Err(format!( - "sprout-acp models failed (exit {}): {stderr_redacted}", + "buzz-acp models failed (exit {}): {stderr_redacted}", output.status.code().unwrap_or(-1) )); } @@ -270,7 +270,7 @@ pub async fn update_managed_agent( { Ok(()) => None, Err(e) => { - eprintln!("sprout-desktop: relay profile sync failed after rename: {e}"); + eprintln!("buzz-desktop: relay profile sync failed after rename: {e}"); Some(e) } } @@ -286,7 +286,7 @@ pub async fn update_managed_agent( // ── Model normalization ─────────────────────────────────────────────────────── -/// Normalize raw `sprout-acp models --json` output into a typed DTO for the frontend. +/// Normalize raw `buzz-acp models --json` output into a typed DTO for the frontend. /// /// Merges models from both ACP paths (stable configOptions + unstable SessionModelState), /// deduplicates by ID (stable takes precedence), and returns a unified list. diff --git a/desktop/src-tauri/src/commands/agents.rs b/desktop/src-tauri/src/commands/agents.rs index 9bbc7f6c7..76f34bc59 100644 --- a/desktop/src-tauri/src/commands/agents.rs +++ b/desktop/src-tauri/src/commands/agents.rs @@ -20,7 +20,7 @@ use crate::{ }; /// Read the workspace owner's pubkey hex from app state without holding the -/// lock for longer than necessary. Used to populate `SPROUT_ACP_AGENT_OWNER` +/// lock for longer than necessary. Used to populate `BUZZ_ACP_AGENT_OWNER` /// as a fallback for legacy agent records that have no NIP-OA `auth_tag`. fn workspace_owner_hex(state: &AppState) -> Result { let keys = state.keys.lock().map_err(|e| e.to_string())?; @@ -313,7 +313,7 @@ pub async fn create_managed_agent( crate::managed_agents::validate_user_env_keys(&input.env_vars)?; // Validate & normalize the respond-to allowlist BEFORE any side effects. - // The harness has its own validator (sprout-acp/src/config.rs) but we want + // The harness has its own validator (buzz-acp/src/config.rs) but we want // to catch malformed input at the boundary so the agent never tries to // start with a list that will crash it on launch. let respond_to_allowlist = @@ -384,12 +384,12 @@ pub async fn create_managed_agent( // No tokens are minted. Fail closed: bad auth tag → don't create agent. let auth_tag = { let owner_keys = state.keys.lock().map_err(|e| e.to_string())?; - // Bridge nostr 0.37 → 0.36 (sprout-sdk) via hex round-trip. + // Bridge nostr 0.37 → 0.36 (buzz-sdk) via hex round-trip. let compat_owner = nostr::Keys::parse(&owner_keys.secret_key().to_secret_hex()) .map_err(|e| format!("failed to bridge owner keys: {e}"))?; let compat_agent = nostr::PublicKey::from_hex(&agent_keys.public_key().to_hex()) .map_err(|e| format!("failed to bridge agent pubkey: {e}"))?; - let tag = sprout_sdk::nip_oa::compute_auth_tag(&compat_owner, &compat_agent, "") + let tag = buzz_sdk_pkg::nip_oa::compute_auth_tag(&compat_owner, &compat_agent, "") .map_err(|e| format!("failed to compute NIP-OA auth tag: {e}"))?; Some(tag) }; @@ -647,7 +647,7 @@ pub async fn create_managed_agent( if let Err(persist_err) = persist_create_deploy_error(&app, &state, &pubkey, &e) { eprintln!( - "sprout-desktop: failed to persist deploy-prep error for {pubkey}: {persist_err}" + "buzz-desktop: failed to persist deploy-prep error for {pubkey}: {persist_err}" ); } Some(e) @@ -834,7 +834,7 @@ pub async fn start_managed_agent( .await { eprintln!( - "sprout-desktop: profile reconciliation failed for agent {reconcile_pubkey}: {e}" + "buzz-desktop: profile reconciliation failed for agent {reconcile_pubkey}: {e}" ); } }); @@ -1103,7 +1103,7 @@ pub fn discover_backend_providers() -> Vec { #[tauri::command] pub async fn probe_backend_provider(binary_path: String) -> Result { - // Validate that the requested path is actually a discovered sprout-backend-* binary. + // Validate that the requested path is actually a discovered buzz-backend-* binary. // This prevents arbitrary binary execution via a compromised frontend or IPC. let candidates = discover_provider_candidates(); let path = std::path::PathBuf::from(&binary_path); @@ -1115,7 +1115,7 @@ pub async fn probe_backend_provider(binary_path: String) -> Result) -> Result let events = query_relay( &state, &[serde_json::json!({ - "kinds": [sprout_core::kind::KIND_DM_VISIBILITY], + "kinds": [buzz_core_pkg::kind::KIND_DM_VISIBILITY], "#p": [&my_pubkey], "limit": 1, })], diff --git a/desktop/src-tauri/src/commands/engrams.rs b/desktop/src-tauri/src/commands/engrams.rs index c10c2c483..13e9ecb96 100644 --- a/desktop/src-tauri/src/commands/engrams.rs +++ b/desktop/src-tauri/src/commands/engrams.rs @@ -22,8 +22,8 @@ use nostr::PublicKey; use serde::Serialize; use tauri::{AppHandle, State}; -use sprout_core::engram::{self, extract_refs, select_head, validate_and_decrypt, Body}; -use sprout_core::kind::KIND_AGENT_ENGRAM; +use buzz_core_pkg::engram::{self, extract_refs, select_head, validate_and_decrypt, Body}; +use buzz_core_pkg::kind::KIND_AGENT_ENGRAM; use crate::{app_state::AppState, managed_agents::load_managed_agents, relay::query_relay}; diff --git a/desktop/src-tauri/src/commands/identity.rs b/desktop/src-tauri/src/commands/identity.rs index f883298e1..4c0e6ce53 100644 --- a/desktop/src-tauri/src/commands/identity.rs +++ b/desktop/src-tauri/src/commands/identity.rs @@ -37,10 +37,10 @@ pub fn get_default_relay_url() -> String { #[tauri::command] pub fn is_shared_identity() -> bool { - std::env::var("SPROUT_SHARE_IDENTITY") + std::env::var("BUZZ_SHARE_IDENTITY") .map(|v| v == "1") .unwrap_or(false) - && std::env::var("SPROUT_PRIVATE_KEY") + && std::env::var("BUZZ_PRIVATE_KEY") .ok() .and_then(|k| Keys::parse(k.trim()).ok()) .is_some() @@ -112,7 +112,7 @@ pub fn decrypt_observer_event( return Err("observer event has invalid signature".into()); } - sprout_core::observer::decrypt_observer_payload(&keys, &event) + buzz_core_pkg::observer::decrypt_observer_payload(&keys, &event) .map_err(|error| format!("decrypt observer event failed: {error}")) } @@ -132,12 +132,13 @@ pub fn build_observer_control_event( let agent_pubkey = PublicKey::from_hex(agent_pubkey.trim()) .map_err(|error| format!("invalid agent pubkey: {error}"))?; let agent_pubkey_hex = agent_pubkey.to_hex(); - let encrypted = sprout_core::observer::encrypt_observer_payload(&keys, &agent_pubkey, &payload) - .map_err(|error| format!("encrypt observer control failed: {error}"))?; - let builder = sprout_sdk::build_agent_observer_frame( + let encrypted = + buzz_core_pkg::observer::encrypt_observer_payload(&keys, &agent_pubkey, &payload) + .map_err(|error| format!("encrypt observer control failed: {error}"))?; + let builder = buzz_sdk_pkg::build_agent_observer_frame( &agent_pubkey_hex, &agent_pubkey_hex, - sprout_core::observer::OBSERVER_FRAME_CONTROL, + buzz_core_pkg::observer::OBSERVER_FRAME_CONTROL, &encrypted, ) .map_err(|error| format!("build observer control failed: {error}"))?; @@ -187,7 +188,7 @@ pub fn import_identity( bech32 }; - eprintln!("sprout-desktop: imported identity pubkey {}", pubkey_hex); + eprintln!("buzz-desktop: imported identity pubkey {}", pubkey_hex); Ok(IdentityInfo { pubkey: pubkey_hex, diff --git a/desktop/src-tauri/src/commands/identity_archive.rs b/desktop/src-tauri/src/commands/identity_archive.rs index 5df6463af..64459061b 100644 --- a/desktop/src-tauri/src/commands/identity_archive.rs +++ b/desktop/src-tauri/src/commands/identity_archive.rs @@ -27,7 +27,7 @@ use crate::{ /// /// Mirrors the verification the relay will do (per spec gotcha #3: the /// preimage subject is the *target* pubkey, not the request signer). The -/// `sprout-sdk` lives on nostr 0.36; the desktop is on 0.37, so we bridge +/// `buzz-sdk` lives on nostr 0.36; the desktop is on 0.37, so we bridge /// via hex round-trip exactly like `relay::build_profile_event` does. fn extract_oa_owner(target_kind0: &nostr::Event) -> Option<(String, [String; 4])> { let target_hex = target_kind0.pubkey.to_hex(); @@ -39,7 +39,7 @@ fn extract_oa_owner(target_kind0: &nostr::Event) -> Option<(String, [String; 4]) continue; } let json = serde_json::to_string(slice).ok()?; - match sprout_sdk::nip_oa::verify_auth_tag(&json, &target_compat) { + match buzz_sdk_pkg::nip_oa::verify_auth_tag(&json, &target_compat) { Ok(owner) => { let raw: [String; 4] = [ slice[0].clone(), @@ -228,7 +228,7 @@ pub struct ArchivedIdentitiesSnapshot { /// fetch wired up (the sibling relay-signed kind:13534 membership list is /// consumed the same way). Author-filtering against NIP-11 `self` is the /// correct hardening for an untrusted/multi-relay client and is tracked as a -/// follow-up — not a runtime gap on Sprout's relay. +/// follow-up — not a runtime gap on Buzz's relay. #[tauri::command] pub async fn list_archived_identities( state: State<'_, AppState>, @@ -273,15 +273,16 @@ mod tests { /// Build a fake `kind:0` with a valid NIP-OA auth tag for a fresh owner. fn kind0_with_auth(agent: &Keys, owner: &Keys) -> nostr::Event { - // Compute auth tag via sprout-sdk (nostr 0.36) and bridge. + // Compute auth tag via buzz-sdk (nostr 0.36) and bridge. let agent_hex = agent.public_key().to_hex(); let agent_compat = nostr::PublicKey::from_hex(&agent_hex).unwrap(); let owner_compat_secret = nostr::SecretKey::from_slice(owner.secret_key().as_secret_bytes()).unwrap(); let owner_compat_keys = nostr::Keys::new(owner_compat_secret); - let tag_json = sprout_sdk::nip_oa::compute_auth_tag(&owner_compat_keys, &agent_compat, "") - .expect("compute_auth_tag"); - let compat_tag = sprout_sdk::nip_oa::parse_auth_tag(&tag_json).unwrap(); + let tag_json = + buzz_sdk_pkg::nip_oa::compute_auth_tag(&owner_compat_keys, &agent_compat, "") + .expect("compute_auth_tag"); + let compat_tag = buzz_sdk_pkg::nip_oa::parse_auth_tag(&tag_json).unwrap(); let tag = Tag::parse(compat_tag.as_slice()).unwrap(); EventBuilder::new(Kind::Metadata, "{}") .tags([tag]) diff --git a/desktop/src-tauri/src/commands/media.rs b/desktop/src-tauri/src/commands/media.rs index faf743c6a..fbe5528f6 100644 --- a/desktop/src-tauri/src/commands/media.rs +++ b/desktop/src-tauri/src/commands/media.rs @@ -173,7 +173,7 @@ fn sign_blossom_upload_auth( if let Some(domain) = extract_server_authority(base_url) { tags.push(Tag::parse(vec!["server".to_string(), domain]).map_err(|e| e.to_string())?); } - EventBuilder::new(Kind::from(24242), "Upload sprout-media") + EventBuilder::new(Kind::from(24242), "Upload buzz-media") .tags(tags) .sign_with_keys(keys) .map_err(|e| e.to_string()) @@ -321,7 +321,7 @@ async fn process_picked_path( if let Some(poster) = poster_bytes { match do_upload(poster, "image/jpeg", state).await { Ok(poster_desc) => descriptor.image = Some(poster_desc.url), - Err(e) => eprintln!("sprout-desktop: poster upload failed (non-fatal): {e}"), + Err(e) => eprintln!("buzz-desktop: poster upload failed (non-fatal): {e}"), } } @@ -398,7 +398,7 @@ pub async fn upload_media_bytes( // All blocking I/O runs off the async runtime via spawn_blocking. tokio::task::spawn_blocking(move || -> Result<(Vec, Option>), String> { let tmp_input = - std::env::temp_dir().join(format!("sprout-drop-{}", uuid::Uuid::new_v4())); + std::env::temp_dir().join(format!("buzz-drop-{}", uuid::Uuid::new_v4())); // Cleanup guard: remove temp file on ALL exit paths (including write failure). let result = (|| { std::fs::write(&tmp_input, &data) @@ -422,7 +422,7 @@ pub async fn upload_media_bytes( if let Some(poster) = poster_bytes { match do_upload(poster, "image/jpeg", &state).await { Ok(poster_desc) => descriptor.image = Some(poster_desc.url), - Err(e) => eprintln!("sprout-desktop: poster upload failed (non-fatal): {e}"), + Err(e) => eprintln!("buzz-desktop: poster upload failed (non-fatal): {e}"), } } diff --git a/desktop/src-tauri/src/commands/media_transcode.rs b/desktop/src-tauri/src/commands/media_transcode.rs index 604e7b5b7..96aabed6c 100644 --- a/desktop/src-tauri/src/commands/media_transcode.rs +++ b/desktop/src-tauri/src/commands/media_transcode.rs @@ -111,8 +111,7 @@ pub(super) fn transcode_to_mp4( ffmpeg: &std::path::Path, ) -> Result { // UUID-based temp path — unique across concurrent uploads. - let output = - std::env::temp_dir().join(format!("sprout-transcode-{}.mp4", uuid::Uuid::new_v4())); + let output = std::env::temp_dir().join(format!("buzz-transcode-{}.mp4", uuid::Uuid::new_v4())); let result = run_ffmpeg_with_timeout( std::process::Command::new(ffmpeg) @@ -167,7 +166,7 @@ pub(super) fn extract_poster_frame( mp4_path: &std::path::Path, ffmpeg: &std::path::Path, ) -> Result { - let output = std::env::temp_dir().join(format!("sprout-poster-{}.jpg", uuid::Uuid::new_v4())); + let output = std::env::temp_dir().join(format!("buzz-poster-{}.jpg", uuid::Uuid::new_v4())); // Poster extraction is a single-frame decode — 30s is generous. let poster_timeout = std::time::Duration::from_secs(30); @@ -194,7 +193,7 @@ pub(super) fn extract_poster_frame( { if !result.status.success() { let stderr = String::from_utf8_lossy(&result.stderr); - eprintln!("sprout-desktop: poster seek-to-1s failed, trying first frame: {stderr}"); + eprintln!("buzz-desktop: poster seek-to-1s failed, trying first frame: {stderr}"); } let _ = std::fs::remove_file(&output); let fallback = run_ffmpeg_with_timeout( @@ -211,7 +210,7 @@ pub(super) fn extract_poster_frame( if !fallback.status.success() || !output.exists() { let stderr = String::from_utf8_lossy(&fallback.stderr); - eprintln!("sprout-desktop: poster frame extraction failed: {stderr}"); + eprintln!("buzz-desktop: poster frame extraction failed: {stderr}"); let _ = std::fs::remove_file(&output); return Err("ffmpeg could not extract a poster frame".to_string()); } @@ -238,7 +237,7 @@ pub(super) fn transcode_and_extract_poster( bytes } Err(e) => { - eprintln!("sprout-desktop: poster extraction failed (non-fatal): {e}"); + eprintln!("buzz-desktop: poster extraction failed (non-fatal): {e}"); None } }; diff --git a/desktop/src-tauri/src/commands/mesh_llm.rs b/desktop/src-tauri/src/commands/mesh_llm.rs index c1b5ddad8..ecac20588 100644 --- a/desktop/src-tauri/src/commands/mesh_llm.rs +++ b/desktop/src-tauri/src/commands/mesh_llm.rs @@ -347,7 +347,7 @@ pub(crate) async fn ensure_relay_mesh_for_record( { // Non-fatal: the one-sided dial may still punch on a favorable NAT. // Surface the reason without blocking the agent's spawn. - eprintln!("sprout-mesh: saved-start connect-request failed: {error}"); + eprintln!("buzz-mesh: saved-start connect-request failed: {error}"); } } } @@ -503,7 +503,7 @@ mod tests { /// frontend selected earlier. /// /// Hardware-gated (`#[ignore]`): loads a real model. Run with: - /// cargo test -p sprout-desktop --features mesh-llm \ + /// cargo test -p buzz-desktop --features mesh-llm \ /// ensure_serve_runtime_serves_other_model -- --ignored --nocapture #[tokio::test] #[ignore = "loads a real model; run manually with --ignored"] diff --git a/desktop/src-tauri/src/commands/messages.rs b/desktop/src-tauri/src/commands/messages.rs index abbe0b0e6..51164106c 100644 --- a/desktop/src-tauri/src/commands/messages.rs +++ b/desktop/src-tauri/src/commands/messages.rs @@ -284,19 +284,19 @@ pub async fn send_channel_message( let media = media_tags.unwrap_or_default(); let emoji = emoji_tags.unwrap_or_default(); let mention_refs_only = mention_tags.unwrap_or_default(); - let kind_num = kind.unwrap_or(sprout_core::kind::KIND_STREAM_MESSAGE); + let kind_num = kind.unwrap_or(buzz_core_pkg::kind::KIND_STREAM_MESSAGE); let mut resolved_root: Option = None; let builder = match kind_num { - sprout_core::kind::KIND_FORUM_POST => events::build_forum_post( + buzz_core_pkg::kind::KIND_FORUM_POST => events::build_forum_post( channel_uuid, content.trim(), &mention_refs, &media, &mention_refs_only, )?, - sprout_core::kind::KIND_FORUM_COMMENT => { + buzz_core_pkg::kind::KIND_FORUM_COMMENT => { let parent_id = parent_event_id .as_deref() .ok_or("forum comment requires parent_event_id")?; @@ -362,7 +362,7 @@ pub async fn add_reaction( // Custom-emoji reaction (NIP-30): kind:7 with `:shortcode:` content and // an `["emoji", shortcode, url]` tag. Delegates to the SDK builder so // shortcode normalization + validation match the relay exactly. - Some(url) => sprout_sdk::build_custom_emoji_reaction(target_eid, emoji.trim(), &url) + Some(url) => buzz_sdk_pkg::build_custom_emoji_reaction(target_eid, emoji.trim(), &url) .map_err(|e| format!("invalid custom emoji reaction: {e}"))?, None => events::build_reaction(target_eid, emoji.trim())?, }; diff --git a/desktop/src-tauri/src/commands/pairing.rs b/desktop/src-tauri/src/commands/pairing.rs index cbed4d52e..1168b9f1b 100644 --- a/desktop/src-tauri/src/commands/pairing.rs +++ b/desktop/src-tauri/src/commands/pairing.rs @@ -1,13 +1,13 @@ use std::sync::Arc; use std::time::Duration; +use buzz_core_pkg::kind::KIND_PAIRING; +use buzz_core_pkg::pairing::qr::encode_qr; +use buzz_core_pkg::pairing::session::PairingSession; +use buzz_core_pkg::pairing::types::{AbortReason, PayloadType}; use futures_util::{SinkExt, StreamExt}; use nostr::ToBech32; use serde::Serialize; -use sprout_core::kind::KIND_PAIRING; -use sprout_core::pairing::qr::encode_qr; -use sprout_core::pairing::session::PairingSession; -use sprout_core::pairing::types::{AbortReason, PayloadType}; use tauri::{AppHandle, Emitter, State}; use tokio::sync::mpsc; use tokio_tungstenite::{connect_async, tungstenite::Message}; @@ -400,7 +400,7 @@ fn event_to_relay_json(event: &nostr::Event) -> String { format!("[\"EVENT\",{}]", nostr::JsonUtil::as_json(event)) } -/// Parse a relay EVENT message into a nostr 0.36 Event (sprout-core compatible). +/// Parse a relay EVENT message into a nostr 0.36 Event (buzz-core compatible). fn parse_relay_event(text: &str, sub_id: &str) -> Option { let arr: serde_json::Value = serde_json::from_str(text).ok()?; let arr = arr.as_array()?; diff --git a/desktop/src-tauri/src/commands/profile.rs b/desktop/src-tauri/src/commands/profile.rs index 40fac227f..e218ca36e 100644 --- a/desktop/src-tauri/src/commands/profile.rs +++ b/desktop/src-tauri/src/commands/profile.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; +use buzz_core_pkg::PresenceStatus; use serde_json::Value; -use sprout_core::PresenceStatus; use tauri::State; use crate::{ @@ -196,7 +196,7 @@ pub async fn search_users( // NIP-50 full-text search on kind:0 profiles. The relay's HTTP bridge // intercepts the `search` field on POST /query and routes to Typesense - // (see `crates/sprout-relay/src/api/bridge.rs::handle_bridge_search`), + // (see `crates/buzz-relay/src/api/bridge.rs::handle_bridge_search`), // so we get indexed, server-side search instead of fetching every kind:0 // and scanning client-side. The old path was capped at 2000 kind:0 events // by the relay's HTTP bridge limit, which silently hid users on busy relays. diff --git a/desktop/src-tauri/src/commands/teams.rs b/desktop/src-tauri/src/commands/teams.rs index ad8930f9f..aba201b2f 100644 --- a/desktop/src-tauri/src/commands/teams.rs +++ b/desktop/src-tauri/src/commands/teams.rs @@ -260,7 +260,7 @@ fn parse_team_from_pack_zip(zip_bytes: &[u8]) -> Result (true, None), Err(e) => { eprintln!( - "sprout-desktop: add agent to parent channel failed (may already be member): {e}" + "buzz-desktop: add agent to parent channel failed (may already be member): {e}" ); (false, Some(e)) } diff --git a/desktop/src-tauri/src/huddle/audio_output.rs b/desktop/src-tauri/src/huddle/audio_output.rs index 7af9256a7..02acf2730 100644 --- a/desktop/src-tauri/src/huddle/audio_output.rs +++ b/desktop/src-tauri/src/huddle/audio_output.rs @@ -83,7 +83,7 @@ pub(crate) fn open_output_sink_by_name( } } eprintln!( - "sprout-desktop: preferred output device {name:?} not found, falling back to default" + "buzz-desktop: preferred output device {name:?} not found, falling back to default" ); } diff --git a/desktop/src-tauri/src/huddle/mod.rs b/desktop/src-tauri/src/huddle/mod.rs index b2e6e769a..a25b9def3 100644 --- a/desktop/src-tauri/src/huddle/mod.rs +++ b/desktop/src-tauri/src/huddle/mod.rs @@ -106,7 +106,7 @@ pub async fn set_voice_input_mode( // Best-effort restart — if models aren't ready, the pipeline // stays down until the next hotstart cycle picks it up. if let Err(e) = maybe_start_stt_pipeline(&state, &eph_id).await { - eprintln!("sprout-desktop: STT pipeline restart on mode switch failed: {e}"); + eprintln!("buzz-desktop: STT pipeline restart on mode switch failed: {e}"); } } } @@ -203,7 +203,7 @@ pub async fn start_huddle( events::build_huddle_guidelines(&ephemeral_channel_id, &guidelines) { if let Err(e) = submit_event(guidelines_builder, &state).await { - eprintln!("sprout-desktop: huddle guidelines (kind:48106) failed: {e}"); + eprintln!("buzz-desktop: huddle guidelines (kind:48106) failed: {e}"); } } @@ -214,7 +214,7 @@ pub async fn start_huddle( match submit_event(add_builder, &state).await { Ok(_) => successful_agents.push(pubkey.clone()), Err(e) => { - eprintln!("sprout-desktop: huddle add_member failed for {pubkey}: {e}"); + eprintln!("buzz-desktop: huddle add_member failed for {pubkey}: {e}"); // Intentionally not added — policy rejected this agent. } } @@ -266,7 +266,7 @@ pub async fn start_huddle( if let Ok(archive_builder) = events::build_archive(ephemeral_uuid) { if let Err(ae) = submit_event(archive_builder, &state).await { eprintln!( - "sprout-desktop: rollback archive of {ephemeral_channel_id} failed: {ae}" + "buzz-desktop: rollback archive of {ephemeral_channel_id} failed: {ae}" ); } } @@ -287,7 +287,7 @@ pub async fn start_huddle( if let Ok(archive_builder) = events::build_archive(ephemeral_uuid) { if let Err(ae) = submit_event(archive_builder, &state).await { eprintln!( - "sprout-desktop: rollback archive of {ephemeral_channel_id} failed: {ae}" + "buzz-desktop: rollback archive of {ephemeral_channel_id} failed: {ae}" ); } } @@ -423,7 +423,7 @@ async fn emit_end_and_archive( events::build_huddle_ended(parent_channel_id, ephemeral_channel_id) { if let Err(e) = submit_event(ended_builder, state).await { - eprintln!("sprout-desktop: huddle_ended event failed: {e}"); + eprintln!("buzz-desktop: huddle_ended event failed: {e}"); } } } @@ -431,7 +431,7 @@ async fn emit_end_and_archive( if let Ok(uuid) = parse_channel_uuid(ephemeral_channel_id) { if let Ok(archive_builder) = events::build_archive(uuid) { if let Err(e) = submit_event(archive_builder, state).await { - eprintln!("sprout-desktop: archive ephemeral channel failed: {e}"); + eprintln!("buzz-desktop: archive ephemeral channel failed: {e}"); } } } @@ -481,14 +481,14 @@ pub async fn leave_huddle(state: State<'_, AppState>) -> Result<(), String> { // Archive subsumes leave (the channel is gone, membership is moot). // This avoids the "cannot remove the last owner" relay error that // build_leave hits when the creator is the sole remaining member. - eprintln!("sprout-desktop: last human left huddle — auto-ending"); + eprintln!("buzz-desktop: last human left huddle — auto-ending"); emit_end_and_archive(&parent_channel_id, &ephemeral_channel_id, &state).await; } else { // Other humans still in the huddle — just remove self from membership. if let Ok(eph_uuid) = parse_channel_uuid(&ephemeral_channel_id) { if let Ok(leave_builder) = events::build_leave(eph_uuid) { if let Err(e) = submit_event(leave_builder, &state).await { - eprintln!("sprout-desktop: huddle leave ephemeral channel failed: {e}"); + eprintln!("buzz-desktop: huddle leave ephemeral channel failed: {e}"); } } } @@ -671,14 +671,14 @@ pub async fn check_pipeline_hotstart(state: State<'_, AppState>) -> Result<(), S // Start TTS first (so STT can capture tts_cancel). if !has_tts && (tts_ready || models::is_tts_ready()) { if let Err(e) = maybe_start_tts_pipeline(&state).await { - eprintln!("sprout-desktop: TTS hotstart failed: {e}"); + eprintln!("buzz-desktop: TTS hotstart failed: {e}"); } } if !has_stt && (stt_ready || models::is_stt_ready()) { if let Some(eph_id) = &ephemeral_channel_id { if let Err(e) = maybe_start_stt_pipeline(&state, eph_id).await { - eprintln!("sprout-desktop: STT hotstart failed: {e}"); + eprintln!("buzz-desktop: STT hotstart failed: {e}"); } } } @@ -812,7 +812,7 @@ pub async fn set_tts_enabled(enabled: bool, state: State<'_, AppState>) -> Resul }; if matches!(phase, HuddlePhase::Connected | HuddlePhase::Active) { if let Err(e) = maybe_start_tts_pipeline(&state).await { - eprintln!("sprout-desktop: TTS pipeline restart failed: {e}"); + eprintln!("buzz-desktop: TTS pipeline restart failed: {e}"); } } } @@ -853,7 +853,7 @@ pub async fn speak_agent_message(text: String, state: State<'_, AppState>) -> Re // Lazy-start: models may have finished downloading after the huddle began. if needs_pipeline { if let Err(e) = maybe_start_tts_pipeline(&state).await { - eprintln!("sprout-desktop: TTS lazy-start failed: {e}"); + eprintln!("buzz-desktop: TTS lazy-start failed: {e}"); } } diff --git a/desktop/src-tauri/src/huddle/models.rs b/desktop/src-tauri/src/huddle/models.rs index 98729b2fb..8571c4043 100644 --- a/desktop/src-tauri/src/huddle/models.rs +++ b/desktop/src-tauri/src/huddle/models.rs @@ -1,17 +1,17 @@ //! Model download manager for STT (Parakeet TDT-CTC 110M) and TTS (Pocket TTS) models. //! //! Mental model: -//! app launch → start_stt_download (background) → ~/.sprout/models/parakeet-tdt-ctc-110m-en/ -//! app launch → start_tts_download (background) → ~/.sprout/models/pocket-tts/ +//! app launch → start_stt_download (background) → ~/.buzz/models/parakeet-tdt-ctc-110m-en/ +//! app launch → start_tts_download (background) → ~/.buzz/models/pocket-tts/ //! STT pipeline → is_stt_ready() → stt_model_dir() → run inference //! TTS pipeline → is_tts_ready() → tts_model_dir() → run synthesis //! -//! Models are downloaded once and cached. A version manifest (`.sprout-model-manifest`) +//! Models are downloaded once and cached. A version manifest (`.buzz-model-manifest`) //! is written alongside model files — if the on-disk version doesn't match the //! compiled-in version, the model is re-downloaded. //! //! Upgrade note: an older Moonshine STT model directory at -//! `~/.sprout/models/moonshine-tiny/` is removed best-effort once the new STT +//! `~/.buzz/models/moonshine-tiny/` is removed best-effort once the new STT //! model finishes installing successfully. Cleanup is gated on the new model //! being Ready, so a failed download never removes the previous on-disk model //! during migration. If removal fails (permissions, etc.) the leftover is @@ -94,7 +94,7 @@ const STT_MODEL_VERSION: &str = "2"; const TTS_MODEL_VERSION: &str = "2"; /// Filename for the version manifest written alongside model files. -const MANIFEST_FILENAME: &str = ".sprout-model-manifest"; +const MANIFEST_FILENAME: &str = ".buzz-model-manifest"; // ── Constants ───────────────────────────────────────────────────────────────── @@ -117,12 +117,12 @@ const STT_DOWNLOAD_URL: &str = /// Subdirectory name produced by `tar xjf` on the archive. const STT_ARCHIVE_SUBDIR: &str = "sherpa-onnx-nemo-parakeet_tdt_ctc_110m-en-36000-int8"; -/// Final directory name under `~/.sprout/models/`. +/// Final directory name under `~/.buzz/models/`. const STT_MODEL_DIR_NAME: &str = "parakeet-tdt-ctc-110m-en"; /// All files that must be present for the model to be considered ready. /// -/// Includes the attribution sidecar written by Sprout during install. The +/// Includes the attribution sidecar written by Buzz during install. The /// upstream archive does not ship a license file, so readiness should require /// the local CC-BY-4.0 attribution to travel with the cached model bytes. const STT_EXPECTED_FILES: &[&str] = &["model.int8.onnx", "tokens.txt", STT_LICENSE_FILE_NAME]; @@ -143,7 +143,7 @@ Licensed under the Creative Commons Attribution 4.0 International License Original model: https://huggingface.co/nvidia/parakeet-tdt_ctc-110m Converted to ONNX with int8 quantization by the sherpa-onnx project -(https://github.com/k2-fsa/sherpa-onnx); Sprout ships this conversion +(https://github.com/k2-fsa/sherpa-onnx); Buzz ships this conversion unmodified. Provided \"AS IS\", without warranty of any kind, express or implied. See the @@ -152,7 +152,7 @@ license text for full warranty disclaimer. // ── Pocket TTS model ────────────────────────────────────────────────────────── -/// Final directory name under `~/.sprout/models/`. +/// Final directory name under `~/.buzz/models/`. const TTS_MODEL_DIR_NAME: &str = "pocket-tts"; /// Attribution sidecar written next to the Pocket TTS model files. @@ -184,7 +184,7 @@ https://datashare.ed.ac.uk/handle/10283/3443 (CC-BY-4.0). Recording enhancement (denoise/dereverb) by ai-coustics: https://ai-coustics.com/ -Sprout ships all ONNX/model artifacts and the reference voice WAV unmodified, +Buzz ships all ONNX/model artifacts and the reference voice WAV unmodified, renamed only by placement in the local model directory. Provided \"AS IS\", without warranty of any kind, express or implied. See the @@ -391,7 +391,7 @@ where /// Per-model state + config. `ModelManager` owns two of these (stt, tts). #[derive(Clone)] struct ModelSlot { - dir_name: &'static str, // subdir under ~/.sprout/models/ + dir_name: &'static str, // subdir under ~/.buzz/models/ expected_files: &'static [&'static str], // files required for "ready" version: &'static str, // manifest version; increment to force re-download status: Arc>, @@ -474,7 +474,7 @@ impl ModelSlot { // is accessible on the current thread. Tauri's runtime is always available. tauri::async_runtime::spawn(async move { if let Err(e) = download_fn(http_client).await { - eprintln!("sprout-desktop: {name} download failed: {e}"); + eprintln!("buzz-desktop: {name} download failed: {e}"); slot.set_status(ModelStatus::Error(e)); } }); @@ -539,18 +539,18 @@ impl ModelSlot { /// Cheap to clone — all inner state is behind `Arc`. #[derive(Clone)] pub struct ModelManager { - /// `~/.sprout/models/` + /// `~/.buzz/models/` models_dir: PathBuf, stt: ModelSlot, tts: ModelSlot, } impl ModelManager { - /// Create a new `ModelManager` rooted at `~/.sprout/models/`. + /// Create a new `ModelManager` rooted at `~/.buzz/models/`. /// /// Returns `None` if the home directory cannot be resolved. pub fn new() -> Option { - let models_dir = dirs::home_dir()?.join(".sprout").join("models"); + let models_dir = dirs::home_dir()?.join(".buzz").join("models"); Some(Self { models_dir, stt: ModelSlot::new(STT_MODEL_DIR_NAME, STT_EXPECTED_FILES, STT_MODEL_VERSION), @@ -654,7 +654,7 @@ impl ModelManager { .join(format!("{STT_MODEL_DIR_NAME}.tar.bz2")); let temp_dir = self.models_dir.join(format!("{STT_MODEL_DIR_NAME}.tmp")); - eprintln!("sprout-desktop: downloading STT model from {STT_DOWNLOAD_URL}"); + eprintln!("buzz-desktop: downloading STT model from {STT_DOWNLOAD_URL}"); let response = fetch_url(&http_client, STT_DOWNLOAD_URL, "stt archive").await?; let slot = self.stt.clone(); @@ -674,7 +674,7 @@ impl ModelManager { }, ) .await?; - eprintln!("sprout-desktop: downloaded {bytes} bytes, wrote to disk"); + eprintln!("buzz-desktop: downloaded {bytes} bytes, wrote to disk"); // Verify archive integrity before extraction. let hash = sha256_file(&archive_path).await?; @@ -690,7 +690,7 @@ impl ModelManager { }); fresh_temp_dir(&temp_dir).await?; - eprintln!("sprout-desktop: extracting STT archive…"); + eprintln!("buzz-desktop: extracting STT archive…"); let (ap, td) = (archive_path.clone(), temp_dir.clone()); tokio::task::spawn_blocking(move || extract_archive(&ap, &td)) .await @@ -735,7 +735,7 @@ impl ModelManager { cleanup_legacy_moonshine_dir(&self.models_dir).await; eprintln!( - "sprout-desktop: STT model ready at {}", + "buzz-desktop: STT model ready at {}", self.stt.model_dir(&self.models_dir).display() ); Ok(()) @@ -743,10 +743,10 @@ impl ModelManager { /// Download and verify the Pocket TTS model files from HuggingFace. /// - /// Downloads files into `~/.sprout/models/pocket-tts/`: + /// Downloads files into `~/.buzz/models/pocket-tts/`: /// - five ONNX sessions (Pocket TTS + Mimi codec) /// - `vocab.json` / `token_scores.json` for sherpa-onnx text conditioning - /// - upstream `LICENSE` plus Sprout's `MODEL_LICENSE.txt` attribution sidecar + /// - upstream `LICENSE` plus Buzz's `MODEL_LICENSE.txt` attribution sidecar /// - `reference_sample.wav` as the bundled default voice /// /// Files are written to a temp directory first, then moved atomically. @@ -776,7 +776,7 @@ impl ModelManager { let total_files = downloads.len() as u32; for (i, (url, filename)) in downloads.iter().enumerate() { - eprintln!("sprout-desktop: downloading Pocket TTS {filename} from {url}"); + eprintln!("buzz-desktop: downloading Pocket TTS {filename} from {url}"); let response = fetch_url(&http_client, url, filename) .await @@ -810,7 +810,7 @@ impl ModelManager { .inspect_err(|_| { let _ = std::fs::remove_dir_all(&temp_dir); })?; - eprintln!("sprout-desktop: downloaded {bytes} bytes ({filename}), wrote to disk"); + eprintln!("buzz-desktop: downloaded {bytes} bytes ({filename}), wrote to disk"); let expected = TTS_FILE_HASHES .iter() @@ -850,7 +850,7 @@ impl ModelManager { } eprintln!( - "sprout-desktop: Pocket TTS model ready at {}", + "buzz-desktop: Pocket TTS model ready at {}", self.tts.model_dir(&self.models_dir).display() ); Ok(()) @@ -882,7 +882,7 @@ pub fn is_stt_ready() -> bool { /// Best-effort cleanup of the legacy Moonshine STT model directory. /// -/// Removes `~/.sprout/models/moonshine-tiny/` if present (~70 MB on disk). +/// Removes `~/.buzz/models/moonshine-tiny/` if present (~70 MB on disk). /// Idempotent — no-op if the directory is absent. Errors are logged and /// swallowed; the leftover is harmless and the user can remove it manually. /// @@ -897,11 +897,11 @@ async fn cleanup_legacy_moonshine_dir(models_dir: &Path) { } match tokio::fs::remove_dir_all(&legacy).await { Ok(()) => eprintln!( - "sprout-desktop: removed legacy STT model dir {}", + "buzz-desktop: removed legacy STT model dir {}", legacy.display() ), Err(e) => eprintln!( - "sprout-desktop: could not remove legacy STT model dir {}: {e} \ + "buzz-desktop: could not remove legacy STT model dir {}: {e} \ (harmless — remove manually to reclaim disk space)", legacy.display() ), diff --git a/desktop/src-tauri/src/huddle/pipeline.rs b/desktop/src-tauri/src/huddle/pipeline.rs index 73642cb1a..bcb6e5ea5 100644 --- a/desktop/src-tauri/src/huddle/pipeline.rs +++ b/desktop/src-tauri/src/huddle/pipeline.rs @@ -60,10 +60,10 @@ pub(crate) async fn post_connect_setup( // Start pipelines: TTS first (so STT can capture tts_cancel for barge-in). if let Err(e) = maybe_start_tts_pipeline(state).await { - eprintln!("sprout-desktop: TTS pipeline failed to start: {e}"); + eprintln!("buzz-desktop: TTS pipeline failed to start: {e}"); } if let Err(e) = maybe_start_stt_pipeline(state, ephemeral_channel_id).await { - eprintln!("sprout-desktop: STT pipeline failed to start: {e}"); + eprintln!("buzz-desktop: STT pipeline failed to start: {e}"); } Ok(()) @@ -273,14 +273,14 @@ pub(crate) fn spawn_transcription_task( match events::build_message(channel_uuid, &t, None, &p_tags, &[], &[], &[]) { Ok(b) => b, Err(e) => { - eprintln!("sprout-desktop: STT build_message: {e}"); + eprintln!("buzz-desktop: STT build_message: {e}"); continue; } }; let event = match builder.sign_with_keys(&keys) { Ok(e) => e, Err(e) => { - eprintln!("sprout-desktop: STT sign event: {e}"); + eprintln!("buzz-desktop: STT sign event: {e}"); continue; } }; @@ -294,7 +294,7 @@ pub(crate) fn spawn_transcription_task( ) { Ok(h) => h, Err(e) => { - eprintln!("sprout-desktop: STT NIP-98 auth: {e}"); + eprintln!("buzz-desktop: STT NIP-98 auth: {e}"); continue; } }; @@ -311,12 +311,12 @@ pub(crate) fn spawn_transcription_task( Ok(resp) if resp.status().is_success() => {} Ok(resp) => { eprintln!( - "sprout-desktop: STT kind:9 post failed: HTTP {}", + "buzz-desktop: STT kind:9 post failed: HTTP {}", resp.status() ); } Err(e) => { - eprintln!("sprout-desktop: STT kind:9 post failed: {e}"); + eprintln!("buzz-desktop: STT kind:9 post failed: {e}"); } } } diff --git a/desktop/src-tauri/src/huddle/playout.rs b/desktop/src-tauri/src/huddle/playout.rs index ffdfc7bfb..69548bcfc 100644 --- a/desktop/src-tauri/src/huddle/playout.rs +++ b/desktop/src-tauri/src/huddle/playout.rs @@ -98,7 +98,7 @@ impl PeerSlot { last_packet_at: tokio::time::Instant::now(), }), Err(e) => { - eprintln!("sprout-desktop: jitter buffer init peer {peer_idx}: {e}"); + eprintln!("buzz-desktop: jitter buffer init peer {peer_idx}: {e}"); None } } @@ -194,7 +194,7 @@ pub(crate) async fn run_playout_recv_loop( // queue grow without bound. if slot.player.len() >= PLAYOUT_QUEUE_HIGH_WATER { eprintln!( - "sprout-desktop: playout queue high-water for peer {peer_idx} \ + "buzz-desktop: playout queue high-water for peer {peer_idx} \ (depth={}) — dropping oldest frame", slot.player.len(), ); @@ -204,7 +204,7 @@ pub(crate) async fn run_playout_recv_loop( } Err(e) => { eprintln!( - "sprout-desktop: jitter get_audio peer {peer_idx}: {e}" + "buzz-desktop: jitter get_audio peer {peer_idx}: {e}" ); } } @@ -237,7 +237,7 @@ pub(crate) async fn run_playout_recv_loop( // the slice is too short, which `if data.len() <= ...` // already guards. Defensive log + drop. eprintln!( - "sprout-desktop: dropping malformed audio frame from peer {peer_idx} ({} bytes)", + "buzz-desktop: dropping malformed audio frame from peer {peer_idx} ({} bytes)", data.len(), ); continue; @@ -281,7 +281,7 @@ pub(crate) async fn run_playout_recv_loop( .insert_packet(header.seq, header.ts_48k, opus_bytes) { eprintln!( - "sprout-desktop: jitter insert peer {peer_idx}: {err}" + "buzz-desktop: jitter insert peer {peer_idx}: {err}" ); } else { // Heartbeat for the playout tick's idle-peer diff --git a/desktop/src-tauri/src/huddle/pocket.rs b/desktop/src-tauri/src/huddle/pocket.rs index a0639802d..73d40f4f1 100644 --- a/desktop/src-tauri/src/huddle/pocket.rs +++ b/desktop/src-tauri/src/huddle/pocket.rs @@ -23,7 +23,7 @@ //! in . CC-BY-4.0, base recording //! from the VCTK corpus, enhanced by ai-coustics. //! -//! Sprout ships these files unmodified; see the on-disk `MODEL_LICENSE.txt` +//! Buzz ships these files unmodified; see the on-disk `MODEL_LICENSE.txt` //! sidecar written by `huddle::models` during install for the canonical //! CC-BY-4.0 §3(a)(1) attribution block. //! @@ -539,7 +539,7 @@ impl PocketTts { let sample_rate = audio.sample_rate(); if sample_rate != SAMPLE_RATE as i32 { eprintln!( - "sprout-desktop: Pocket TTS returned unexpected sample rate {sample_rate}Hz \ + "buzz-desktop: Pocket TTS returned unexpected sample rate {sample_rate}Hz \ (expected {SAMPLE_RATE}Hz); playback speed may be wrong" ); } diff --git a/desktop/src-tauri/src/huddle/relay_api.rs b/desktop/src-tauri/src/huddle/relay_api.rs index e840d24f6..eb3fea92d 100644 --- a/desktop/src-tauri/src/huddle/relay_api.rs +++ b/desktop/src-tauri/src/huddle/relay_api.rs @@ -183,7 +183,7 @@ pub(crate) async fn connect_audio_relay( }) .await { - eprintln!("sprout-desktop: audio relay pipeline exited: {e}"); + eprintln!("buzz-desktop: audio relay pipeline exited: {e}"); } // Only emit the disconnect event for UNEXPECTED exits. @@ -292,7 +292,7 @@ async fn audio_relay_pipeline(args: AudioRelayPipelineArgs) -> Result<(), String let n = match encode_result { Ok(n) => n, Err(e) => { - eprintln!("sprout-desktop: opus encode error: {e}"); + eprintln!("buzz-desktop: opus encode error: {e}"); continue; } }; @@ -363,7 +363,7 @@ pub(crate) async fn fetch_channel_members_with_roles( let events = query_relay(state, std::slice::from_ref(&filter)) .await .map_err(|e| { - eprintln!("sprout-desktop: fetch channel members failed: {e}"); + eprintln!("buzz-desktop: fetch channel members failed: {e}"); e })?; diff --git a/desktop/src-tauri/src/huddle/stt.rs b/desktop/src-tauri/src/huddle/stt.rs index 50ec6e819..6f502ca72 100644 --- a/desktop/src-tauri/src/huddle/stt.rs +++ b/desktop/src-tauri/src/huddle/stt.rs @@ -216,7 +216,7 @@ fn stt_worker( let mut resampler = match Fft::::new(48_000, 16_000, 1024, 2, 1, FixedSync::Input) { Ok(r) => r, Err(e) => { - eprintln!("sprout-desktop: STT resampler init failed: {e}"); + eprintln!("buzz-desktop: STT resampler init failed: {e}"); return; } }; @@ -239,7 +239,7 @@ fn stt_worker( let model_path = model_dir.join("model.int8.onnx"); if !tokens_path.exists() || !model_path.exists() { eprintln!( - "sprout-desktop: STT model not found at {} — STT disabled", + "buzz-desktop: STT model not found at {} — STT disabled", model_dir.display() ); drain_until_shutdown(audio_rx, &shutdown); @@ -257,7 +257,7 @@ fn stt_worker( let recognizer = match OfflineRecognizer::create(&cfg) { Some(r) => r, None => { - eprintln!("sprout-desktop: OfflineRecognizer::create returned None — STT disabled"); + eprintln!("buzz-desktop: OfflineRecognizer::create returned None — STT disabled"); drain_until_shutdown(audio_rx, &shutdown); return; } @@ -369,7 +369,7 @@ fn resample_chunk(resampler: &mut rubato::Fft, chunk_48k: &[f32]) -> Vec a, Err(e) => { - eprintln!("sprout-desktop: STT resample input error: {e}"); + eprintln!("buzz-desktop: STT resample input error: {e}"); return Vec::new(); } }; @@ -377,7 +377,7 @@ fn resample_chunk(resampler: &mut rubato::Fft, chunk_48k: &[f32]) -> Vec out.take_data(), Err(e) => { - eprintln!("sprout-desktop: STT resample error: {e}"); + eprintln!("buzz-desktop: STT resample error: {e}"); Vec::new() } } @@ -545,7 +545,7 @@ fn flush_to_stt( if !text.is_empty() { if let Err(e) = text_tx.blocking_send(text) { - eprintln!("sprout-desktop: STT text channel closed: {e}"); + eprintln!("buzz-desktop: STT text channel closed: {e}"); } } } diff --git a/desktop/src-tauri/src/huddle/tts.rs b/desktop/src-tauri/src/huddle/tts.rs index 21fbb94ea..482f01010 100644 --- a/desktop/src-tauri/src/huddle/tts.rs +++ b/desktop/src-tauri/src/huddle/tts.rs @@ -208,7 +208,7 @@ impl TtsPipeline { /// `TEXT_QUEUE_DEPTH`) — caller may log and discard. pub fn speak(&self, text: String) -> Result<(), String> { self.text_tx.try_send(text).map_err(|e| { - eprintln!("sprout-desktop: TTS queue saturated, dropping message: {e}"); + eprintln!("buzz-desktop: TTS queue saturated, dropping message: {e}"); format!("TTS queue full, dropping: {e}") }) } @@ -254,7 +254,7 @@ fn tts_worker( Ok(e) => e, Err(e) => { eprintln!( - "sprout-desktop: TTS engine init failed (model_dir={}): {e}. TTS disabled.", + "buzz-desktop: TTS engine init failed (model_dir={}): {e}. TTS disabled.", model_dir.display() ); drain_until_shutdown(text_rx, &shutdown); @@ -268,7 +268,7 @@ fn tts_worker( Ok(s) => s, Err(e) => { eprintln!( - "sprout-desktop: TTS voice style load failed ({voice_name}): {e}. TTS disabled." + "buzz-desktop: TTS voice style load failed ({voice_name}): {e}. TTS disabled." ); drain_until_shutdown(text_rx, &shutdown); return; @@ -284,11 +284,11 @@ fn tts_worker( let t = std::time::Instant::now(); match engine.synth_chunk("warmup", "en", &style, SYNTH_STEPS, SYNTH_SPEED) { Ok(_) => eprintln!( - "sprout-desktop: TTS warmup completed in {:.0}ms", + "buzz-desktop: TTS warmup completed in {:.0}ms", t.elapsed().as_millis() ), Err(e) => eprintln!( - "sprout-desktop: TTS warmup failed after {:.0}ms: {e} — first utterance may be slow", + "buzz-desktop: TTS warmup failed after {:.0}ms: {e} — first utterance may be slow", t.elapsed().as_millis() ), } @@ -301,7 +301,7 @@ fn tts_worker( { Ok(h) => h, Err(e) => { - eprintln!("sprout-desktop: TTS audio output failed: {e}. TTS disabled."); + eprintln!("buzz-desktop: TTS audio output failed: {e}. TTS disabled."); drain_until_shutdown(text_rx, &shutdown); return; } @@ -373,14 +373,14 @@ fn tts_worker( let channels = match NonZero::new(1u16) { Some(c) => c, None => { - eprintln!("sprout-desktop: TTS channel count invariant violated"); + eprintln!("buzz-desktop: TTS channel count invariant violated"); break; } }; let rate = match NonZero::new(SAMPLE_RATE) { Some(r) => r, None => { - eprintln!("sprout-desktop: TTS sample rate invariant violated"); + eprintln!("buzz-desktop: TTS sample rate invariant violated"); break; } }; @@ -432,7 +432,7 @@ fn tts_worker( } Ok(_) => {} Err(e) => { - eprintln!("sprout-desktop: TTS synth failed: {e}"); + eprintln!("buzz-desktop: TTS synth failed: {e}"); } } } diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 8794e94e8..e98c86696 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -233,13 +233,13 @@ fn shutdown_managed_agents(app: &tauri::AppHandle) -> Result<(), String> { // All tracked PIDs have already been killed above, so pass an empty skip list. managed_agents::sweep_orphaned_agent_processes(app, &[]); - // System-wide sweep: agent workers (goose, sprout-agent, etc.) are spawned - // in their own process groups by sprout-acp, so group-kills above only + // System-wide sweep: agent workers (goose, buzz-agent, etc.) are spawned + // in their own process groups by buzz-acp, so group-kills above only // reach the harness, not the workers. Scan all user processes and kill any // known agent binaries that are still running. managed_agents::sweep_system_agent_processes(&managed_agents::current_instance_id(app), &[]); - // Dead-instance reaping: find agents belonging to Sprout instances + // Dead-instance reaping: find agents belonging to Buzz instances // whose desktop process is no longer running and reap them. managed_agents::reap_dead_instance_agents(&managed_agents::current_instance_id(app), &[]); @@ -282,7 +282,7 @@ fn parse_message_deep_link(url: &Url) -> Option { })) } -/// Handle an incoming `buzz://` deep link URL, with `sprout://` accepted as a legacy alias. +/// Handle an incoming `buzz://` deep link URL. /// /// Currently supports: /// - `buzz://connect?relay=` — emits `deep-link-connect` to the frontend @@ -290,13 +290,13 @@ fn handle_deep_link_url(app: &tauri::AppHandle, url_str: &str) { let url = match Url::parse(url_str) { Ok(u) => u, Err(e) => { - eprintln!("sprout-desktop: invalid deep link URL {url_str:?}: {e}"); + eprintln!("buzz-desktop: invalid deep link URL {url_str:?}: {e}"); return; } }; - if url.scheme() != "sprout" && url.scheme() != "buzz" { - eprintln!("sprout-desktop: ignoring unsupported deep link scheme: {url_str}"); + if url.scheme() != "buzz" { + eprintln!("buzz-desktop: ignoring unsupported deep link scheme: {url_str}"); return; } @@ -307,7 +307,7 @@ fn handle_deep_link_url(app: &tauri::AppHandle, url_str: &str) { .find(|(k, _)| k == "relay") .map(|(_, v)| v.into_owned()); let Some(relay_url) = relay else { - eprintln!("sprout-desktop: connect deep link missing relay param: {url_str}"); + eprintln!("buzz-desktop: connect deep link missing relay param: {url_str}"); return; }; // Validate the relay URL is ws:// or wss:// @@ -315,13 +315,13 @@ fn handle_deep_link_url(app: &tauri::AppHandle, url_str: &str) { Ok(parsed) if parsed.scheme() == "ws" || parsed.scheme() == "wss" => {} Ok(parsed) => { eprintln!( - "sprout-desktop: rejecting non-websocket relay URL scheme {:?}: {relay_url}", + "buzz-desktop: rejecting non-websocket relay URL scheme {:?}: {relay_url}", parsed.scheme() ); return; } Err(e) => { - eprintln!("sprout-desktop: invalid relay URL {relay_url:?}: {e}"); + eprintln!("buzz-desktop: invalid relay URL {relay_url:?}: {e}"); return; } } @@ -337,16 +337,16 @@ fn handle_deep_link_url(app: &tauri::AppHandle, url_str: &str) { // structure on this side (serde JSON) and let the TS code own // any further normalisation. let Some(payload) = parse_message_deep_link(&url) else { - eprintln!("sprout-desktop: message deep link missing channel or id: {url_str}"); + eprintln!("buzz-desktop: message deep link missing channel or id: {url_str}"); return; }; let _ = app.emit("deep-link-message", payload); } Some(action) => { - eprintln!("sprout-desktop: unknown deep link action: {action}"); + eprintln!("buzz-desktop: unknown deep link action: {action}"); } None => { - eprintln!("sprout-desktop: deep link missing action: {url_str}"); + eprintln!("buzz-desktop: deep link missing action: {url_str}"); } } } @@ -377,7 +377,7 @@ pub fn run() { } // Forward any deep link URLs from the duplicate launch. for arg in &argv { - if arg.starts_with("sprout://") || arg.starts_with("buzz://") { + if arg.starts_with("buzz://") { handle_deep_link_url(app, arg); } } @@ -494,23 +494,23 @@ pub fn run() { // Only register the updater in release builds that were compiled with a // real updater configuration. Local unsigned builds omit that config and // should still launch for debugging. - #[cfg(sprout_updater_enabled)] + #[cfg(buzz_updater_enabled)] let builder = if cfg!(debug_assertions) { builder } else { builder.plugin(tauri_plugin_updater::Builder::new().build()) }; - #[cfg(not(sprout_updater_enabled))] + #[cfg(not(buzz_updater_enabled))] let builder = builder; let shutdown_started = Arc::new(AtomicBool::new(false)); let restore_shutdown_started = Arc::clone(&shutdown_started); let app = builder - .register_asynchronous_uri_scheme_protocol("sprout-media", |ctx, request, responder| { + .register_asynchronous_uri_scheme_protocol("buzz-media", |ctx, request, responder| { let app = ctx.app_handle().clone(); tauri::async_runtime::spawn(async move { - let response = media_proxy::handle_sprout_media(&app, &request).await; + let response = media_proxy::handle_buzz_media(&app, &request).await; responder.respond(response); }); }) @@ -520,6 +520,10 @@ pub fn run() { let app_handle = app.handle().clone(); let shutdown_started = Arc::clone(&restore_shutdown_started); + // Copy legacy app data into the Buzz app data directory + // before any state is loaded from disk. + migration::migrate_legacy_app_data_dir(&app_handle); + // Sync shared agent data from the canonical dev data directory to // this worktree's data directory. Must run before // restore_managed_agents_on_launch (which reads managed-agents.json). @@ -530,7 +534,7 @@ pub fn run() { migration::migrate_persona_provider_to_runtime(&app_handle); if let Err(e) = managed_agents::sync_team_personas(&app_handle) { - eprintln!("sprout-desktop: sync-team-personas: {e}"); + eprintln!("buzz-desktop: sync-team-personas: {e}"); } // Resolve persisted identity key (env var → file → generate+save). @@ -574,19 +578,19 @@ pub fn run() { .store(port, std::sync::atomic::Ordering::Relaxed); }); - // Create the Sprout nest (~/.sprout) before agents are restored, + // Create the Buzz nest (~/.buzz) before agents are restored, // so default_agent_workdir() resolves to the nest directory. // Non-fatal: agents fall back to $HOME if nest creation fails. if let Err(error) = ensure_nest() { - eprintln!("sprout-desktop: failed to create nest: {error}"); + eprintln!("buzz-desktop: failed to create nest: {error}"); } - // Create/update ~/.local/bin/sprout symlink pointing to the + // Create/update the local CLI symlink pointing to the // bundled CLI binary. Non-fatal: agents find CLI via PATH. if let Ok(exe) = std::env::current_exe() { if let Some(parent) = exe.parent() { if let Err(error) = managed_agents::ensure_cli_symlink(parent) { - eprintln!("sprout-desktop: failed to create CLI symlink: {error}"); + eprintln!("buzz-desktop: failed to create CLI symlink: {error}"); } } } @@ -604,7 +608,7 @@ pub fn run() { use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, Modifiers, Shortcut}; let shortcut = Shortcut::new(Some(Modifiers::CONTROL), Code::Space); if let Err(e) = app.handle().global_shortcut().register(shortcut) { - eprintln!("sprout-desktop: failed to register PTT shortcut: {e}"); + eprintln!("buzz-desktop: failed to register PTT shortcut: {e}"); } } @@ -628,7 +632,7 @@ pub fn run() { if let Err(error) = restore_managed_agents_on_launch(&app_handle, shutdown_started.as_ref()).await { - eprintln!("sprout-desktop: failed to restore managed agents: {error}"); + eprintln!("buzz-desktop: failed to restore managed agents: {error}"); } }); @@ -855,7 +859,7 @@ pub fn run() { } std::process::exit(0); }) { - eprintln!("sprout-desktop: failed to register signal handler: {e}"); + eprintln!("buzz-desktop: failed to register signal handler: {e}"); } } @@ -866,7 +870,7 @@ pub fn run() { if !run_shutdown_done.swap(true, Ordering::SeqCst) { prevent_sleep::release(&app_handle.state::().prevent_sleep); if let Err(error) = shutdown_managed_agents(app_handle) { - eprintln!("sprout-desktop: failed to stop managed agents: {error}"); + eprintln!("buzz-desktop: failed to stop managed agents: {error}"); } } } @@ -905,7 +909,7 @@ mod tests { #[test] fn parse_message_deep_link_extracts_required_params() { - let url = Url::parse("sprout://message?channel=abc&id=xyz").unwrap(); + let url = Url::parse("buzz://message?channel=abc&id=xyz").unwrap(); let payload = parse_message_deep_link(&url).expect("required params present"); assert_eq!(payload["channelId"], "abc"); assert_eq!(payload["messageId"], "xyz"); @@ -922,33 +926,33 @@ mod tests { #[test] fn parse_message_deep_link_includes_thread_root() { - let url = Url::parse("sprout://message?channel=abc&id=xyz&thread=root1").unwrap(); + let url = Url::parse("buzz://message?channel=abc&id=xyz&thread=root1").unwrap(); let payload = parse_message_deep_link(&url).expect("required params present"); assert_eq!(payload["threadRootId"], "root1"); } #[test] fn parse_message_deep_link_rejects_missing_id() { - let url = Url::parse("sprout://message?channel=abc").unwrap(); + let url = Url::parse("buzz://message?channel=abc").unwrap(); assert!(parse_message_deep_link(&url).is_none()); } #[test] fn parse_message_deep_link_rejects_empty_channel() { // Regression: `channel=&id=foo` previously produced channelId: "". - let url = Url::parse("sprout://message?channel=&id=foo").unwrap(); + let url = Url::parse("buzz://message?channel=&id=foo").unwrap(); assert!(parse_message_deep_link(&url).is_none()); } #[test] fn parse_message_deep_link_rejects_empty_id() { - let url = Url::parse("sprout://message?channel=abc&id=").unwrap(); + let url = Url::parse("buzz://message?channel=abc&id=").unwrap(); assert!(parse_message_deep_link(&url).is_none()); } #[test] fn parse_message_deep_link_treats_empty_thread_as_absent() { - let url = Url::parse("sprout://message?channel=abc&id=xyz&thread=").unwrap(); + let url = Url::parse("buzz://message?channel=abc&id=xyz&thread=").unwrap(); let payload = parse_message_deep_link(&url).expect("required params present"); assert!(payload["threadRootId"].is_null()); } diff --git a/desktop/src-tauri/src/main.rs b/desktop/src-tauri/src/main.rs index 0488c8271..13cb6c1b7 100644 --- a/desktop/src-tauri/src/main.rs +++ b/desktop/src-tauri/src/main.rs @@ -2,5 +2,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { - sprout_lib::run() + buzz_lib::run() } diff --git a/desktop/src-tauri/src/managed_agents/backend.rs b/desktop/src-tauri/src/managed_agents/backend.rs index 8ee8d16f9..0eada757d 100644 --- a/desktop/src-tauri/src/managed_agents/backend.rs +++ b/desktop/src-tauri/src/managed_agents/backend.rs @@ -408,10 +408,10 @@ pub fn validate_provider_config(config: &serde_json::Value) -> Result<(), String Ok(()) } -/// Enumerate PATH for sprout-backend-* executables. Returns (id, path) pairs. +/// Enumerate PATH for buzz-backend-* executables. Returns (id, path) pairs. /// Only includes files that are executable. Does NOT execute any binaries. pub fn discover_provider_candidates() -> Vec<(String, PathBuf)> { - let prefix = "sprout-backend-"; + let prefix = "buzz-backend-"; let mut seen = std::collections::HashSet::new(); let mut results = Vec::new(); @@ -441,7 +441,7 @@ pub fn discover_provider_candidates() -> Vec<(String, PathBuf)> { /// 3. Returns the canonical path of the discovered binary /// /// All deploy, start, and create paths MUST use this instead of raw -/// `resolve_command(format!("sprout-backend-{id}"))` to prevent a compromised +/// `resolve_command(format!("buzz-backend-{id}"))` to prevent a compromised /// frontend/IPC caller from steering execution to an arbitrary binary. pub fn resolve_provider_binary(provider_id: &str) -> Result { // Reject IDs that could be path components or shell metacharacters. @@ -467,7 +467,7 @@ pub fn resolve_provider_binary(provider_id: &str) -> Result { .canonicalize() .map_err(|e| format!("provider binary not accessible: {e}")), None => Err(format!( - "provider 'sprout-backend-{provider_id}' not found on PATH" + "provider 'buzz-backend-{provider_id}' not found on PATH" )), } } diff --git a/desktop/src-tauri/src/managed_agents/discovery.rs b/desktop/src-tauri/src/managed_agents/discovery.rs index 3e6e9f330..88f5c2d6a 100644 --- a/desktop/src-tauri/src/managed_agents/discovery.rs +++ b/desktop/src-tauri/src/managed_agents/discovery.rs @@ -11,7 +11,7 @@ pub(crate) struct KnownAcpRuntime { pub commands: &'static [&'static str], pub aliases: &'static [&'static str], pub avatar_url: &'static str, - /// Legacy MCP server binary field. Vestigial — all agents now use sprout CLI + /// Legacy MCP server binary field. Vestigial — all agents now use the bundled CLI. /// directly. Will be removed when runtime discovery is simplified. pub mcp_command: Option<&'static str>, /// Whether to enable MCP hook tools (`_Stop`, `_PostCompact`) for this agent. @@ -29,8 +29,8 @@ pub(crate) struct KnownAcpRuntime { /// Human-readable hint about installing the ACP adapter. pub adapter_install_hint: &'static str, /// Harness-specific skill discovery directory (e.g. `.goose/skills`). - /// `Some(dir)` → Sprout creates a symlink at `//sprout-cli` - /// pointing to the canonical `.agents/skills/sprout-cli`. `None` → this + /// `Some(dir)` → Buzz creates a symlink at `//buzz-cli` + /// pointing to the canonical `.agents/skills/buzz-cli`. `None` → this /// runtime reads the canonical path directly or has no skill support. pub skill_dir: Option<&'static str>, /// Whether this runtime handles model switching via ACP protocol natively. @@ -47,8 +47,8 @@ pub(crate) struct KnownAcpRuntime { const GOOSE_AVATAR_URL: &str = "https://goose-docs.ai/img/logo_dark.png"; const CLAUDE_CODE_AVATAR_URL: &str = "https://anthropic.gallerycdn.vsassets.io/extensions/anthropic/claude-code/2.1.77/1773707456892/Microsoft.VisualStudio.Services.Icons.Default"; const CODEX_AVATAR_URL: &str = "https://openai.gallerycdn.vsassets.io/extensions/openai/chatgpt/26.5313.41514/1773706730621/Microsoft.VisualStudio.Services.Icons.Default"; -const SPROUT_AGENT_AVATAR_URL: &str = - "https://raw.githubusercontent.com/block/sprout/refs/heads/main/crates/sprout-agent/sprout-agent.png"; +const BUZZ_AGENT_AVATAR_URL: &str = + "https://raw.githubusercontent.com/block/buzz/refs/heads/main/crates/buzz-agent/buzz-agent.png"; fn common_binary_paths() -> &'static [PathBuf] { use std::sync::OnceLock; @@ -137,18 +137,18 @@ const KNOWN_ACP_RUNTIMES: &[KnownAcpRuntime] = &[ default_env: &[], }, KnownAcpRuntime { - id: "sprout-agent", - label: "Sprout Agent", - commands: &["sprout-agent"], + id: "buzz-agent", + label: "Buzz Agent", + commands: &["buzz-agent"], aliases: &[], - avatar_url: SPROUT_AGENT_AVATAR_URL, - mcp_command: Some("sprout-dev-mcp"), + avatar_url: BUZZ_AGENT_AVATAR_URL, + mcp_command: Some("buzz-dev-mcp"), mcp_hooks: true, underlying_cli: None, cli_install_commands: &[], adapter_install_commands: &[], - install_instructions_url: "https://github.com/block/sprout", - cli_install_hint: "Ships with the Sprout desktop app.", + install_instructions_url: "https://github.com/block/buzz", + cli_install_hint: "Ships with the Buzz desktop app.", adapter_install_hint: "", skill_dir: None, supports_acp_model_switching: true, @@ -232,7 +232,7 @@ fn default_agent_args(command: &str) -> Option> { match normalize_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, } } @@ -288,17 +288,37 @@ fn command_search_dirs() -> Vec { unique } +fn is_executable_file(path: &Path) -> bool { + let Ok(metadata) = path.metadata() else { + return false; + }; + if !metadata.is_file() { + return false; + } + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + metadata.permissions().mode() & 0o111 != 0 + } + + #[cfg(not(unix))] + { + true + } +} + fn resolve_workspace_command(command: &str) -> Option { if command_looks_like_path(command) { let path = PathBuf::from(command); - return path.exists().then_some(path); + return is_executable_file(&path).then_some(path); } let file_name = executable_basename(command); command_search_dirs() .into_iter() .map(|dir| dir.join(&file_name)) - .find(|candidate| candidate.exists()) + .find(|candidate| is_executable_file(candidate)) } fn resolve_cache() -> &'static std::sync::Mutex>> @@ -351,7 +371,7 @@ fn resolve_command_uncached(command: &str) -> Option { } for candidate in path_candidates_from_env(command) { - if candidate.exists() { + if is_executable_file(&candidate) { return Some(candidate); } } @@ -361,7 +381,7 @@ fn resolve_command_uncached(command: &str) -> Option { } for dir in common_binary_paths() { let candidate = dir.join(executable_basename(command)); - if candidate.exists() { + if is_executable_file(&candidate) { return Some(candidate); } } @@ -401,7 +421,7 @@ fn find_via_login_shell(command: &str) -> Option { let stdout = run_in_login_shell(&["-l", "-c", r#"command -v -- "$1""#, "_", command])?; let resolved = stdout.lines().rfind(|line| !line.trim().is_empty())?; let path = PathBuf::from(resolved.trim()); - (path.is_absolute() && path.exists()).then_some(path) + (path.is_absolute() && is_executable_file(&path)).then_some(path) } /// Return the user's full PATH from a login shell. @@ -543,7 +563,7 @@ mod tests { use super::{ classify_runtime, find_via_login_shell, managed_agent_avatar_url, normalize_agent_args, - CLAUDE_CODE_AVATAR_URL, CODEX_AVATAR_URL, GOOSE_AVATAR_URL, SPROUT_AGENT_AVATAR_URL, + BUZZ_AGENT_AVATAR_URL, CLAUDE_CODE_AVATAR_URL, CODEX_AVATAR_URL, GOOSE_AVATAR_URL, }; use crate::managed_agents::AcpAvailabilityStatus; @@ -596,25 +616,25 @@ mod tests { } #[test] - fn resolves_sprout_agent_avatar() { + fn resolves_buzz_agent_avatar() { assert_eq!( - managed_agent_avatar_url("sprout-agent"), - Some(SPROUT_AGENT_AVATAR_URL.to_string()) + managed_agent_avatar_url("buzz-agent"), + Some(BUZZ_AGENT_AVATAR_URL.to_string()) ); assert_eq!( - managed_agent_avatar_url("/usr/local/bin/sprout-agent"), - Some(SPROUT_AGENT_AVATAR_URL.to_string()) + managed_agent_avatar_url("/usr/local/bin/buzz-agent"), + Some(BUZZ_AGENT_AVATAR_URL.to_string()) ); } #[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() ); } @@ -622,7 +642,7 @@ mod tests { #[test] fn login_shell_lookup_treats_command_as_data() { let marker = - std::env::temp_dir().join(format!("sprout-discovery-marker-{}", uuid::Uuid::new_v4())); + std::env::temp_dir().join(format!("buzz-discovery-marker-{}", uuid::Uuid::new_v4())); let payload = format!("doesnotexist; touch {} #", marker.display()); let resolved = find_via_login_shell(&payload); @@ -637,6 +657,34 @@ mod tests { ); } + #[cfg(unix)] + #[test] + fn explicit_path_resolution_ignores_non_executable_files() { + use std::os::unix::fs::PermissionsExt; + + let dir = + std::env::temp_dir().join(format!("buzz-discovery-path-{}", uuid::Uuid::new_v4())); + std::fs::create_dir_all(&dir).expect("create temp dir"); + let bin = dir.join("buzz-acp"); + std::fs::write(&bin, "").expect("write placeholder"); + std::fs::set_permissions(&bin, std::fs::Permissions::from_mode(0o644)) + .expect("chmod placeholder"); + + assert!( + super::resolve_workspace_command(bin.to_str().expect("utf8 path")).is_none(), + "non-executable placeholder must not resolve" + ); + + std::fs::set_permissions(&bin, std::fs::Permissions::from_mode(0o755)) + .expect("chmod executable"); + assert_eq!( + super::resolve_workspace_command(bin.to_str().expect("utf8 path")), + Some(bin.clone()) + ); + + let _ = std::fs::remove_dir_all(dir); + } + #[test] fn classifies_available_when_adapter_found() { let (status, cmd, path) = classify_runtime( diff --git a/desktop/src-tauri/src/managed_agents/env_vars.rs b/desktop/src-tauri/src/managed_agents/env_vars.rs index 27bfe944e..979bbcde1 100644 --- a/desktop/src-tauri/src/managed_agents/env_vars.rs +++ b/desktop/src-tauri/src/managed_agents/env_vars.rs @@ -55,7 +55,7 @@ pub(crate) fn filter_derived_provider_model_env_vars( .collect() } -/// Env var keys that Sprout sets itself and users must not override from +/// Env var keys that Buzz sets itself and users must not override from /// the persona/agent env_vars UI. Three categories: /// /// 1. **Identity / secrets** — overriding would swap the agent's nsec or @@ -107,8 +107,8 @@ pub(crate) fn is_reserved_env_key(key: &str) -> bool { /// nit: Rust's `Command::env` will happily accept a key containing `=` /// or whitespace and pass it straight into the child's environ block, /// where `getenv("FOO")` then matches whatever comes after the first -/// `=`. That means a key like `SPROUT_AUTH_TAG=x` with value `forged` -/// lands as `SPROUT_AUTH_TAG=x=forged` in the child env and +/// `=`. That means a key like `BUZZ_AUTH_TAG=x` with value `forged` +/// lands as `BUZZ_AUTH_TAG=x=forged` in the child env and /// `getenv("BUZZ_AUTH_TAG")` returns `"x=forged"` — a full reserved- /// key bypass. Rejecting non-POSIX keys closes this hole at the /// boundary where the input enters the system. @@ -200,7 +200,7 @@ pub fn validate_user_env_keys(env_vars: &BTreeMap) -> Result<(), reserved.dedup(); if !reserved.is_empty() { return Err(format!( - "the following env vars are reserved by Sprout and cannot be overridden: {}", + "the following env vars are reserved by Buzz and cannot be overridden: {}", reserved.join(", ") )); } @@ -260,7 +260,7 @@ pub(crate) fn merged_user_env( merged.retain(|k, v| { if is_reserved_env_key(k) { eprintln!( - "sprout-desktop: ignoring reserved env var `{k}` from persona/agent overrides" + "buzz-desktop: ignoring reserved env var `{k}` from persona/agent overrides" ); return false; } @@ -270,7 +270,7 @@ pub(crate) fn merged_user_env( // smuggle a reserved key past us via `=`-in-key tricks. See // `is_well_formed_env_key` for the exploit. eprintln!( - "sprout-desktop: ignoring malformed env var key `{}` from persona/agent overrides", + "buzz-desktop: ignoring malformed env var key `{}` from persona/agent overrides", display_invalid_key(k) ); return false; @@ -280,13 +280,13 @@ pub(crate) fn merged_user_env( // have escaped the value validator; drop them here rather // than crash the spawn. We deliberately do NOT log the value. eprintln!( - "sprout-desktop: ignoring env var `{k}` with NUL byte in value" + "buzz-desktop: ignoring env var `{k}` with NUL byte in value" ); return false; } if v.len() > MAX_ENV_VALUE_BYTES { eprintln!( - "sprout-desktop: ignoring env var `{k}` with oversize value ({} bytes > {MAX_ENV_VALUE_BYTES})", + "buzz-desktop: ignoring env var `{k}` with oversize value ({} bytes > {MAX_ENV_VALUE_BYTES})", v.len() ); return false; diff --git a/desktop/src-tauri/src/managed_agents/env_vars/tests.rs b/desktop/src-tauri/src/managed_agents/env_vars/tests.rs index 9b0d56ade..f4f55c206 100644 --- a/desktop/src-tauri/src/managed_agents/env_vars/tests.rs +++ b/desktop/src-tauri/src/managed_agents/env_vars/tests.rs @@ -161,7 +161,7 @@ fn reserved_keys_include_respond_to_gate() { #[test] fn reserved_keys_include_code_execution_surface() { - // The agent/MCP command + args are what Sprout actually exec's. + // The agent/MCP command + args are what Buzz actually exec's. // Overriding lets the user run arbitrary code as the agent. for key in [ "BUZZ_ACP_AGENT_COMMAND", diff --git a/desktop/src-tauri/src/managed_agents/mod.rs b/desktop/src-tauri/src/managed_agents/mod.rs index 263ee7d26..f10376a27 100644 --- a/desktop/src-tauri/src/managed_agents/mod.rs +++ b/desktop/src-tauri/src/managed_agents/mod.rs @@ -29,12 +29,12 @@ pub use team_repair::sync_team_personas; pub use teams::*; pub use types::*; -/// Returns the Sprout nest directory (`~/.sprout`) if it exists as a real +/// Returns the Buzz nest directory (`~/.buzz`) if it exists as a real /// directory (not a symlink), falling back to the user's home directory. /// /// Used as the default working directory for spawned agent processes. /// `ensure_nest()` must be called during app setup before this is first -/// invoked, so that `~/.sprout` exists and gets cached. +/// invoked, so that `~/.buzz` exists and gets cached. /// /// Cached for the process lifetime via `OnceLock`. /// Returns `None` in sandboxed/containerized environments where `$HOME` is @@ -45,7 +45,7 @@ pub fn default_agent_workdir() -> Option { static WORKDIR: OnceLock> = OnceLock::new(); WORKDIR .get_or_init(|| { - // Prefer ~/.sprout if it exists (created by ensure_nest()). + // Prefer ~/.buzz if it exists (created by ensure_nest()). // Reject symlinks to prevent redirect attacks — is_dir() // follows symlinks, so check symlink_metadata() first. // Fall back to $HOME for resilience. diff --git a/desktop/src-tauri/src/managed_agents/nest.rs b/desktop/src-tauri/src/managed_agents/nest.rs index 2f300b62a..8b1f3c62d 100644 --- a/desktop/src-tauri/src/managed_agents/nest.rs +++ b/desktop/src-tauri/src/managed_agents/nest.rs @@ -1,7 +1,7 @@ -//! Sprout Nest — persistent agent workspace at `~/.sprout`. +//! Buzz Nest — persistent agent workspace at `~/.buzz`. //! //! Creates a shared knowledge directory on first launch so every -//! Sprout-spawned agent starts with orientation (AGENTS.md) and a +//! Buzz-spawned agent starts with orientation (AGENTS.md) and a //! place to accumulate research, plans, and logs across sessions. //! //! Static template content in AGENTS.md (above the managed-section markers) @@ -34,9 +34,9 @@ const NEST_DIRS: &[&str] = &[ /// Fully static — no runtime interpolation, no secrets, no user paths. const AGENTS_MD: &str = include_str!("nest_agents.md"); -/// Default SKILL.md content for the sprout-cli skill. -/// Written to ~/.sprout/.agents/skills/sprout-cli/SKILL.md on first init. -const SPROUT_CLI_SKILL_MD: &str = include_str!("nest_skill.md"); +/// Default SKILL.md content for the buzz-cli skill. +/// Written to ~/.buzz/.agents/skills/buzz-cli/SKILL.md on first init. +const BUZZ_CLI_SKILL_MD: &str = include_str!("nest_skill.md"); /// Template content version for AGENTS.md static content (above managed markers). /// Bump this when changing `nest_agents.md` to trigger refresh on existing installs. @@ -47,18 +47,18 @@ const NEST_AGENTS_VERSION: u32 = 4; /// Bump this when changing `nest_skill.md` to trigger refresh on existing installs. const NEST_SKILL_VERSION: u32 = 3; -const BEGIN_MARKER: &str = ""; +const BEGIN_MARKER: &str = ""; /// Canonical skill directory path relative to the nest root. -const CANONICAL_SKILL_DIR: &str = ".agents/skills/sprout-cli"; -/// Returns the nest root path (`~/.sprout`), or `None` if the home +const CANONICAL_SKILL_DIR: &str = ".agents/skills/buzz-cli"; +/// Returns the nest root path (`~/.buzz`), or `None` if the home /// directory cannot be resolved. pub fn nest_dir() -> Option { - dirs::home_dir().map(|h| h.join(".sprout")) + dirs::home_dir().map(|h| h.join(".buzz")) } -/// Creates the Sprout nest at `~/.sprout` if it doesn't already exist. +/// Creates the Buzz nest at `~/.buzz` if it doesn't already exist. /// /// Delegates to [`ensure_nest_at`] with the resolved nest directory. /// Returns an error string if the home directory cannot be resolved. @@ -67,13 +67,13 @@ pub fn ensure_nest() -> Result<(), String> { ensure_nest_at(&root) } -/// Creates a Sprout nest at the given `root` path. +/// Creates a Buzz nest at the given `root` path. /// /// - Creates the root directory and all subdirectories. /// - Writes `AGENTS.md` only if it doesn't already exist. -/// - Writes `.agents/skills/sprout-cli/SKILL.md` only if it doesn't already exist. +/// - Writes `.agents/skills/buzz-cli/SKILL.md` only if it doesn't already exist. /// - Creates harness-specific symlinks pointing to the canonical -/// `.agents/skills/sprout-cli` directory for each known provider. +/// `.agents/skills/buzz-cli` directory for each known provider. /// - Sets 700 permissions on the root, all subdirectories, and the skill /// directory tree (Unix). /// @@ -132,7 +132,7 @@ pub fn ensure_nest_at(root: &Path) -> Result<(), String> { } } - // Write sprout-cli skill to the harness-agnostic .agents path. + // Write buzz-cli skill to the harness-agnostic .agents path. // The first-init write uses the new canonical path; migration from // the old .claude path is handled in refresh_skill_md_if_stale. let agents_skill_dir = root.join(CANONICAL_SKILL_DIR); @@ -147,7 +147,7 @@ pub fn ensure_nest_at(root: &Path) -> Result<(), String> { { Ok(mut file) => { use std::io::Write; - file.write_all(SPROUT_CLI_SKILL_MD.as_bytes()) + file.write_all(BUZZ_CLI_SKILL_MD.as_bytes()) .map_err(|e| format!("write {}: {e}", skill_md.display()))?; } Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {} @@ -157,7 +157,7 @@ pub fn ensure_nest_at(root: &Path) -> Result<(), String> { } // Create harness-specific symlinks for all known providers. - // Migration of the old .claude/skills/sprout-cli real dir is handled in + // Migration of the old .claude/skills/buzz-cli real dir is handled in // refresh_skill_md_if_stale; ensure_skill_symlinks skips paths that already exist. ensure_skill_symlinks(root)?; @@ -225,7 +225,7 @@ fn ensure_skill_symlinks(root: &Path) -> Result<(), String> { for skill_dir in known_skill_dirs() { let parent = root.join(skill_dir); fs::create_dir_all(&parent).map_err(|e| format!("create {}: {e}", parent.display()))?; - let link = parent.join("sprout-cli"); + let link = parent.join("buzz-cli"); if link.symlink_metadata().is_ok() { continue; // symlink or real path exists — skip } @@ -243,18 +243,18 @@ fn ensure_skill_symlinks(_root: &Path) -> Result<(), String> { Ok(()) } -/// Ensures `~/.local/bin/sprout` is a symlink to the bundled CLI binary. +/// Ensures `~/.local/bin/buzz` is a symlink to the bundled CLI binary. /// /// Creates the symlink if it doesn't exist, updates it if it already points -/// to a Sprout app bundle, and leaves it alone if it points elsewhere (to +/// to a Buzz app bundle, and leaves it alone if it points elsewhere (to /// avoid clobbering another tool's binary). /// /// Non-fatal: callers should ignore errors — the symlink is a convenience /// for human Terminal use; agents find the CLI via PATH augmentation. #[cfg(unix)] pub fn ensure_cli_symlink(exe_parent: &Path) -> Result<(), String> { - let sprout_bin = exe_parent.join("sprout"); - if !sprout_bin.exists() { + let buzz_bin = exe_parent.join("buzz"); + if !buzz_bin.exists() { return Ok(()); // CLI not bundled (e.g., dev builds without sidecars). } @@ -264,16 +264,16 @@ pub fn ensure_cli_symlink(exe_parent: &Path) -> Result<(), String> { .join("bin"); fs::create_dir_all(&local_bin).map_err(|e| format!("create {}: {e}", local_bin.display()))?; - let link = local_bin.join("sprout"); + let link = local_bin.join("buzz"); match link.symlink_metadata() { Ok(meta) if meta.file_type().is_symlink() => { - // Symlink exists — only update if it points to a Sprout bundle. + // Symlink exists — only update if it points to a Buzz bundle. if let Ok(target) = fs::read_link(&link) { let target_str = target.display().to_string(); if target_str.contains(".app/Contents/MacOS") { - // Sprout-owned symlink — update to current bundle path. + // Buzz-owned symlink — update to current bundle path. let _ = fs::remove_file(&link); - std::os::unix::fs::symlink(&sprout_bin, &link) + std::os::unix::fs::symlink(&buzz_bin, &link) .map_err(|e| format!("symlink {}: {e}", link.display()))?; } // Otherwise: symlink points elsewhere — don't clobber. @@ -284,7 +284,7 @@ pub fn ensure_cli_symlink(exe_parent: &Path) -> Result<(), String> { } Err(e) if e.kind() == std::io::ErrorKind::NotFound => { // No file exists — create the symlink. - std::os::unix::fs::symlink(&sprout_bin, &link) + std::os::unix::fs::symlink(&buzz_bin, &link) .map_err(|e| format!("symlink {}: {e}", link.display()))?; } Err(e) => { @@ -311,7 +311,7 @@ fn read_version_file(path: &Path) -> u32 { /// Refresh AGENTS.md static content if the template version has changed. /// -/// Preserves everything from the `\nold section\n\n\nafter\n", + "# Header\n\nsome content\n\n\nold section\n\n\nafter\n", ) .unwrap(); upsert_managed_section(&file, "new section").unwrap(); let result = fs::read_to_string(&file).unwrap(); - assert!(result.contains("")); + assert!(result.contains("")); assert!(result.contains("new section")); assert!(!result.contains("old section")); assert!(result.contains("# Header")); @@ -1052,10 +1052,10 @@ mod tests { let result = fs::read_to_string(&file).unwrap(); assert!(result.contains("# Header")); assert!(result.contains("existing content")); - assert!(result.contains("")); + assert!(result.contains("")); assert!(result.contains("injected section")); - let begin_pos = result.find("\nsome middle content\n\nold section\n", + "# Header\n\n\nsome middle content\n\nold section\n", ) .unwrap(); @@ -1142,7 +1142,7 @@ mod tests { let file = tmp.path().join("AGENTS.md"); fs::write( &file, - "# Header\n\nsome content\n\n\norphaned section without end marker\n", + "# Header\n\nsome content\n\n\norphaned section without end marker\n", ) .unwrap(); @@ -1183,7 +1183,7 @@ mod tests { let file = tmp.path().join("AGENTS.md"); fs::write( &file, - "# Header\n\n\nfirst block\n\n\nbetween blocks\n\n\nsecond block\n\n", + "# Header\n\n\nfirst block\n\n\nbetween blocks\n\n\nsecond block\n\n", ) .unwrap(); @@ -1216,7 +1216,7 @@ mod tests { // Indented by 4 spaces — not at column 0, so should NOT match as a real marker. fs::write( &file, - "# Header\n\n \n\nReal content here\n", + "# Header\n\n \n\nReal content here\n", ) .unwrap(); @@ -1225,7 +1225,7 @@ mod tests { let result = fs::read_to_string(&file).unwrap(); assert!( - result.contains(" "), + result.contains(" "), "indented marker inside code block must be preserved verbatim" ); assert!( @@ -1239,7 +1239,7 @@ mod tests { // The real markers appended at the end must be at line-start (column 0). let begin_pos = result - .find("\nexisting section\n\n", + "# Header\n\n\nexisting section\n\n", ) .unwrap(); @@ -1323,7 +1323,7 @@ mod tests { #[test] fn refresh_agents_md_writes_version_file() { let tmp = tempfile::tempdir().unwrap(); - let root = tmp.path().join(".sprout"); + let root = tmp.path().join(".buzz"); ensure_nest_at(&root).unwrap(); let version = fs::read_to_string(root.join(".nest-agents-version")).unwrap(); assert_eq!(version.trim(), NEST_AGENTS_VERSION.to_string()); @@ -1332,17 +1332,17 @@ mod tests { #[test] fn refresh_skill_md_writes_version_file() { let tmp = tempfile::tempdir().unwrap(); - let root = tmp.path().join(".sprout"); + let root = tmp.path().join(".buzz"); ensure_nest_at(&root).unwrap(); let version = - fs::read_to_string(root.join(".agents/skills/sprout-cli/.skill-version")).unwrap(); + fs::read_to_string(root.join(".agents/skills/buzz-cli/.skill-version")).unwrap(); assert_eq!(version.trim(), NEST_SKILL_VERSION.to_string()); } #[test] fn refresh_agents_md_preserves_managed_section() { let tmp = tempfile::tempdir().unwrap(); - let root = tmp.path().join(".sprout"); + let root = tmp.path().join(".buzz"); ensure_nest_at(&root).unwrap(); // Simulate a managed section update. @@ -1362,7 +1362,7 @@ mod tests { let content = fs::read_to_string(&agents_md).unwrap(); // Static content should be refreshed (from template). assert!( - content.starts_with("# Sprout Nest"), + content.starts_with("# Buzz Nest"), "template header must be present" ); // Managed section should be preserved. @@ -1377,7 +1377,7 @@ mod tests { #[test] fn refresh_skips_when_version_current() { let tmp = tempfile::tempdir().unwrap(); - let root = tmp.path().join(".sprout"); + let root = tmp.path().join(".buzz"); ensure_nest_at(&root).unwrap(); // Manually change AGENTS.md content after version file is written. @@ -1397,20 +1397,20 @@ mod tests { #[test] fn refresh_skill_overwrites_on_version_bump() { let tmp = tempfile::tempdir().unwrap(); - let root = tmp.path().join(".sprout"); + let root = tmp.path().join(".buzz"); ensure_nest_at(&root).unwrap(); - let skill_md = root.join(".agents/skills/sprout-cli/SKILL.md"); + let skill_md = root.join(".agents/skills/buzz-cli/SKILL.md"); fs::write(&skill_md, "stale skill content").unwrap(); // Remove version file to simulate upgrade. - let _ = fs::remove_file(root.join(".agents/skills/sprout-cli/.skill-version")); + let _ = fs::remove_file(root.join(".agents/skills/buzz-cli/.skill-version")); ensure_nest_at(&root).unwrap(); let content = fs::read_to_string(&skill_md).unwrap(); assert_eq!( - content, SPROUT_CLI_SKILL_MD, + content, BUZZ_CLI_SKILL_MD, "SKILL.md must be refreshed on version bump" ); } diff --git a/desktop/src-tauri/src/managed_agents/nest_agents.md b/desktop/src-tauri/src/managed_agents/nest_agents.md index eed14ff20..427018be7 100644 --- a/desktop/src-tauri/src/managed_agents/nest_agents.md +++ b/desktop/src-tauri/src/managed_agents/nest_agents.md @@ -1,6 +1,6 @@ -# Sprout Nest +# Buzz Nest -Your persistent workspace. Created once by the Sprout desktop app. The static content above the managed-section markers is regenerated on upgrades — add custom notes below the markers or in separate files. +Your persistent workspace. Created once by the Buzz desktop app. The static content above the managed-section markers is regenerated on upgrades — add custom notes below the markers or in separate files. ## Directory Layout @@ -16,7 +16,7 @@ Your persistent workspace. Created once by the Sprout desktop app. The static co Filenames: `ALL_CAPS_WITH_UNDERSCORES.md` (e.g., `OAUTH_FLOW_NOTES.md`). -The `sprout` CLI is your primary tool interface — run `sprout --help` for commands. The CLI skill file has the full reference. +The bundled CLI is your primary tool interface — run its `--help` command for usage. The CLI skill file has the full reference. ## Knowledge File Conventions @@ -54,9 +54,9 @@ The human operator signs off for accountability. - **Signing:** if the agent has a registered signing key, sign commits. If not, commits will land unverified — this is acceptable until agent SSH keys are provisioned. Do NOT use the human's signing key. - **Verify before pushing:** `git log -1` should show the human's `Signed-off-by` trailer. - + ## Active Agents -*(No agents deployed yet. Add agents in the Sprout desktop app.)* +*(No agents deployed yet. Add agents in the Buzz desktop app.)* - + diff --git a/desktop/src-tauri/src/managed_agents/nest_skill.md b/desktop/src-tauri/src/managed_agents/nest_skill.md index 5117d396a..3dbf780e0 100644 --- a/desktop/src-tauri/src/managed_agents/nest_skill.md +++ b/desktop/src-tauri/src/managed_agents/nest_skill.md @@ -1,20 +1,20 @@ --- -name: sprout-cli +name: buzz-cli description: > - Sprout CLI for relay operations: messaging, channels, DMs, users, workflows, + Buzz CLI for relay operations: messaging, channels, DMs, users, workflows, feed, reactions, canvas, social, repos, uploads, and agent memory. version: 1 --- -# Sprout CLI Skill +# Buzz CLI Skill ## Environment -`SPROUT_PRIVATE_KEY` is set by the harness at runtime or by the developer's environment. If missing, tell the user to set it (hex or nsec format). Never read or echo the value. +`BUZZ_PRIVATE_KEY` is set by the harness at runtime or by the developer's environment. If missing, tell the user to set it (hex or nsec format). Never read or echo the value. -`SPROUT_RELAY_URL` defaults to `http://localhost:3000`. In development, the user may need to set this to a staging or production relay URL. +`BUZZ_RELAY_URL` defaults to `http://localhost:3000`. In development, the user may need to set this to a staging or production relay URL. -Run `sprout --help` and `sprout --help` to discover all flags, arguments, and usage. This skill documents only what `--help` cannot tell you. +Run the bundled CLI with `--help` and ` --help` to discover all flags, arguments, and usage. This skill documents only what `--help` cannot tell you. ## Output Contracts @@ -45,10 +45,10 @@ Output varies by command group — `--help` shows flags but not response shapes. `--format compact` is a global flag — position it before the subcommand: ```bash -sprout --format compact channels list # [{channel_id, name}] -sprout --format compact messages get --channel # [{id, content, created_at}] -sprout --format compact users get # [{pubkey, display_name}] -sprout --format compact feed get # [{id, content, created_at}] +buzz --format compact channels list # [{channel_id, name}] +buzz --format compact messages get --channel # [{id, content, created_at}] +buzz --format compact users get # [{pubkey, display_name}] +buzz --format compact feed get # [{id, content, created_at}] ``` Write commands are unaffected. `--format json` (default) returns full fields. @@ -59,10 +59,10 @@ Write commands are unaffected. `--format json` (default) returns full fields. ```bash # ✅ Correct — notification delivered automatically -sprout messages send --channel --content "@Alice check this" +buzz messages send --channel --content "@Alice check this" # Multiple mentions — same pattern -sprout messages send --channel --content "@Alice @Bob review please" +buzz messages send --channel --content "@Alice @Bob review please" ``` ## DM Management @@ -98,7 +98,7 @@ sprout messages send --channel --content "@Alice @Bob review please" 4. **`dms open` returns `dm_id`** — use this value as `--channel` for subsequent `messages send/get` commands on that DM. 5. **Content max 65,536 bytes** (exit 1 if exceeded). Diffs auto-truncate at 61,440 bytes at a hunk boundary. 6. **`users get` always returns an array** — even for a single pubkey lookup. Never expect a bare object. -7. **All `mem` subcommands accept `--owner `** — for querying or writing memories owned by a different pubkey in multi-agent scenarios. Defaults to the owner from `SPROUT_AUTH_TAG`. +7. **All `mem` subcommands accept `--owner `** — for querying or writing memories owned by a different pubkey in multi-agent scenarios. Defaults to the owner from `BUZZ_AUTH_TAG`. 8. **`mem rm` cannot delete `core`** — use `mem set core ''` instead. ## Forum Posts @@ -125,9 +125,9 @@ Message content is rendered as GitHub-flavored Markdown on both desktop and mobi For safe concurrent writes, use hash-based conflict detection: ```bash -HASH=$(sprout mem hash ) # 1. get current SHA-256 +HASH=$(buzz mem hash ) # 1. get current SHA-256 # ... build unified diff ... -sprout mem patch --base-hash "$HASH" --patch-file diff.patch # 2. apply with check +buzz mem patch --base-hash "$HASH" --patch-file diff.patch # 2. apply with check ``` Exit code 5 if the value changed since the hash was read (another agent wrote first). Retry by re-reading, re-diffing, and re-patching. @@ -138,9 +138,9 @@ Flags: `--dry-run` to preview without writing, `--no-base-hash` to skip conflict The relay has no push or webhook support. Poll with a `--since` cursor: -1. `sprout messages get --channel --limit 50` — note the maximum `created_at` from results +1. `buzz messages get --channel --limit 50` — note the maximum `created_at` from results 2. Sleep 10-30 seconds -3. `sprout messages get --channel --since --limit 50` +3. `buzz messages get --channel --since --limit 50` 4. Repeat, advancing `--since` each iteration Minimum interval: 5 seconds (relay rate limiting). Use 10s for low-latency, 30s for background monitoring. `feed get` always returns newest-first regardless of `--since`. diff --git a/desktop/src-tauri/src/managed_agents/persona_card.rs b/desktop/src-tauri/src/managed_agents/persona_card.rs index 48466302b..68d9b30a4 100644 --- a/desktop/src-tauri/src/managed_agents/persona_card.rs +++ b/desktop/src-tauri/src/managed_agents/persona_card.rs @@ -51,19 +51,19 @@ pub fn parse_png_persona(png_bytes: &[u8]) -> Result = None; + let mut buzz_text: Option<&str> = None; let mut chara_text: Option<&str> = None; for chunk in &info.uncompressed_latin1_text { match chunk.keyword.as_str() { - "sprout_persona" if sprout_text.is_none() => sprout_text = Some(&chunk.text), + "buzz_persona_pkg" if buzz_text.is_none() => buzz_text = Some(&chunk.text), "chara" | "ccv3" if chara_text.is_none() => chara_text = Some(&chunk.text), _ => {} } } - let fields = if let Some(text) = sprout_text { - parse_sprout_payload(text)? + let fields = if let Some(text) = buzz_text { + parse_buzz_payload(text)? } else if let Some(text) = chara_text { parse_chara_payload(text)? } else { @@ -96,8 +96,8 @@ fn decode_b64_json(b64: &str) -> Result { serde_json::from_slice(&bytes).map_err(|e| format!("Invalid JSON: {e}")) } -/// Extracted fields from a Sprout persona JSON payload. -struct SproutPersonaFields { +/// Extracted fields from a Buzz persona JSON payload. +struct BuzzPersonaFields { display_name: String, system_prompt: String, avatar_url: Option, @@ -107,9 +107,9 @@ struct SproutPersonaFields { name_pool: Vec, } -/// Extract and validate fields from a Sprout persona JSON value +/// Extract and validate fields from a Buzz persona JSON value /// (shared by both the PNG tEXt-chunk path and the standalone JSON path). -fn extract_sprout_fields(v: &Value) -> Result { +fn extract_buzz_fields(v: &Value) -> Result { let version = v.get("version").and_then(|v| v.as_u64()).unwrap_or(0); if version != 1 { return Err(format!("Unsupported persona version: {version}")); @@ -172,7 +172,7 @@ fn extract_sprout_fields(v: &Value) -> Result { .collect() }) .unwrap_or_default(); - Ok(SproutPersonaFields { + Ok(BuzzPersonaFields { display_name: name, system_prompt: prompt, avatar_url, @@ -183,12 +183,12 @@ fn extract_sprout_fields(v: &Value) -> Result { }) } -fn parse_sprout_payload(b64: &str) -> Result { +fn parse_buzz_payload(b64: &str) -> Result { let v = decode_b64_json(b64)?; - extract_sprout_fields(&v) + extract_buzz_fields(&v) } -fn parse_chara_payload(b64: &str) -> Result { +fn parse_chara_payload(b64: &str) -> Result { let v = decode_b64_json(b64)?; let data = v.get("data").ok_or("Missing 'data' in chara payload")?; let name = data @@ -217,7 +217,7 @@ fn parse_chara_payload(b64: &str) -> Result { if prompt.is_empty() { return Err("Chara card has no system_prompt or description".to_string()); } - Ok(SproutPersonaFields { + Ok(BuzzPersonaFields { display_name: name, system_prompt: prompt, avatar_url: None, @@ -234,7 +234,7 @@ fn parse_chara_payload(b64: &str) -> Result { pub fn parse_json_persona(json_bytes: &[u8]) -> Result { let v: Value = serde_json::from_slice(json_bytes).map_err(|e| format!("Invalid JSON: {e}"))?; - let fields = extract_sprout_fields(&v)?; + let fields = extract_buzz_fields(&v)?; Ok(ParsedPersonaPreview { display_name: fields.display_name, @@ -288,13 +288,13 @@ pub fn encode_persona_json( pub fn parse_md_persona(md_bytes: &[u8]) -> Result { let content = std::str::from_utf8(md_bytes).map_err(|e| format!("Invalid UTF-8 in .persona.md: {e}"))?; - let config = sprout_persona::persona::parse_persona_md(content) + let config = buzz_persona_pkg::persona::parse_persona_md(content) .map_err(|e| format!("Failed to parse .persona.md: {e}"))?; // Split "provider:model" into separate fields for the preview. let model = match config.model.as_deref() { Some(s) if !s.is_empty() => { - let (_prov, id) = sprout_persona::persona::split_model(s); + let (_prov, id) = buzz_persona_pkg::persona::split_model(s); Some(id.to_owned()) } _ => None, @@ -395,7 +395,7 @@ pub fn parse_zip_pack(zip_bytes: &[u8]) -> Result = resolved @@ -572,7 +572,7 @@ mod tests { buf } - /// Helper: build a PNG with a sprout_persona tEXt chunk for the given name/prompt. + /// Helper: build a PNG with a buzz_persona_pkg tEXt chunk for the given name/prompt. fn make_test_persona_png(name: &str, prompt: &str) -> Vec { let payload = serde_json::json!({ "version": 1, @@ -580,7 +580,7 @@ mod tests { "systemPrompt": prompt, }); let b64 = STANDARD.encode(payload.to_string().as_bytes()); - make_png_with_text("sprout_persona", &b64) + make_png_with_text("buzz_persona_pkg", &b64) } /// Helper: build a plain PNG with no metadata. @@ -632,14 +632,14 @@ mod tests { fn parse_png_unknown_version() { let payload = serde_json::json!({"version": 99, "displayName": "X", "systemPrompt": "Y"}); let b64 = STANDARD.encode(payload.to_string().as_bytes()); - let png = make_png_with_text("sprout_persona", &b64); + let png = make_png_with_text("buzz_persona_pkg", &b64); let err = parse_png_persona(&png).unwrap_err(); assert!(err.contains("Unsupported persona version")); } #[test] fn parse_png_malformed_base64() { - let png = make_png_with_text("sprout_persona", "!!!not-base64!!!"); + let png = make_png_with_text("buzz_persona_pkg", "!!!not-base64!!!"); let err = parse_png_persona(&png).unwrap_err(); assert!(err.contains("Invalid base64")); } @@ -647,7 +647,7 @@ mod tests { #[test] fn parse_png_malformed_json() { let b64 = STANDARD.encode(b"not json at all"); - let png = make_png_with_text("sprout_persona", &b64); + let png = make_png_with_text("buzz_persona_pkg", &b64); let err = parse_png_persona(&png).unwrap_err(); assert!(err.contains("Invalid JSON")); } @@ -656,7 +656,7 @@ mod tests { fn parse_png_empty_fields() { let payload = serde_json::json!({"version": 1, "displayName": "", "systemPrompt": "Y"}); let b64 = STANDARD.encode(payload.to_string().as_bytes()); - let png = make_png_with_text("sprout_persona", &b64); + let png = make_png_with_text("buzz_persona_pkg", &b64); let err = parse_png_persona(&png).unwrap_err(); assert!(err.contains("displayName is empty")); } @@ -680,14 +680,14 @@ mod tests { } #[test] - fn parse_png_chara_ignored_when_sprout_present() { - // Build a PNG with both sprout_persona and chara chunks. - let sprout = serde_json::json!({"version": 1, "displayName": "Sprout Name", "systemPrompt": "Sprout prompt"}); + fn parse_png_chara_ignored_when_buzz_present() { + // Build a PNG with both buzz_persona_pkg and chara chunks. + let buzz = serde_json::json!({"version": 1, "displayName": "Buzz Name", "systemPrompt": "Buzz prompt"}); let chara = serde_json::json!({ "spec": "chara_card_v2", "spec_version": "2.0", "data": {"name": "Chara Name", "system_prompt": "Chara prompt", "description": ""} }); - let sprout_b64 = STANDARD.encode(sprout.to_string().as_bytes()); + let buzz_b64 = STANDARD.encode(buzz.to_string().as_bytes()); let chara_b64 = STANDARD.encode(chara.to_string().as_bytes()); let mut buf = Vec::new(); @@ -695,7 +695,7 @@ mod tests { let mut enc = Encoder::new(Cursor::new(&mut buf), 1, 1); enc.set_color(ColorType::Rgba); enc.set_depth(BitDepth::Eight); - enc.add_text_chunk("sprout_persona".to_string(), sprout_b64) + enc.add_text_chunk("buzz_persona_pkg".to_string(), buzz_b64) .unwrap(); enc.add_text_chunk("chara".to_string(), chara_b64).unwrap(); let mut w = enc.write_header().unwrap(); @@ -703,8 +703,8 @@ mod tests { } let result = parse_png_persona(&buf).unwrap(); - assert_eq!(result.display_name, "Sprout Name"); - assert_eq!(result.system_prompt, "Sprout prompt"); + assert_eq!(result.display_name, "Buzz Name"); + assert_eq!(result.system_prompt, "Buzz prompt"); } #[test] @@ -767,7 +767,7 @@ mod tests { #[test] fn parse_png_duplicate_chunks() { - // Two sprout_persona chunks — should use the first and ignore the second. + // Two buzz_persona_pkg chunks — should use the first and ignore the second. let payload1 = serde_json::json!({"version": 1, "displayName": "First", "systemPrompt": "Prompt 1"}); let payload2 = @@ -780,9 +780,9 @@ mod tests { let mut enc = Encoder::new(Cursor::new(&mut buf), 1, 1); enc.set_color(ColorType::Rgba); enc.set_depth(BitDepth::Eight); - enc.add_text_chunk("sprout_persona".to_string(), b64_1) + enc.add_text_chunk("buzz_persona_pkg".to_string(), b64_1) .unwrap(); - enc.add_text_chunk("sprout_persona".to_string(), b64_2) + enc.add_text_chunk("buzz_persona_pkg".to_string(), b64_2) .unwrap(); let mut w = enc.write_header().unwrap(); w.write_image_data(&[0, 0, 0, 255]).unwrap(); diff --git a/desktop/src-tauri/src/managed_agents/personas.rs b/desktop/src-tauri/src/managed_agents/personas.rs index 35eb575f5..06eb95774 100644 --- a/desktop/src-tauri/src/managed_agents/personas.rs +++ b/desktop/src-tauri/src/managed_agents/personas.rs @@ -178,7 +178,7 @@ RIGHT — kickoff with no `@`, no ping: Then later, when actually ready to assign: -> "@scout — PHASE: PLAN REVIEW + RESEARCH. Worktree: /tmp/sprout-auth. Plan: . Deliverable: verdict + research brief." +> "@scout — PHASE: PLAN REVIEW + RESEARCH. Worktree: /tmp/buzz-auth. Plan: . Deliverable: verdict + research brief." # Size The Task First @@ -452,7 +452,7 @@ You are read-only, but you still resolve questions yourself before pinging Kit: # Standalone Mode -If you're invoked outside a Sprout team channel (or by an agent that isn't Kit), apply the same protocols. Default to FULL REVIEW for completed work or PLAN REVIEW + RESEARCH for plans. Report to whoever invoked you. +If you're invoked outside a Buzz team channel (or by an agent that isn't Kit), apply the same protocols. Default to FULL REVIEW for completed work or PLAN REVIEW + RESEARCH for plans. Report to whoever invoked you. Your name is Scout. You are friendly and helpful. You are understated, but have a sense of humor."#, model: None, @@ -616,7 +616,7 @@ fn migrate_retired_personas(stored: &mut [PersonaRecord], now: &str) -> bool { if needs_suffix || record.is_active { let was_unmodified = record.system_prompt == *original_prompt; eprintln!( - "sprout-desktop: persona-migration: retiring {} persona '{}' → '{} (retired)'", + "buzz-desktop: persona-migration: retiring {} persona '{}' → '{} (retired)'", if was_unmodified { "unmodified" } else { diff --git a/desktop/src-tauri/src/managed_agents/relay_mesh.rs b/desktop/src-tauri/src/managed_agents/relay_mesh.rs index ec25981db..808546aa4 100644 --- a/desktop/src-tauri/src/managed_agents/relay_mesh.rs +++ b/desktop/src-tauri/src/managed_agents/relay_mesh.rs @@ -2,7 +2,7 @@ use super::ManagedAgentRecord; pub use super::RelayMeshConfig; pub const RELAY_MESH_API_BASE_URL: &str = "http://127.0.0.1:9337/v1"; -pub const RELAY_MESH_API_KEY_PLACEHOLDER: &str = "sprout-mesh-local"; +pub const RELAY_MESH_API_KEY_PLACEHOLDER: &str = "buzz-mesh-local"; /// Resolve a record's relay-mesh config, typed field first. /// @@ -19,7 +19,7 @@ pub fn relay_mesh_config(record: &ManagedAgentRecord) -> Option } /// Returns the relay-mesh model id for agents whose provider env points at the -/// local mesh client endpoint created by Sprout's relay-mesh preset. +/// local mesh client endpoint created by Buzz's relay-mesh preset. /// /// Prefer [`relay_mesh_config`]; this remains as a convenience for call sites /// that only need the model id. @@ -69,7 +69,7 @@ mod tests { auth_tag: Some("tag".into()), relay_url: "ws://localhost:3000".into(), avatar_url: None, - acp_command: "sprout-acp".into(), + acp_command: "buzz-acp".into(), agent_command: "goose".into(), agent_args: vec![], mcp_command: String::new(), diff --git a/desktop/src-tauri/src/managed_agents/restore.rs b/desktop/src-tauri/src/managed_agents/restore.rs index aa9b3a03d..2bad0fd36 100644 --- a/desktop/src-tauri/src/managed_agents/restore.rs +++ b/desktop/src-tauri/src/managed_agents/restore.rs @@ -61,7 +61,7 @@ pub async fn restore_managed_agents_on_launch( // process group whose parent harness exited). super::sweep_system_agent_processes(&super::current_instance_id(app), &tracked_pids); - // Dead-instance reaping: find agents belonging to Sprout instances + // Dead-instance reaping: find agents belonging to Buzz instances // whose desktop process is no longer running and reap them. super::reap_dead_instance_agents(&super::current_instance_id(app), &tracked_pids); @@ -232,7 +232,7 @@ pub async fn restore_managed_agents_on_launch( crate::commands::reconcile_agent_profile(&state, &reconcile_app, &pubkey, &data) .await { - eprintln!("sprout-desktop: profile reconciliation failed for agent {pubkey}: {e}"); + eprintln!("buzz-desktop: profile reconciliation failed for agent {pubkey}: {e}"); } }); } diff --git a/desktop/src-tauri/src/managed_agents/runtime.rs b/desktop/src-tauri/src/managed_agents/runtime.rs index a42ed8627..6e140c9e2 100644 --- a/desktop/src-tauri/src/managed_agents/runtime.rs +++ b/desktop/src-tauri/src/managed_agents/runtime.rs @@ -13,16 +13,16 @@ use crate::{ type RespondToEnv = (Vec<(&'static str, String)>, Vec<&'static str>); -/// Binary name fragments for all known agent/harness processes that Sprout +/// Binary name fragments for all known agent/harness processes that Buzz /// may spawn. Used by `process_belongs_to_us()` and the orphan sweep to /// identify processes we should clean up. Both hyphenated and underscored /// variants are listed because macOS `proc_name()` and Linux `/proc/comm` /// may report either form depending on how the binary was built. pub(crate) const KNOWN_AGENT_BINARIES: &[&str] = &[ - "sprout-acp", - "sprout_acp", - "sprout-agent", - "sprout_agent", + "buzz-acp", + "buzz_acp", + "buzz-agent", + "buzz_agent", "claude-agent-acp", "claude_agent_acp", "claude-code-acp", @@ -30,11 +30,11 @@ pub(crate) const KNOWN_AGENT_BINARIES: &[&str] = &[ "codex-acp", "codex_acp", "goose", - // sprout-dev-mcp's multicall personalities (rg, tree, sprout, + // buzz-dev-mcp's multicall personalities (rg, tree, buzz, // git-credential-nostr, git-sign-nostr) are short-lived per-tool-call // invocations — not listed here. - "sprout-dev-mcp", - "sprout_dev_mcp", + "buzz-dev-mcp", + "buzz_dev_mcp", ]; /// Check if a process name matches any of our known agent binaries. @@ -118,11 +118,11 @@ pub(crate) fn process_belongs_to_us(_pid: u32) -> bool { /// The value stamped into the `BUZZ_MANAGED_AGENT` env var of every agent we /// spawn, identifying *which* desktop instance owns it. We use the app's bundle -/// identifier (`xyz.block.sprout.app` for release, `xyz.block.sprout.app.dev` +/// identifier (`xyz.block.buzz.app` for release, `xyz.block.buzz.app.dev` /// for `just dev`) because it is stable across restarts — a relaunched dev /// instance still recognizes its own previously-spawned agents as reclaimable, /// while never matching another instance's (e.g. a dev build never reaps a DMG -/// build's agents, and vice versa). This is what lets two Sprouts coexist on +/// build's agents, and vice versa). This is what lets two Buzzs coexist on /// one machine without one's cleanup nuking the other's agents. pub(crate) fn current_instance_id(app: &AppHandle) -> String { app.config().identifier.clone() @@ -131,17 +131,17 @@ pub(crate) fn current_instance_id(app: &AppHandle) -> String { /// Build the full `BUZZ_MANAGED_AGENT=` env entry we match /// against when scanning processes. Kept here so the spawn stamp and the sweep /// matcher can never drift apart. -fn sprout_marker_entry(instance_id: &str) -> Vec { +fn buzz_marker_entry(instance_id: &str) -> Vec { format!("BUZZ_MANAGED_AGENT={instance_id}").into_bytes() } /// Check if a running process is one of *our* managed agents: it must carry /// `BUZZ_MANAGED_AGENT=` in its environment, where `instance_id` /// is this desktop instance's id. A process stamped with a *different* instance -/// id belongs to another live Sprout app and must never be reaped here. +/// id belongs to another live Buzz app and must never be reaped here. #[cfg(target_os = "macos")] -fn process_has_sprout_marker(pid: u32, instance_id: &str) -> bool { - let marker = sprout_marker_entry(instance_id); +fn process_has_buzz_marker(pid: u32, instance_id: &str) -> bool { + let marker = buzz_marker_entry(instance_id); let mut mib: [libc::c_int; 3] = [libc::CTL_KERN, libc::KERN_PROCARGS2, pid as libc::c_int]; let mut buf_size: libc::size_t = 0; @@ -215,8 +215,8 @@ fn process_has_sprout_marker(pid: u32, instance_id: &str) -> bool { } #[cfg(all(unix, not(target_os = "macos")))] -fn process_has_sprout_marker(pid: u32, instance_id: &str) -> bool { - let marker = sprout_marker_entry(instance_id); +fn process_has_buzz_marker(pid: u32, instance_id: &str) -> bool { + let marker = buzz_marker_entry(instance_id); let Ok(data) = std::fs::read(format!("/proc/{pid}/environ")) else { return false; }; @@ -224,7 +224,7 @@ fn process_has_sprout_marker(pid: u32, instance_id: &str) -> bool { } #[cfg(not(unix))] -fn process_has_sprout_marker(_pid: u32, _instance_id: &str) -> bool { +fn process_has_buzz_marker(_pid: u32, _instance_id: &str) -> bool { false } @@ -402,7 +402,7 @@ const PROC_PIDTBSDINFO: libc::c_int = 3; /// (`instance_id`) that isn't in `skip_pids`. This catches orphans that escaped /// PID-file-based cleanup (e.g. agent workers spawned with their own process /// group whose parent harness already exited and had its PID file removed), -/// while leaving another live Sprout instance's agents untouched. +/// while leaving another live Buzz instance's agents untouched. #[cfg(target_os = "macos")] pub(crate) fn sweep_system_agent_processes(instance_id: &str, skip_pids: &[u32]) { let my_uid = unsafe { libc::getuid() }; @@ -470,7 +470,7 @@ pub(crate) fn sweep_system_agent_processes(instance_id: &str, skip_pids: &[u32]) if skip_pids.contains(&info.pbi_ppid) { continue; } - if !process_has_sprout_marker(upid, instance_id) { + if !process_has_buzz_marker(upid, instance_id) { continue; } orphans.push(pid); @@ -478,7 +478,7 @@ pub(crate) fn sweep_system_agent_processes(instance_id: &str, skip_pids: &[u32]) if !orphans.is_empty() { eprintln!( - "sprout-desktop: system sweep found {} orphaned agent process(es), cleaning up", + "buzz-desktop: system sweep found {} orphaned agent process(es), cleaning up", orphans.len() ); sigterm_then_sigkill(&orphans); @@ -529,7 +529,7 @@ pub(crate) fn sweep_system_agent_processes(instance_id: &str, skip_pids: &[u32]) if meta.uid() != my_uid { continue; } - if !process_belongs_to_us(upid) || !process_has_sprout_marker(upid, instance_id) { + if !process_belongs_to_us(upid) || !process_has_buzz_marker(upid, instance_id) { continue; } // Live child of a tracked harness — not an orphan. If /proc//stat @@ -547,7 +547,7 @@ pub(crate) fn sweep_system_agent_processes(instance_id: &str, skip_pids: &[u32]) if !orphans.is_empty() { eprintln!( - "sprout-desktop: system sweep found {} orphaned agent process(es), cleaning up", + "buzz-desktop: system sweep found {} orphaned agent process(es), cleaning up", orphans.len() ); sigterm_then_sigkill(&orphans); @@ -577,7 +577,7 @@ pub(crate) fn sweep_system_agent_processes_with_grace( .collect(); if !confirmed.is_empty() { eprintln!( - "sprout-desktop: periodic sweep confirmed {} orphaned agent process(es), cleaning up", + "buzz-desktop: periodic sweep confirmed {} orphaned agent process(es), cleaning up", confirmed.len() ); sigterm_then_sigkill(&confirmed); @@ -661,7 +661,7 @@ pub(crate) fn collect_same_instance_orphans( if skip_pids.contains(&info.pbi_ppid) { continue; } - if process_has_sprout_marker(upid, instance_id) { + if process_has_buzz_marker(upid, instance_id) { orphans.insert(upid); } } @@ -702,7 +702,7 @@ pub(crate) fn collect_same_instance_orphans( if meta.uid() != my_uid { continue; } - if !process_belongs_to_us(upid) || !process_has_sprout_marker(upid, instance_id) { + if !process_belongs_to_us(upid) || !process_has_buzz_marker(upid, instance_id) { continue; } // Live child of a tracked harness — not an orphan. If /proc//stat @@ -727,22 +727,21 @@ pub(crate) fn collect_same_instance_orphans( std::collections::HashSet::new() } -/// Binary names for the Sprout desktop/Tauri process. Used by dead-instance -/// detection to confirm the owning desktop is still alive. The release .app -/// bundle reports as "Sprout"; `tauri dev` reports as "sprout-desktop". -const DESKTOP_BINARY_NAMES: &[&str] = &["Sprout", "sprout-desktop", "sprout_desktop"]; +/// Binary names for the Buzz desktop/Tauri process. Used by dead-instance +/// detection to confirm the owning desktop is still alive. +const DESKTOP_BINARY_NAMES: &[&str] = &["Buzz", "buzz-desktop", "buzz_desktop"]; -/// Check if a process name matches a known Sprout desktop binary. +/// Check if a process name matches a known Buzz desktop binary. fn is_desktop_binary(name: &str) -> bool { DESKTOP_BINARY_NAMES.contains(&name) } /// Check whether `buf` contains `id` as a complete identifier — not as a /// prefix of a longer dotted name. The identifier appears in the Tauri config -/// JSON as `"identifier":"xyz.block.sprout.app.dev"` and in environment entries +/// JSON as `"identifier":"xyz.block.buzz.app.dev"` and in environment entries /// as `KEY=...app.dev\0`, so a valid match is followed by a non-identifier byte /// (not `[A-Za-z0-9._-]`) or sits at the end of the buffer. This prevents -/// `xyz.block.sprout.app` from matching inside `xyz.block.sprout.app.dev`. +/// `xyz.block.buzz.app` from matching inside `xyz.block.buzz.app.dev`. fn buffer_contains_identifier(buf: &[u8], id: &[u8]) -> bool { if id.is_empty() { return false; @@ -765,7 +764,7 @@ fn buffer_contains_identifier(buf: &[u8], id: &[u8]) -> bool { /// Extract the `BUZZ_MANAGED_AGENT` value from a process's environment. /// Returns `None` if the process doesn't have the marker or can't be read. #[cfg(target_os = "macos")] -fn extract_sprout_marker_value(pid: u32) -> Option { +fn extract_buzz_marker_value(pid: u32) -> Option { let prefix = b"BUZZ_MANAGED_AGENT="; let mut mib: [libc::c_int; 3] = [libc::CTL_KERN, libc::KERN_PROCARGS2, pid as libc::c_int]; @@ -842,7 +841,7 @@ fn extract_sprout_marker_value(pid: u32) -> Option { } #[cfg(all(unix, not(target_os = "macos")))] -fn extract_sprout_marker_value(pid: u32) -> Option { +fn extract_buzz_marker_value(pid: u32) -> Option { let prefix = b"BUZZ_MANAGED_AGENT="; let data = std::fs::read(format!("/proc/{pid}/environ")).ok()?; for entry in data.split(|&b| b == 0) { @@ -854,12 +853,12 @@ fn extract_sprout_marker_value(pid: u32) -> Option { } #[cfg(not(unix))] -fn extract_sprout_marker_value(_pid: u32) -> Option { +fn extract_buzz_marker_value(_pid: u32) -> Option { None } -/// Check if a Sprout desktop process is still alive for the given instance ID. -/// Scans all user-owned processes named "Sprout" or "sprout-desktop" and checks +/// Check if a Buzz desktop process is still alive for the given instance ID. +/// Scans all user-owned processes named "Buzz" or "buzz-desktop" and checks /// whether any has the identifier in its command-line args (KERN_PROCARGS2 buffer /// includes both argv and environ — the `--config` JSON from `tauri dev` contains /// the identifier string). @@ -1020,11 +1019,11 @@ fn desktop_is_alive_for_instance(_instance_id: &str) -> bool { false } -/// Reap agent processes belonging to dead Sprout desktop instances. +/// Reap agent processes belonging to dead Buzz desktop instances. /// /// Scans all user processes for `BUZZ_MANAGED_AGENT=*`, groups them by /// instance ID, and for each foreign instance (≠ `our_instance_id`) checks -/// whether a Sprout desktop binary is still alive for that instance. If not, +/// whether a Buzz desktop binary is still alive for that instance. If not, /// all agents from that dead instance are reaped. #[cfg(target_os = "macos")] pub(crate) fn reap_dead_instance_agents(our_instance_id: &str, skip_pids: &[u32]) { @@ -1087,7 +1086,7 @@ pub(crate) fn reap_dead_instance_agents(our_instance_id: &str, skip_pids: &[u32] continue; } // Extract the instance ID from this agent's env. - let Some(agent_instance_id) = extract_sprout_marker_value(upid) else { + let Some(agent_instance_id) = extract_buzz_marker_value(upid) else { continue; }; // Skip agents belonging to our own instance (handled by sweep_system_agent_processes). @@ -1106,7 +1105,7 @@ pub(crate) fn reap_dead_instance_agents(our_instance_id: &str, skip_pids: &[u32] continue; } eprintln!( - "sprout-desktop: reaping {} orphaned agent(s) from dead instance '{instance_id}'", + "buzz-desktop: reaping {} orphaned agent(s) from dead instance '{instance_id}'", agent_pids.len() ); sigterm_then_sigkill(agent_pids); @@ -1147,7 +1146,7 @@ pub(crate) fn reap_dead_instance_agents(our_instance_id: &str, skip_pids: &[u32] if !process_belongs_to_us(upid) { continue; } - let Some(agent_instance_id) = extract_sprout_marker_value(upid) else { + let Some(agent_instance_id) = extract_buzz_marker_value(upid) else { continue; }; if agent_instance_id == our_instance_id { @@ -1164,7 +1163,7 @@ pub(crate) fn reap_dead_instance_agents(our_instance_id: &str, skip_pids: &[u32] continue; } eprintln!( - "sprout-desktop: reaping {} orphaned agent(s) from dead instance '{instance_id}'", + "buzz-desktop: reaping {} orphaned agent(s) from dead instance '{instance_id}'", agent_pids.len() ); sigterm_then_sigkill(agent_pids); @@ -1488,7 +1487,7 @@ pub fn spawn_agent_child( Some(path) => Some(path), None => { eprintln!( - "sprout-desktop: mcp_command {:?} not found, skipping", + "buzz-desktop: mcp_command {:?} not found, skipping", record.mcp_command ); None @@ -1501,8 +1500,8 @@ pub fn spawn_agent_child( .unwrap_or_else(|| record.agent_command.clone()); // Augment PATH for DMG launches so child processes can find: - // - sprout CLI via ~/.local/bin symlink - // - bundled sidecars (sprout, sprout-acp, etc.) via exe parent (Contents/MacOS/) + // - bundled CLI via ~/.local/bin symlink + // - bundled sidecars (buzz, buzz-acp, etc.) via exe parent (Contents/MacOS/) // - runtimes (node, python, etc.) via login shell PATH let augmented_path = { let mut parts: Vec = Vec::new(); @@ -1548,14 +1547,14 @@ pub fn spawn_agent_child( } } // Enable MCP hook tools (_Stop, _PostCompact) for agents that need them. - // Uses "*" because build_mcp_servers() hard-codes the server name to "sprout-mcp". + // Uses "*" because build_mcp_servers() hard-codes the server name to "buzz-mcp". let runtime_meta = known_acp_runtime(&record.agent_command); if runtime_meta.is_some_and(|r| r.mcp_hooks) { command.env("MCP_HOOK_SERVERS", "*"); } // Only emit BUZZ_ACP_IDLE_TIMEOUT when the user has explicitly set an - // override. When unset, the sprout-acp harness applies its own default - // (see `DEFAULT_IDLE_TIMEOUT_SECS` in crates/sprout-acp/src/config.rs), + // override. When unset, the buzz-acp harness applies its own default + // (see `DEFAULT_IDLE_TIMEOUT_SECS` in crates/buzz-acp/src/config.rs), // which is the single source of truth. The previously-emitted // `BUZZ_ACP_TURN_TIMEOUT` is deprecated upstream and was pinning every // agent to the desktop's stale default (320s), bypassing harness bumps. @@ -1647,9 +1646,9 @@ pub fn spawn_agent_child( command.env("BUZZ_ACP_RELAY_OBSERVER", "true"); - // ── Git credential helper for Sprout relay ────────────────────────── + // ── Git credential helper for Buzz relay ────────────────────────── // - // Agents need to clone/push repos hosted on the Sprout relay's git + // Agents need to clone/push repos hosted on the Buzz relay's git // server, which authenticates via NIP-98. The `git-credential-nostr` // binary signs auth events using the agent's nostr key. // @@ -1676,13 +1675,13 @@ pub fn spawn_agent_child( command.env("GIT_CONFIG_VALUE_1", "true"); } else { eprintln!( - "sprout-desktop: git-credential-nostr not found — agent {} will not have automatic Sprout git auth", + "buzz-desktop: git-credential-nostr not found — agent {} will not have automatic Buzz git auth", record.name, ); } - // Baked-in Databricks defaults for internal builds (sprout-releases sets - // SPROUT_BUILD_DATABRICKS_* at compile time; OSS builds bake nothing). + // Baked-in Databricks defaults for internal builds (buzz-releases sets + // BUZZ_BUILD_DATABRICKS_* at compile time; OSS builds bake nothing). // Written BEFORE user env_vars so a GUI/persona override still wins. for (key, value) in build_databricks_defaults() { command.env(key, value); @@ -1691,11 +1690,11 @@ pub fn spawn_agent_child( // ── User env vars: persona first, then per-agent (last wins) ──────── // // Precedence: desktop parent env < persona env_vars < agent env_vars. - // These writes go LAST so user-provided values win over every Sprout-set + // These writes go LAST so user-provided values win over every Buzz-set // env above — EXCEPT reserved keys (BUZZ_PRIVATE_KEY, NOSTR_PRIVATE_KEY, // BUZZ_AUTH_TAG, BUZZ_API_TOKEN, BUZZ_ACP_PRIVATE_KEY, // BUZZ_ACP_API_TOKEN), which `merged_user_env` strips. Those carry - // Sprout's identity and must never be GUI-overridable. + // Buzz's identity and must never be GUI-overridable. // Fail closed on persona-lookup errors: persona env_vars carry API // credentials, so silently substituting an empty map would spawn an // unauthenticated agent and surface as a confusing downstream auth error. @@ -1704,11 +1703,11 @@ pub fn spawn_agent_child( command.env(key, value); } - // Mark as Sprout-managed *and* which desktop instance owns us, so the + // Mark as Buzz-managed *and* which desktop instance owns us, so the // system-wide orphan sweep only reaps this instance's own agents and never - // another live Sprout's (e.g. a `just dev` build won't kill a DMG build's - // agents). Propagates automatically through the full tree (sprout-acp → - // goose → MCP servers) because neither sprout-acp nor goose calls + // another live Buzz's (e.g. a `just dev` build won't kill a DMG build's + // agents). Propagates automatically through the full tree (buzz-acp → + // goose → MCP servers) because neither buzz-acp nor goose calls // env_clear(). command.env("BUZZ_MANAGED_AGENT", current_instance_id(app)); @@ -1735,22 +1734,22 @@ pub fn spawn_agent_child( fn child_rust_log_filter() -> String { match std::env::var("RUST_LOG") { - Ok(existing) if existing.contains("sprout_acp") => existing, - Ok(existing) if !existing.trim().is_empty() => format!("{existing},sprout_acp=info"), - _ => "sprout_acp=info".to_string(), + Ok(existing) if existing.contains("buzz_acp") => existing, + Ok(existing) if !existing.trim().is_empty() => format!("{existing},buzz_acp=info"), + _ => "buzz_acp=info".to_string(), } } /// Databricks host/model baked in at compile time for internal builds. Empty -/// in OSS builds, where the `SPROUT_BUILD_DATABRICKS_*` env is unset. +/// in OSS builds, where the `BUZZ_BUILD_DATABRICKS_*` env is unset. fn build_databricks_defaults() -> Vec<(&'static str, &'static str)> { let mut defaults = Vec::new(); - if let Some(host) = option_env!("SPROUT_DESKTOP_BUILD_DATABRICKS_HOST") { + if let Some(host) = option_env!("BUZZ_DESKTOP_BUILD_DATABRICKS_HOST") { if !host.is_empty() { defaults.push(("DATABRICKS_HOST", host)); } } - if let Some(model) = option_env!("SPROUT_DESKTOP_BUILD_DATABRICKS_MODEL") { + if let Some(model) = option_env!("BUZZ_DESKTOP_BUILD_DATABRICKS_MODEL") { if !model.is_empty() { defaults.push(("DATABRICKS_MODEL", model)); } diff --git a/desktop/src-tauri/src/managed_agents/runtime/tests.rs b/desktop/src-tauri/src/managed_agents/runtime/tests.rs index 580ae59a2..f8a922da5 100644 --- a/desktop/src-tauri/src/managed_agents/runtime/tests.rs +++ b/desktop/src-tauri/src/managed_agents/runtime/tests.rs @@ -5,42 +5,42 @@ use crate::managed_agents::known_acp_runtime; #[test] fn identifier_prefix_does_not_match_longer_id() { // DMG identifier should NOT match inside a dev desktop's config JSON. - let buf = br#""identifier":"xyz.block.sprout.app.dev""#; - let id = b"xyz.block.sprout.app"; + let buf = br#""identifier":"xyz.block.buzz.app.dev""#; + let id = b"xyz.block.buzz.app"; assert!(!super::buffer_contains_identifier(buf, id)); } #[test] fn identifier_prefix_does_not_match_worktree_slug() { // Main dev identifier should NOT match inside a worktree desktop's buffer. - let buf = br#""identifier":"xyz.block.sprout.app.dev.my-branch""#; - let id = b"xyz.block.sprout.app.dev"; + let buf = br#""identifier":"xyz.block.buzz.app.dev.my-branch""#; + let id = b"xyz.block.buzz.app.dev"; assert!(!super::buffer_contains_identifier(buf, id)); } #[test] fn identifier_exact_match_with_quote_boundary() { // Exact match followed by closing quote — should match. - let buf = br#""identifier":"xyz.block.sprout.app.dev""#; - let id = b"xyz.block.sprout.app.dev"; + let buf = br#""identifier":"xyz.block.buzz.app.dev""#; + let id = b"xyz.block.buzz.app.dev"; assert!(super::buffer_contains_identifier(buf, id)); } #[test] fn identifier_match_with_null_boundary() { // In KERN_PROCARGS2, entries are null-delimited. - let mut buf = b"BUZZ_MANAGED_AGENT=xyz.block.sprout.app.dev".to_vec(); + let mut buf = b"BUZZ_MANAGED_AGENT=xyz.block.buzz.app.dev".to_vec(); buf.push(0); buf.extend_from_slice(b"OTHER_VAR=value"); - let id = b"xyz.block.sprout.app.dev"; + let id = b"xyz.block.buzz.app.dev"; assert!(super::buffer_contains_identifier(&buf, id)); } #[test] fn identifier_exact_match_at_end_of_buffer() { // Exact match with end-of-buffer as the boundary — Thufir's case 1. - let buf = b"xyz.block.sprout.app.dev"; - let id = b"xyz.block.sprout.app.dev"; + let buf = b"xyz.block.buzz.app.dev"; + let id = b"xyz.block.buzz.app.dev"; assert!(super::buffer_contains_identifier(buf, id)); } @@ -48,10 +48,10 @@ fn identifier_exact_match_at_end_of_buffer() { fn longer_id_matches_when_short_prefix_also_present() { // Searching for the longer ID finds it even when a shorter prefix token // appears earlier — Thufir's "longer-of-prefix must match" case. - let mut buf = b"xyz.block.sprout.app".to_vec(); + let mut buf = b"xyz.block.buzz.app".to_vec(); buf.push(0); - buf.extend_from_slice(br#""identifier":"xyz.block.sprout.app.dev""#); - let id = b"xyz.block.sprout.app.dev"; + buf.extend_from_slice(br#""identifier":"xyz.block.buzz.app.dev""#); + let id = b"xyz.block.buzz.app.dev"; assert!(super::buffer_contains_identifier(&buf, id)); } @@ -66,36 +66,36 @@ fn identifier_empty_returns_false() { #[test] fn marker_entry_is_namespaced_by_instance_id() { // The spawn stamp and the sweep matcher must produce identical bytes; - // both go through sprout_marker_entry, so this pins the on-the-wire + // both go through buzz_marker_entry, so this pins the on-the-wire // format and guards against a dev build (`...app.dev`) matching a // release build's (`...app`) agents. assert_eq!( - super::sprout_marker_entry("xyz.block.sprout.app"), - b"BUZZ_MANAGED_AGENT=xyz.block.sprout.app".to_vec() + super::buzz_marker_entry("xyz.block.buzz.app"), + b"BUZZ_MANAGED_AGENT=xyz.block.buzz.app".to_vec() ); assert_ne!( - super::sprout_marker_entry("xyz.block.sprout.app"), - super::sprout_marker_entry("xyz.block.sprout.app.dev") + super::buzz_marker_entry("xyz.block.buzz.app"), + super::buzz_marker_entry("xyz.block.buzz.app.dev") ); } #[test] -fn sprout_agent_has_mcp_hooks() { - let p = known_acp_runtime("sprout-agent").expect("should resolve"); +fn buzz_agent_has_mcp_hooks() { + let p = known_acp_runtime("buzz-agent").expect("should resolve"); assert!(p.mcp_hooks); - assert_eq!(p.mcp_command, Some("sprout-dev-mcp")); + assert_eq!(p.mcp_command, Some("buzz-dev-mcp")); } #[test] fn databricks_defaults_empty_in_oss_build() { - // OSS (and normal test) builds set neither SPROUT_BUILD_DATABRICKS_*, + // OSS (and normal test) builds set neither BUZZ_BUILD_DATABRICKS_*, // so nothing is baked in and no DATABRICKS_* is injected on spawn. assert!(super::build_databricks_defaults().is_empty()); } #[test] -fn sprout_agent_resolved_via_path() { - assert!(known_acp_runtime("/usr/local/bin/sprout-agent").is_some_and(|p| p.mcp_hooks)); +fn buzz_agent_resolved_via_path() { + assert!(known_acp_runtime("/usr/local/bin/buzz-agent").is_some_and(|p| p.mcp_hooks)); } #[test] @@ -130,7 +130,7 @@ fn fixture( auth_tag, relay_url: "ws://localhost:3000".into(), avatar_url: None, - acp_command: "sprout-acp".into(), + acp_command: "buzz-acp".into(), agent_command: "goose".into(), agent_args: vec![], mcp_command: String::new(), @@ -374,7 +374,7 @@ fn runtime_metadata_env_vars_skips_provider_when_locked() { #[test] fn runtime_metadata_env_vars_injects_model_even_with_acp_model_switching() { - // sprout-agent has supports_acp_model_switching=true but we still inject + // buzz-agent has supports_acp_model_switching=true but we still inject // the model env var because ACP model switching is post-bootstrap let vars = runtime_metadata_env_vars( Some("BUZZ_AGENT_MODEL"), diff --git a/desktop/src-tauri/src/managed_agents/screenshot_skill.md b/desktop/src-tauri/src/managed_agents/screenshot_skill.md index 09b797bb2..b822d76a2 100644 --- a/desktop/src-tauri/src/managed_agents/screenshot_skill.md +++ b/desktop/src-tauri/src/managed_agents/screenshot_skill.md @@ -9,7 +9,7 @@ version: 1 ## CRITICAL: How to Host Screenshots for PRs -**NEVER use `sprout upload`, the relay media endpoint, or any third-party image +**NEVER use `buzz upload`, the relay media endpoint, or any third-party image host (imgur, imgbb, etc.) for PR screenshots.** Relay media URLs fail through GitHub's camo proxy (`Non-Image content-type returned`). External hosts are unreliable and may expose content. diff --git a/desktop/src-tauri/src/managed_agents/storage.rs b/desktop/src-tauri/src/managed_agents/storage.rs index 5540bddc7..2bd975ca0 100644 --- a/desktop/src-tauri/src/managed_agents/storage.rs +++ b/desktop/src-tauri/src/managed_agents/storage.rs @@ -187,7 +187,7 @@ pub fn read_log_tail(path: &Path, max_lines: usize) -> Result { // Strip ANSI escapes here (not in the harness) so the desktop log view // renders cleanly while terminals and other tools still get the colors - // sprout-acp emits. + // buzz-acp emits. let cleaned = strip_ansi_escapes::strip_str(String::from_utf8_lossy(&buf)); let lines: Vec<&str> = cleaned.lines().collect(); let start = lines.len().saturating_sub(max_lines); @@ -249,10 +249,10 @@ mod tests { #[test] fn strips_ansi_from_typical_tracing_line() { - let input = "\x1b[2m2026-05-27T15:16:32\x1b[0m \x1b[32m INFO\x1b[0m \x1b[2msprout_acp\x1b[0m\x1b[2m:\x1b[0m starting"; + let input = "\x1b[2m2026-05-27T15:16:32\x1b[0m \x1b[32m INFO\x1b[0m \x1b[2mbuzz_acp\x1b[0m\x1b[2m:\x1b[0m starting"; assert_eq!( strip_ansi_escapes::strip_str(input), - "2026-05-27T15:16:32 INFO sprout_acp: starting" + "2026-05-27T15:16:32 INFO buzz_acp: starting" ); } } diff --git a/desktop/src-tauri/src/managed_agents/team_repair.rs b/desktop/src-tauri/src/managed_agents/team_repair.rs index 6e33f433c..e33d36ae2 100644 --- a/desktop/src-tauri/src/managed_agents/team_repair.rs +++ b/desktop/src-tauri/src/managed_agents/team_repair.rs @@ -220,7 +220,7 @@ pub fn sync_team_personas(app: &AppHandle) -> Result<(), String> { for team in &teams { if team.source_dir.as_ref().is_some_and(|d| d.exists()) { if let Err(e) = sync_team_from_dir(app, &team.id) { - eprintln!("sprout-desktop: sync team {}: {e}", team.id); + eprintln!("buzz-desktop: sync team {}: {e}", team.id); } } } diff --git a/desktop/src-tauri/src/managed_agents/teams.rs b/desktop/src-tauri/src/managed_agents/teams.rs index f0f18fbb3..e8e55c3aa 100644 --- a/desktop/src-tauri/src/managed_agents/teams.rs +++ b/desktop/src-tauri/src/managed_agents/teams.rs @@ -230,7 +230,7 @@ pub fn import_team_from_directory( use uuid::Uuid; // 1. Validate + resolve at source - let resolved = sprout_persona::resolve::resolve_pack(source_dir) + let resolved = buzz_persona_pkg::resolve::resolve_pack(source_dir) .map_err(|e| format!("team directory validation failed: {e}"))?; // 2. Sanitize team ID @@ -271,7 +271,7 @@ pub fn import_team_from_directory( } // 5. Re-validate the copy/symlink target (defense-in-depth) - let re_resolved = sprout_persona::resolve::resolve_pack(&dest).map_err(|e| { + let re_resolved = buzz_persona_pkg::resolve::resolve_pack(&dest).map_err(|e| { // Clean up on failure if use_symlink { let _ = fs::remove_file(&dest); @@ -445,7 +445,7 @@ pub fn sync_team_from_dir( } // Resolve current state of the directory - let resolved = sprout_persona::resolve::resolve_pack(source_dir) + let resolved = buzz_persona_pkg::resolve::resolve_pack(source_dir) .map_err(|e| format!("failed to resolve team directory: {e}"))?; let mut personas = super::load_personas(app)?; diff --git a/desktop/src-tauri/src/managed_agents/types.rs b/desktop/src-tauri/src/managed_agents/types.rs index e90e83e61..029081466 100644 --- a/desktop/src-tauri/src/managed_agents/types.rs +++ b/desktop/src-tauri/src/managed_agents/types.rs @@ -20,12 +20,12 @@ pub struct PersonaRecord { pub avatar_url: Option, pub system_prompt: String, /// Preferred ACP runtime ID (e.g., 'goose', 'claude', 'codex'). Determines which agent binary - /// Sprout spawns. When deploying from this persona, this runtime is pre-selected in the UI. + /// Buzz spawns. When deploying from this persona, this runtime is pre-selected in the UI. #[serde(default, skip_serializing_if = "Option::is_none")] pub runtime: Option, /// Opaque, harness-specific model identifier string. Format depends on the runtime and its LLM /// provider (e.g., 'goose-claude-4-6-opus' for Databricks, 'claude-opus-4-7' for Anthropic - /// direct). Sprout stores and passes through without interpretation. + /// direct). Buzz stores and passes through without interpretation. #[serde(default, skip_serializing_if = "Option::is_none")] pub model: Option, /// LLM inference provider (e.g., 'databricks', 'anthropic', 'openai'). Optional — when set, @@ -59,7 +59,7 @@ pub struct PersonaRecord { )] pub source_team_persona_slug: Option, /// Harness-level configuration passed to the agent subprocess as environment variables. - /// Opaque to Sprout — keys and values are runtime-specific. + /// Opaque to Buzz — keys and values are runtime-specific. /// /// Stored as a BTreeMap for deterministic on-disk ordering. #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] @@ -123,7 +123,7 @@ pub struct ManagedAgentRecord { /// creation by matching this ID against the fresh session/new response. #[serde(default)] pub model: Option, - /// Comma-separated toolset string forwarded as SPROUT_TOOLSETS to the MCP subprocess. + /// Comma-separated toolset string forwarded as BUZZ_TOOLSETS to the MCP subprocess. /// When None, the MCP server uses its own default ("default" toolset). #[serde(default)] pub mcp_toolsets: Option, @@ -163,7 +163,7 @@ pub struct ManagedAgentRecord { pub last_stopped_at: Option, pub last_exit_code: Option, pub last_error: Option, - /// Inbound author gate mode. Translates to `SPROUT_ACP_RESPOND_TO`. + /// Inbound author gate mode. Translates to `BUZZ_ACP_RESPOND_TO`. #[serde(default)] pub respond_to: RespondTo, /// Allowlist used when `respond_to == Allowlist`. Stored normalized @@ -172,7 +172,7 @@ pub struct ManagedAgentRecord { #[serde(default)] pub respond_to_allowlist: Vec, /// Typed marker for relay-mesh agents. `Some(_)` means this agent runs its - /// inference through Sprout's relay-mesh local endpoint; the `model_ref` is + /// inference through Buzz's relay-mesh local endpoint; the `model_ref` is /// the served model id to route to. `None` is a normal agent. /// /// This is the source of truth for "is this a mesh agent + which model" — @@ -544,9 +544,9 @@ pub struct MigrationReport { pub errors: Vec, } -pub const DEFAULT_ACP_COMMAND: &str = "sprout-acp"; +pub const DEFAULT_ACP_COMMAND: &str = "buzz-acp"; pub const DEFAULT_AGENT_COMMAND: &str = "goose"; -/// ~5 min (320s) — matches the CLI harness default (SPROUT_ACP_IDLE_TIMEOUT). +/// ~5 min (320s) — matches the CLI harness default (BUZZ_ACP_IDLE_TIMEOUT). pub const DEFAULT_AGENT_TURN_TIMEOUT_SECONDS: u64 = 320; /// 1 hour — absolute wall-clock safety cap per turn. pub const DEFAULT_AGENT_MAX_TURN_DURATION_SECONDS: u64 = 3600; @@ -566,10 +566,10 @@ fn default_record_active() -> bool { // ── Inbound author gate ────────────────────────────────────────────────────── // -// Mirrors `sprout-acp`'s `--respond-to` CLI flag and the related +// Mirrors `buzz-acp`'s `--respond-to` CLI flag and the related // `--respond-to-allowlist` option. Persisted per agent so the desktop can -// translate the user's choice into `SPROUT_ACP_RESPOND_TO` / -// `SPROUT_ACP_RESPOND_TO_ALLOWLIST` env vars at spawn time. +// translate the user's choice into `BUZZ_ACP_RESPOND_TO` / +// `BUZZ_ACP_RESPOND_TO_ALLOWLIST` env vars at spawn time. // // Wire format is kebab-case (`owner-only`, `allowlist`, `anyone`) to match // the harness CLI vocabulary and the strings the GUI emits. @@ -589,7 +589,7 @@ pub enum RespondTo { } impl RespondTo { - /// CLI/env wire string (matches `sprout-acp`'s `--respond-to`). + /// CLI/env wire string (matches `buzz-acp`'s `--respond-to`). pub fn as_str(self) -> &'static str { match self { Self::OwnerOnly => "owner-only", @@ -601,7 +601,7 @@ impl RespondTo { /// Validate and normalize a respond-to allowlist. /// -/// Rules mirror `sprout-acp/src/config.rs::validate_allowlist`: +/// Rules mirror `buzz-acp/src/config.rs::validate_allowlist`: /// - Each entry is exactly 64 hex chars (any case in, lowercase out). /// - Duplicates removed, insertion order preserved. /// @@ -662,7 +662,7 @@ mod tests { "name": "test-agent", "private_key_nsec": "nsec1fake", "relay_url": "wss://localhost:3000", - "acp_command": "sprout-acp", + "acp_command": "buzz-acp", "agent_command": "goose", "agent_args": [], "mcp_command": "", @@ -692,7 +692,7 @@ mod tests { "private_key_nsec": "nsec1fake", "auth_tag": "[\"auth\",\"deadbeef\",\"\",\"cafebabe\"]", "relay_url": "wss://localhost:3000", - "acp_command": "sprout-acp", + "acp_command": "buzz-acp", "agent_command": "goose", "agent_args": [], "mcp_command": "", @@ -770,7 +770,7 @@ mod tests { "name": "legacy-agent", "private_key_nsec": "nsec1fake", "relay_url": "wss://localhost:3000", - "acp_command": "sprout-acp", + "acp_command": "buzz-acp", "agent_command": "goose", "agent_args": [], "mcp_command": "", @@ -932,7 +932,7 @@ mod tests { "name": "test-agent", "private_key_nsec": "nsec1fake", "relay_url": "wss://localhost:3000", - "acp_command": "sprout-acp", + "acp_command": "buzz-acp", "agent_command": "goose", "agent_args": [], "mcp_command": "", diff --git a/desktop/src-tauri/src/media_proxy.rs b/desktop/src-tauri/src/media_proxy.rs index 7d9da399e..10a02d947 100644 --- a/desktop/src-tauri/src/media_proxy.rs +++ b/desktop/src-tauri/src/media_proxy.rs @@ -139,16 +139,16 @@ pub async fn spawn_media_proxy(http_client: reqwest::Client, app_handle: tauri:: axum::serve(listener, app).await.ok(); }); - eprintln!("sprout-desktop: media proxy listening on 127.0.0.1:{port}"); + eprintln!("buzz-desktop: media proxy listening on 127.0.0.1:{port}"); port } /// Proxy media requests through the Rust backend so they traverse the WARP tunnel. /// /// WKWebView's networking stack bypasses WARP, causing 403s from Cloudflare Access. -/// This handler routes `sprout-media://localhost/{path}` through reqwest, which +/// This handler routes `buzz-media://localhost/{path}` through reqwest, which /// runs in the Tauri process and goes through WARP. -pub async fn handle_sprout_media( +pub async fn handle_buzz_media( app: &tauri::AppHandle, request: &http::Request>, ) -> http::Response> { diff --git a/desktop/src-tauri/src/mesh_llm/coordinator.rs b/desktop/src-tauri/src/mesh_llm/coordinator.rs index 7b252e181..d8f01a96f 100644 --- a/desktop/src-tauri/src/mesh_llm/coordinator.rs +++ b/desktop/src-tauri/src/mesh_llm/coordinator.rs @@ -9,7 +9,7 @@ //! //! Two responsibilities: //! 1. `spawn_listener` — a long-lived task that holds an authenticated WS to -//! Sprout's relay (generalizing the proven `commands::pairing` NIP-42 +//! Buzz's relay (generalizing the proven `commands::pairing` NIP-42 //! machinery), subscribes `kind:24622 #p=self`, and dials each paired //! call-me-now back into the local mesh runtime. Idempotent: one listener //! per process; re-entrant calls return the live handle. @@ -31,7 +31,7 @@ use tauri::{AppHandle, Manager}; use tokio::sync::watch; use tokio_tungstenite::{connect_async, tungstenite::Message}; -use sprout_core::kind::{ +use buzz_core_pkg::kind::{ KIND_MESH_CALL_ME_NOW, KIND_MESH_CONNECT_REQUEST, KIND_MESH_STATUS_REPORT, }; @@ -138,14 +138,14 @@ async fn status_publisher_loop(app: AppHandle) { pub(crate) async fn publish_current_status_once(app: &AppHandle, reason: &str) { let state = app.state::(); if let Err(error) = publish_current_status_for_state(&state).await { - eprintln!("sprout-mesh: status report after {reason} failed: {error}"); + eprintln!("buzz-mesh: status report after {reason} failed: {error}"); } } pub(crate) async fn publish_stopped_status_once(app: &AppHandle, reason: &str) { let state = app.state::(); if let Err(error) = publish_stopped_status_for_state(&state).await { - eprintln!("sprout-mesh: stopped status report after {reason} failed: {error}"); + eprintln!("buzz-mesh: stopped status report after {reason} failed: {error}"); } } @@ -176,14 +176,14 @@ fn stopped_status_payload() -> serde_json::Value { }) } -/// The listener task body. Connects, authenticates as the Sprout identity, +/// The listener task body. Connects, authenticates as the Buzz identity, /// subscribes `24622 #p=self`, and dials each paired call-me-now. Reconnects /// with backoff on connection loss; flips `active` to `false` while down so /// `start_client` won't publish during an outage. async fn listener_loop(app: AppHandle, active: watch::Sender) { loop { if let Err(error) = listener_session(&app, &active).await { - eprintln!("sprout-mesh: call-me-now listener session ended: {error}"); + eprintln!("buzz-mesh: call-me-now listener session ended: {error}"); } let _ = active.send(false); tokio::time::sleep(RETRY_BACKOFF).await; @@ -236,7 +236,7 @@ async fn listener_session(app: &AppHandle, active: &watch::Sender) -> Resu if let Err(error) = crate::commands::ensure_client_node_for_model_dial_only(&state, &addr).await { - eprintln!("sprout-mesh: call-me-now dial failed: {error}"); + eprintln!("buzz-mesh: call-me-now dial failed: {error}"); } } } @@ -296,7 +296,7 @@ pub async fn start_client( )) } -/// Build + sign + submit the kind:24621 connect-request as the Sprout identity. +/// Build + sign + submit the kind:24621 connect-request as the Buzz identity. async fn publish_connect_request( state: &AppState, request: &RelayMeshConnectRequest<'_>, @@ -351,7 +351,7 @@ pub(crate) async fn publish_status_report( /// dropping expired ones. fn call_me_now_peer_addr(event: &nostr::Event) -> Option { let payload: serde_json::Value = serde_json::from_str(&event.content).ok()?; - if payload.get("type")?.as_str()? != "sprout-iroh-call-me-now" { + if payload.get("type")?.as_str()? != "buzz-iroh-call-me-now" { return None; } let now = chrono::Utc::now().timestamp().max(0) as u64; @@ -366,7 +366,7 @@ fn call_me_now_peer_addr(event: &nostr::Event) -> Option { .map(str::to_string) } -/// NIP-42 AUTH as the Sprout identity. Generalized from `commands::pairing`'s +/// NIP-42 AUTH as the Buzz identity. Generalized from `commands::pairing`'s /// `handle_nip42_auth` — same flow, signs with `state.keys` instead of a /// pairing session. Returns `Ok(())` when the relay does not challenge. async fn authenticate( @@ -512,7 +512,7 @@ mod tests { fn call_me_now_peer_addr_extracts_unexpired() { let future = chrono::Utc::now().timestamp() as u64 + 30; let event = test_event(&json!({ - "type": "sprout-iroh-call-me-now", + "type": "buzz-iroh-call-me-now", "peer_endpoint_addr": "node-abc", "attempt_id": "a1", "expires_at": future, @@ -523,7 +523,7 @@ mod tests { #[test] fn call_me_now_peer_addr_drops_expired() { let event = test_event(&json!({ - "type": "sprout-iroh-call-me-now", + "type": "buzz-iroh-call-me-now", "peer_endpoint_addr": "node-abc", "attempt_id": "a1", "expires_at": 1u64, diff --git a/desktop/src-tauri/src/mesh_llm/discovery.rs b/desktop/src-tauri/src/mesh_llm/discovery.rs index fd9ef0abc..ad12a1bf5 100644 --- a/desktop/src-tauri/src/mesh_llm/discovery.rs +++ b/desktop/src-tauri/src/mesh_llm/discovery.rs @@ -19,7 +19,7 @@ pub fn availability_from_events(events: Vec) -> MeshAvailability { return MeshAvailability::unavailable("relay mesh status is not published yet"); } - // Relay status is now per reporter (d=sprout-relay-mesh:), so a + // Relay status is now per reporter (d=buzz-relay-mesh:), so a // query returns multiple replaceable events. Aggregate them; do not pick the // newest single event or one member's machines hide everyone else's. let mut all_targets = Vec::::new(); @@ -102,7 +102,7 @@ pub fn availability_from_events(events: Vec) -> MeshAvailability { pub fn mesh_status_filter() -> serde_json::Value { serde_json::json!({ "kinds": [MESH_STATUS_KIND], - "#k": ["sprout-mesh-status"], + "#k": ["buzz-mesh-status"], "limit": 100 }) } @@ -112,8 +112,7 @@ fn reporter_pubkey_from_status_event(event: &nostr::Event) -> Option { let slice = tag.as_slice(); let d = slice.get(1)?; if slice.first().is_some_and(|name| name == "d") { - d.strip_prefix("sprout-relay-mesh:") - .map(ToString::to_string) + d.strip_prefix("buzz-relay-mesh:").map(ToString::to_string) } else { None } diff --git a/desktop/src-tauri/src/mesh_llm/mod.rs b/desktop/src-tauri/src/mesh_llm/mod.rs index 21cae1c70..99b112b03 100644 --- a/desktop/src-tauri/src/mesh_llm/mod.rs +++ b/desktop/src-tauri/src/mesh_llm/mod.rs @@ -21,13 +21,13 @@ const DEFAULT_MESH_CONSOLE_PORT: u16 = 3131; const MESH_STATUS_KIND: u64 = 30_621; const MESH_API_PORT_ENV: &str = "BUZZ_MESH_API_PORT"; const MESH_CONSOLE_PORT_ENV: &str = "BUZZ_MESH_CONSOLE_PORT"; -const RELAY_MESH_API_KEY_PLACEHOLDER: &str = "sprout-mesh-local"; +const RELAY_MESH_API_KEY_PLACEHOLDER: &str = "buzz-mesh-local"; /// ACP provider relay-mesh agents run on. Sources of truth for its command + /// MCP live in the runtime catalog (`known_acp_runtime_exact`); these are -/// only the fallbacks. `sprout-agent` reads the `SPROUT_AGENT_PROVIDER` / +/// only the fallbacks. `buzz-agent` reads the `BUZZ_AGENT_PROVIDER` / /// `OPENAI_COMPAT_*` env vars below — goose (the global default) does not. -const MESH_AGENT_PROVIDER_ID: &str = "sprout-agent"; -const MESH_AGENT_MCP_COMMAND: &str = "sprout-dev-mcp"; +const MESH_AGENT_PROVIDER_ID: &str = "buzz-agent"; +const MESH_AGENT_MCP_COMMAND: &str = "buzz-dev-mcp"; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] diff --git a/desktop/src-tauri/src/mesh_llm/mod_tests.rs b/desktop/src-tauri/src/mesh_llm/mod_tests.rs index 975a5708a..befcdb1aa 100644 --- a/desktop/src-tauri/src/mesh_llm/mod_tests.rs +++ b/desktop/src-tauri/src/mesh_llm/mod_tests.rs @@ -34,21 +34,21 @@ fn model_ref_is_family_agnostic() { } #[test] -fn agent_preset_runs_on_sprout_agent_not_goose() { +fn agent_preset_runs_on_buzz_agent_not_goose() { // Regression (Tyler): the relay-mesh preset used to hand the agent the // global default runtime (goose), which ignores the OpenAI-compat env // vars and falls back to its own provider. Mesh agents must run on - // sprout-agent, which reads those vars. + // buzz-agent, which reads those vars. let preset = super::agent_preset(super::MeshAgentPresetRequest { model_id: "Qwen3-8B-Q4_K_M".to_string(), }) .expect("preset for a valid model id"); - assert_eq!(preset.agent_command, "sprout-agent"); + assert_eq!(preset.agent_command, "buzz-agent"); assert_ne!(preset.agent_command, "goose"); - assert_eq!(preset.mcp_command, "sprout-dev-mcp"); + assert_eq!(preset.mcp_command, "buzz-dev-mcp"); - // The env vars sprout-agent's config layer reads (crates/sprout-agent). + // The env vars buzz-agent's config layer reads (crates/buzz-agent). assert_eq!( preset .env_vars diff --git a/desktop/src-tauri/src/mesh_llm/preset.rs b/desktop/src-tauri/src/mesh_llm/preset.rs index 4c8a69a51..12e12c326 100644 --- a/desktop/src-tauri/src/mesh_llm/preset.rs +++ b/desktop/src-tauri/src/mesh_llm/preset.rs @@ -33,14 +33,14 @@ pub fn agent_preset(request: MeshAgentPresetRequest) -> Result Option { current.parent().map(|p| p.join(CANONICAL_DEV_IDENTIFIER)) } +fn legacy_app_data_dir(current: &Path) -> Option { + let name = current.file_name()?.to_str()?; + let legacy_name = if name.starts_with(CANONICAL_DEV_IDENTIFIER) { + name.replacen(CANONICAL_DEV_IDENTIFIER, LEGACY_CANONICAL_DEV_IDENTIFIER, 1) + } else if name.starts_with("xyz.block.buzz.app") { + name.replacen("xyz.block.buzz.app", LEGACY_RELEASE_IDENTIFIER, 1) + } else { + return None; + }; + current.parent().map(|parent| parent.join(legacy_name)) +} + +fn copy_dir_all(src: &Path, dst: &Path) -> std::io::Result<()> { + std::fs::create_dir_all(dst)?; + for entry in std::fs::read_dir(src)? { + let entry = entry?; + let src_path = entry.path(); + let dst_path = dst.join(entry.file_name()); + let metadata = std::fs::symlink_metadata(&src_path)?; + if metadata.file_type().is_symlink() { + #[cfg(unix)] + { + let target = std::fs::read_link(&src_path)?; + if dst_path.exists() || dst_path.is_symlink() { + let _ = std::fs::remove_file(&dst_path); + } + std::os::unix::fs::symlink(target, &dst_path)?; + } + #[cfg(not(unix))] + { + continue; + } + } else if metadata.is_dir() { + copy_dir_all(&src_path, &dst_path)?; + } else if metadata.is_file() { + if let Some(parent) = dst_path.parent() { + std::fs::create_dir_all(parent)?; + } + if !dst_path.exists() { + std::fs::copy(&src_path, &dst_path)?; + } + } + } + Ok(()) +} + +/// Copy one-time app state from the legacy app identifier directory to +/// the current Buzz identifier directory. The Tauri identifier controls the app +/// data path, so without this copy a product rename would look like a fresh +/// install and users would lose their persisted identity and agent settings. +pub fn migrate_legacy_app_data_dir(app: &tauri::AppHandle) { + let current_dir = match app.path().app_data_dir() { + Ok(dir) => dir, + Err(e) => { + eprintln!("buzz-desktop: app-data-migration: cannot resolve app data dir: {e}"); + return; + } + }; + let Some(legacy_dir) = legacy_app_data_dir(¤t_dir) else { + return; + }; + if !legacy_dir.exists() { + return; + } + match copy_dir_all(&legacy_dir, ¤t_dir) { + Ok(()) => eprintln!( + "buzz-desktop: app-data-migration: copied legacy data from {} to {}", + legacy_dir.display(), + current_dir.display() + ), + Err(error) => eprintln!( + "buzz-desktop: app-data-migration: failed to copy {} to {}: {error}", + legacy_dir.display(), + current_dir.display() + ), + } +} + /// Read a JSON array of objects from `path`, apply `f` to each object, /// and write back if any mutation returned `true`. fn patch_json_records( @@ -46,7 +126,7 @@ fn patch_json_records( }; let Ok(mut records) = serde_json::from_str::>(&content) else { eprintln!( - "sprout-desktop: patch-json-records: failed to parse {}", + "buzz-desktop: patch-json-records: failed to parse {}", path.display() ); return; @@ -68,35 +148,33 @@ fn patch_json_records( /// data directory to the canonical dev data directory. /// /// Guards: -/// - `SPROUT_SHARE_IDENTITY` must be `"1"` -/// - `SPROUT_PRIVATE_KEY` must parse as valid `nostr::Keys` +/// - `BUZZ_SHARE_IDENTITY` must be `"1"` +/// - `BUZZ_PRIVATE_KEY` must parse as valid `nostr::Keys` /// - The canonical dir must differ from the current dir (skip if we ARE canonical) /// - The canonical dir must exist pub fn sync_shared_agent_data(app: &tauri::AppHandle) { // Guard: only runs when sharing identity with a worktree. - let is_shared = std::env::var("SPROUT_SHARE_IDENTITY") + let is_shared = std::env::var("BUZZ_SHARE_IDENTITY") .map(|v| v == "1") .unwrap_or(false); if !is_shared { return; } - // Guard: SPROUT_PRIVATE_KEY must be a valid nostr key. - let has_valid_key = std::env::var("SPROUT_PRIVATE_KEY") + // Guard: BUZZ_PRIVATE_KEY must be a valid nostr key. + let has_valid_key = std::env::var("BUZZ_PRIVATE_KEY") .ok() .and_then(|k| k.parse::().ok()) .is_some(); if !has_valid_key { - eprintln!( - "sprout-desktop: shared-agent-sync: SPROUT_PRIVATE_KEY missing or invalid, skipping" - ); + eprintln!("buzz-desktop: shared-agent-sync: BUZZ_PRIVATE_KEY missing or invalid, skipping"); return; } let current_dir = match app.path().app_data_dir() { Ok(dir) => dir, Err(e) => { - eprintln!("sprout-desktop: shared-agent-sync: cannot resolve app data dir: {e}"); + eprintln!("buzz-desktop: shared-agent-sync: cannot resolve app data dir: {e}"); return; } }; @@ -104,9 +182,7 @@ pub fn sync_shared_agent_data(app: &tauri::AppHandle) { let canonical_dir = match canonical_dev_data_dir(¤t_dir) { Some(dir) => dir, None => { - eprintln!( - "sprout-desktop: shared-agent-sync: cannot compute canonical dir (no parent)" - ); + eprintln!("buzz-desktop: shared-agent-sync: cannot compute canonical dir (no parent)"); return; } }; @@ -124,7 +200,7 @@ pub fn sync_shared_agent_data(app: &tauri::AppHandle) { // Guard: skip if canonical dir doesn't exist. if !canonical_dir.exists() { eprintln!( - "sprout-desktop: shared-agent-sync: canonical dir does not exist: {}", + "buzz-desktop: shared-agent-sync: canonical dir does not exist: {}", canonical_dir.display() ); return; @@ -142,7 +218,7 @@ pub fn sync_shared_agent_data(app: &tauri::AppHandle) { if let Some(parent) = dst.parent() { if let Err(e) = std::fs::create_dir_all(parent) { eprintln!( - "sprout-desktop: shared-agent-sync: failed to create {}: {e}", + "buzz-desktop: shared-agent-sync: failed to create {}: {e}", parent.display() ); continue; @@ -166,7 +242,7 @@ pub fn sync_shared_agent_data(app: &tauri::AppHandle) { match std::os::unix::fs::symlink(&src, &dst) { Ok(_) => synced += 1, Err(e) => { - eprintln!("sprout-desktop: shared-agent-sync: failed to symlink {rel}: {e}"); + eprintln!("buzz-desktop: shared-agent-sync: failed to symlink {rel}: {e}"); } } } @@ -179,7 +255,7 @@ pub fn sync_shared_agent_data(app: &tauri::AppHandle) { if !canonical_target.exists() { if let Err(e) = std::fs::create_dir_all(&canonical_target) { eprintln!( - "sprout-desktop: shared-agent-sync: failed to create {}: {e}", + "buzz-desktop: shared-agent-sync: failed to create {}: {e}", canonical_target.display() ); } @@ -205,7 +281,7 @@ pub fn sync_shared_agent_data(app: &tauri::AppHandle) { let _ = std::fs::remove_dir_all(&sibling_dir); let _ = std::os::unix::fs::symlink(&canonical_target, &sibling_dir); eprintln!( - "sprout-desktop: shared-agent-sync: migrated {rel} from {}", + "buzz-desktop: shared-agent-sync: migrated {rel} from {}", sibling.display() ); break; @@ -227,7 +303,7 @@ pub fn sync_shared_agent_data(app: &tauri::AppHandle) { if let Some(parent) = dst.parent() { if let Err(e) = std::fs::create_dir_all(parent) { eprintln!( - "sprout-desktop: shared-agent-sync: failed to create {}: {e}", + "buzz-desktop: shared-agent-sync: failed to create {}: {e}", parent.display() ); continue; @@ -251,14 +327,14 @@ pub fn sync_shared_agent_data(app: &tauri::AppHandle) { match std::os::unix::fs::symlink(&src, &dst) { Ok(_) => synced += 1, Err(e) => { - eprintln!("sprout-desktop: shared-agent-sync: failed to symlink {rel}: {e}"); + eprintln!("buzz-desktop: shared-agent-sync: failed to symlink {rel}: {e}"); } } } if synced > 0 { eprintln!( - "sprout-desktop: shared-agent-sync: {synced} item(s) linked to {}", + "buzz-desktop: shared-agent-sync: {synced} item(s) linked to {}", canonical_dir.display() ); } @@ -300,7 +376,7 @@ fn reconcile_team_dirs_in_file(path: &Path, canonical_dir: &Path) { return false; } eprintln!( - "sprout-desktop: team-dir-reconcile: {:?}: {:?} → {:?}", + "buzz-desktop: team-dir-reconcile: {:?}: {:?} → {:?}", obj.get("name").and_then(|v| v.as_str()).unwrap_or("?"), team_path, expected, @@ -417,9 +493,7 @@ pub fn migrate_packs_to_teams(app: &tauri::AppHandle) { report .errors .push(format!("failed to rename packs → teams: {e}")); - eprintln!( - "sprout-desktop: packs→teams migration: directory rename failed: {e}" - ); + eprintln!("buzz-desktop: packs→teams migration: directory rename failed: {e}"); return; } } @@ -472,7 +546,7 @@ pub fn migrate_packs_to_teams(app: &tauri::AppHandle) { if report.packs_migrated > 0 || report.personas_updated > 0 || report.agents_updated > 0 { eprintln!( - "sprout-desktop: packs→teams migration complete: {} dirs, {} personas, {} agents{}", + "buzz-desktop: packs→teams migration complete: {} dirs, {} personas, {} agents{}", report.packs_migrated, report.personas_updated, report.agents_updated, @@ -504,11 +578,11 @@ fn reconcile_mcp_commands_in_file(path: &Path) { } // Only fix values that are clearly stale (empty or a removed binary). // Leave user-customized values untouched. - if !current.is_empty() && current != "sprout-mcp-server" { + if !current.is_empty() && current != "buzz-mcp-server" { return false; } eprintln!( - "sprout-desktop: runtime-reconcile: {:?} ({:?}): mcp_command {:?} → {:?}", + "buzz-desktop: runtime-reconcile: {:?} ({:?}): mcp_command {:?} → {:?}", obj.get("name").and_then(|v| v.as_str()).unwrap_or("?"), agent_command, current, diff --git a/desktop/src-tauri/src/migration_tests.rs b/desktop/src-tauri/src/migration_tests.rs index 133228856..21bf4fcfb 100644 --- a/desktop/src-tauri/src/migration_tests.rs +++ b/desktop/src-tauri/src/migration_tests.rs @@ -3,11 +3,11 @@ use super::*; #[test] fn canonical_dev_data_dir_replaces_last_component() { let current = - PathBuf::from("/Users/me/Library/Application Support/xyz.block.sprout.app.dev.my-branch"); + PathBuf::from("/Users/me/Library/Application Support/xyz.block.buzz.app.dev.my-branch"); let canonical = canonical_dev_data_dir(¤t).unwrap(); assert_eq!( canonical, - PathBuf::from("/Users/me/Library/Application Support/xyz.block.sprout.app.dev") + PathBuf::from("/Users/me/Library/Application Support/xyz.block.buzz.app.dev") ); } @@ -17,14 +17,58 @@ fn canonical_dev_data_dir_returns_none_for_root() { assert!(canonical_dev_data_dir(Path::new("/")).is_none()); } +#[test] +fn legacy_app_data_dir_maps_release_identifier() { + let current = PathBuf::from("/Users/me/Library/Application Support/xyz.block.buzz.app"); + let legacy = legacy_app_data_dir(¤t).unwrap(); + assert_eq!( + legacy, + PathBuf::from("/Users/me/Library/Application Support/xyz.block.sprout.app") + ); +} + +#[test] +fn legacy_app_data_dir_maps_dev_worktree_identifier() { + let current = + PathBuf::from("/Users/me/Library/Application Support/xyz.block.buzz.app.dev.my-branch"); + let legacy = legacy_app_data_dir(¤t).unwrap(); + assert_eq!( + legacy, + PathBuf::from("/Users/me/Library/Application Support/xyz.block.sprout.app.dev.my-branch",) + ); +} + +#[test] +fn copy_dir_all_preserves_nested_files_without_overwriting() { + let dir = tempfile::tempdir().unwrap(); + let src = dir.path().join("old"); + let dst = dir.path().join("new"); + std::fs::create_dir_all(src.join("agents")).unwrap(); + std::fs::write(src.join("identity.key"), "old-key").unwrap(); + std::fs::write(src.join("agents/managed-agents.json"), "old-agents").unwrap(); + std::fs::create_dir_all(&dst).unwrap(); + std::fs::write(dst.join("identity.key"), "new-key").unwrap(); + + copy_dir_all(&src, &dst).unwrap(); + + assert_eq!( + std::fs::read_to_string(dst.join("identity.key")).unwrap(), + "new-key" + ); + assert_eq!( + std::fs::read_to_string(dst.join("agents/managed-agents.json")).unwrap(), + "old-agents" + ); +} + /// Helper: create a temp dir structure mimicking canonical + worktree layout. /// Packs live in a `.main` sibling (not canonical) to match real-world state. /// Returns `(parent_dir_handle, canonical_dir, worktree_dir)`. fn setup_sync_layout() -> (tempfile::TempDir, PathBuf, PathBuf) { let parent = tempfile::tempdir().unwrap(); let canonical = parent.path().join(CANONICAL_DEV_IDENTIFIER); - let worktree = parent.path().join("xyz.block.sprout.app.dev.my-branch"); - let main_instance = parent.path().join("xyz.block.sprout.app.dev.main"); + let worktree = parent.path().join("xyz.block.buzz.app.dev.my-branch"); + let main_instance = parent.path().join("xyz.block.buzz.app.dev.main"); std::fs::create_dir_all(canonical.join("agents")).unwrap(); std::fs::write( @@ -264,10 +308,10 @@ fn canonical_dev_data_dir_returns_self_for_canonical_instance() { // When the current app data dir IS the canonical dev identifier, // canonical_dev_data_dir returns the exact same path — the caller // (sync_shared_agent_data) uses this equality to skip the sync. - // The env-var guards (SPROUT_SHARE_IDENTITY, SPROUT_PRIVATE_KEY) + // The env-var guards (BUZZ_SHARE_IDENTITY, BUZZ_PRIVATE_KEY) // require a live Tauri AppHandle and are covered by integration // testing only. - let current = PathBuf::from("/Users/me/Library/Application Support/xyz.block.sprout.app.dev"); + let current = PathBuf::from("/Users/me/Library/Application Support/xyz.block.buzz.app.dev"); assert_eq!(canonical_dev_data_dir(¤t).unwrap(), current); // Also verify with a temp dir on the real filesystem. @@ -316,7 +360,7 @@ fn sync_migrates_teams_from_sibling_to_canonical() { let main_instance = canonical .parent() .unwrap() - .join("xyz.block.sprout.app.dev.main"); + .join("xyz.block.buzz.app.dev.main"); // Before sync: canonical has no teams, .main has the real team dir. assert!(!canonical.join("agents/teams").exists()); @@ -363,7 +407,7 @@ fn team_dir_reconcile_rewrites_worktree_path() { "{}/agents/packs/com.wpfleger.sietch-tabr", parent .path() - .join("xyz.block.sprout.app.dev.worktree-my-branch") + .join("xyz.block.buzz.app.dev.worktree-my-branch") .display() ); let expected_path = format!( @@ -397,7 +441,7 @@ fn team_dir_reconcile_rewrites_new_field_name() { "{}/agents/teams/com.wpfleger.sietch-tabr", parent .path() - .join("xyz.block.sprout.app.dev.worktree-my-branch") + .join("xyz.block.buzz.app.dev.worktree-my-branch") .display() ); let expected_path = format!( @@ -455,7 +499,7 @@ fn team_dir_reconcile_skips_records_without_team_dir() { &canonical, &serde_json::json!([{ "name": "Test Agent", - "agent_command": "sprout-agent" + "agent_command": "buzz-agent" }]), ); @@ -476,7 +520,7 @@ fn team_dir_reconcile_is_idempotent() { "{}/agents/packs/com.wpfleger.sietch-tabr", parent .path() - .join("xyz.block.sprout.app.dev.worktree-my-branch") + .join("xyz.block.buzz.app.dev.worktree-my-branch") .display() ); @@ -719,14 +763,14 @@ fn rename_provider_to_runtime_preserves_existing_runtime_over_provider() { } #[test] -fn reconcile_mcp_commands_clears_stale_sprout_mcp_server() { +fn reconcile_mcp_commands_clears_stale_buzz_mcp_server() { let dir = tempfile::tempdir().unwrap(); write_agents_json( dir.path(), &serde_json::json!([{ "name": "Solo", "agent_command": "goose", - "mcp_command": "sprout-mcp-server" + "mcp_command": "buzz-mcp-server" }]), ); reconcile_mcp_commands_in_file(&dir.path().join("agents/managed-agents.json")); @@ -735,19 +779,19 @@ fn reconcile_mcp_commands_clears_stale_sprout_mcp_server() { } #[test] -fn reconcile_mcp_commands_sets_canonical_for_sprout_agent() { +fn reconcile_mcp_commands_sets_canonical_for_buzz_agent() { let dir = tempfile::tempdir().unwrap(); write_agents_json( dir.path(), &serde_json::json!([{ "name": "Stilgar", - "agent_command": "sprout-agent", - "mcp_command": "sprout-mcp-server" + "agent_command": "buzz-agent", + "mcp_command": "buzz-mcp-server" }]), ); reconcile_mcp_commands_in_file(&dir.path().join("agents/managed-agents.json")); let records = read_agents_json(dir.path()); - assert_eq!(records[0]["mcp_command"], "sprout-dev-mcp"); + assert_eq!(records[0]["mcp_command"], "buzz-dev-mcp"); } #[test] @@ -771,7 +815,7 @@ fn reconcile_mcp_commands_leaves_unknown_runtime_untouched() { let json = serde_json::json!([{ "name": "Custom", "agent_command": "my-custom-agent", - "mcp_command": "sprout-mcp-server" + "mcp_command": "buzz-mcp-server" }]); write_agents_json(dir.path(), &json); let path = dir.path().join("agents/managed-agents.json"); @@ -788,7 +832,7 @@ fn reconcile_mcp_commands_is_idempotent() { &serde_json::json!([{ "name": "Solo", "agent_command": "goose", - "mcp_command": "sprout-mcp-server" + "mcp_command": "buzz-mcp-server" }]), ); let path = dir.path().join("agents/managed-agents.json"); @@ -804,10 +848,10 @@ fn reconcile_mcp_commands_handles_mixed_agents() { write_agents_json( dir.path(), &serde_json::json!([ - {"name": "Stale Goose", "agent_command": "goose", "mcp_command": "sprout-mcp-server"}, + {"name": "Stale Goose", "agent_command": "goose", "mcp_command": "buzz-mcp-server"}, {"name": "Clean Goose", "agent_command": "goose", "mcp_command": ""}, {"name": "Custom Agent", "agent_command": "goose", "mcp_command": "my-custom-mcp"}, - {"name": "Stale Sprout", "agent_command": "sprout-agent", "mcp_command": "sprout-mcp-server"} + {"name": "Stale Buzz", "agent_command": "buzz-agent", "mcp_command": "buzz-mcp-server"} ]), ); reconcile_mcp_commands_in_file(&dir.path().join("agents/managed-agents.json")); @@ -815,7 +859,7 @@ fn reconcile_mcp_commands_handles_mixed_agents() { assert_eq!(records[0]["mcp_command"], ""); assert_eq!(records[1]["mcp_command"], ""); assert_eq!(records[2]["mcp_command"], "my-custom-mcp"); - assert_eq!(records[3]["mcp_command"], "sprout-dev-mcp"); + assert_eq!(records[3]["mcp_command"], "buzz-dev-mcp"); } #[test] @@ -823,7 +867,7 @@ fn reconcile_mcp_commands_skips_record_without_agent_command() { let dir = tempfile::tempdir().unwrap(); let json = serde_json::json!([{ "name": "No Command", - "mcp_command": "sprout-mcp-server" + "mcp_command": "buzz-mcp-server" }]); write_agents_json(dir.path(), &json); let path = dir.path().join("agents/managed-agents.json"); diff --git a/desktop/src-tauri/src/nostr_convert.rs b/desktop/src-tauri/src/nostr_convert.rs index 5ea7467de..aeb7f8c59 100644 --- a/desktop/src-tauri/src/nostr_convert.rs +++ b/desktop/src-tauri/src/nostr_convert.rs @@ -75,7 +75,7 @@ pub(crate) fn profile_has_valid_oa_owner(event: &Event) -> bool { let Ok(json) = serde_json::to_string(slice) else { continue; }; - if sprout_sdk::nip_oa::verify_auth_tag(&json, &target_pubkey).is_ok() { + if buzz_sdk_pkg::nip_oa::verify_auth_tag(&json, &target_pubkey).is_ok() { return true; } } @@ -590,7 +590,7 @@ mod tests { let agent_keys = Keys::generate(); let owner_keys = Keys::generate(); let agent_pubkey = agent_keys.public_key(); - let tag_json = sprout_sdk::nip_oa::compute_auth_tag(&owner_keys, &agent_pubkey, "") + let tag_json = buzz_sdk_pkg::nip_oa::compute_auth_tag(&owner_keys, &agent_pubkey, "") .expect("compute auth tag"); let tag_values: Vec = serde_json::from_str(&tag_json).expect("parse auth tag json"); let auth_tag = Tag::parse(tag_values).expect("parse auth tag"); diff --git a/desktop/src-tauri/src/nostr_convert/user_search.rs b/desktop/src-tauri/src/nostr_convert/user_search.rs index cdee90b72..85a68df14 100644 --- a/desktop/src-tauri/src/nostr_convert/user_search.rs +++ b/desktop/src-tauri/src/nostr_convert/user_search.rs @@ -200,7 +200,7 @@ mod tests { let agent_keys = nostr::Keys::generate(); let owner_keys = nostr::Keys::generate(); let agent_pubkey = agent_keys.public_key(); - let tag_json = sprout_sdk::nip_oa::compute_auth_tag(&owner_keys, &agent_pubkey, "") + let tag_json = buzz_sdk_pkg::nip_oa::compute_auth_tag(&owner_keys, &agent_pubkey, "") .expect("compute auth tag"); let tag_values: Vec = serde_json::from_str(&tag_json).expect("parse auth tag json"); let auth_tag = Tag::parse(tag_values).expect("parse auth tag"); diff --git a/desktop/src-tauri/src/prevent_sleep.rs b/desktop/src-tauri/src/prevent_sleep.rs index 4679caf06..21e843500 100644 --- a/desktop/src-tauri/src/prevent_sleep.rs +++ b/desktop/src-tauri/src/prevent_sleep.rs @@ -62,7 +62,7 @@ pub fn acquire( #[cfg(target_os = "macos")] { let assertion_type = c"PreventUserIdleSystemSleep".as_ptr(); - let reason = c"Sprout \u{2014} agents are active".as_ptr(); + let reason = c"Buzz \u{2014} agents are active".as_ptr(); unsafe { let cf_type = macos::CFStringCreateWithCString( diff --git a/desktop/src-tauri/src/relay.rs b/desktop/src-tauri/src/relay.rs index e02c23ff2..3333687ff 100644 --- a/desktop/src-tauri/src/relay.rs +++ b/desktop/src-tauri/src/relay.rs @@ -5,7 +5,7 @@ use serde::de::DeserializeOwned; use serde::Deserialize; use sha2::{Digest, Sha256}; -// nostr 0.36 alias — required for cross-version bridging with sprout-sdk. +// nostr 0.36 alias — required for cross-version bridging with buzz-sdk. use crate::app_state::AppState; @@ -191,7 +191,7 @@ pub async fn query_relay_at( /// Parse a command-event OK message of the form `"response:"`. /// -/// Sprout's command kinds (e.g. 41010, 30620, 46020) acknowledge writes via +/// Buzz's command kinds (e.g. 41010, 30620, 46020) acknowledge writes via /// relay OK messages whose payload is a `response:`-prefixed JSON document. /// This helper strips the prefix and deserializes the remainder as `T`. pub fn parse_command_response(message: &str) -> Result { @@ -211,7 +211,7 @@ pub fn parse_command_response(message: &str) -> Result String { let owner_keys = nostr::Keys::generate(); let agent_pubkey_hex = agent_keys.public_key().to_hex(); let agent_compat_pubkey = nostr::PublicKey::from_hex(&agent_pubkey_hex).expect("valid hex pubkey should parse"); - sprout_sdk::nip_oa::compute_auth_tag(&owner_keys, &agent_compat_pubkey, "") + buzz_sdk_pkg::nip_oa::compute_auth_tag(&owner_keys, &agent_compat_pubkey, "") .expect("compute_auth_tag should not fail with distinct keys") } diff --git a/desktop/src-tauri/tauri.conf.json b/desktop/src-tauri/tauri.conf.json index 5d41d21e0..9fbe0db08 100644 --- a/desktop/src-tauri/tauri.conf.json +++ b/desktop/src-tauri/tauri.conf.json @@ -1,8 +1,8 @@ { "$schema": "https://schema.tauri.app/config/2", - "productName": "Sprout", + "productName": "Buzz", "version": "0.3.16", - "identifier": "xyz.block.sprout.app", + "identifier": "xyz.block.buzz.app", "build": { "beforeDevCommand": { "script": "exec ./node_modules/.bin/vite", @@ -43,7 +43,7 @@ }, "deep-link": { "desktop": { - "schemes": ["sprout", "buzz"] + "schemes": ["buzz"] } } }, diff --git a/desktop/src-tauri/tauri.dev.conf.json b/desktop/src-tauri/tauri.dev.conf.json index 30902f2f5..dd2b86c58 100644 --- a/desktop/src-tauri/tauri.dev.conf.json +++ b/desktop/src-tauri/tauri.dev.conf.json @@ -1,4 +1,4 @@ { - "identifier": "xyz.block.sprout.app.dev", - "productName": "Sprout Dev" + "identifier": "xyz.block.buzz.app.dev", + "productName": "Buzz Dev" } diff --git a/desktop/src/app/App.tsx b/desktop/src/app/App.tsx index 80b6f4a4c..5eb266cd2 100644 --- a/desktop/src/app/App.tsx +++ b/desktop/src/app/App.tsx @@ -1,7 +1,7 @@ import { getCurrentWindow } from "@tauri-apps/api/window"; import { QueryClientProvider } from "@tanstack/react-query"; import { RouterProvider } from "@tanstack/react-router"; -import { Sprout } from "lucide-react"; +import { Hexagon } from "lucide-react"; import { type ReactNode, useCallback, @@ -19,7 +19,7 @@ import type { Workspace } from "@/features/workspaces/types"; import { useWorkspaceInit } from "@/features/workspaces/useWorkspaceInit"; import { useWorkspaces } from "@/features/workspaces/useWorkspaces"; import { WelcomeSetup } from "@/features/workspaces/ui/WelcomeSetup"; -import { createSproutQueryClient } from "@/shared/api/queryClient"; +import { createBuzzQueryClient } from "@/shared/api/queryClient"; import { isSharedIdentity as isSharedIdentityCmd } from "@/shared/api/tauri"; import { listenForDeepLinks } from "@/shared/deep-link"; import { useSystemColorScheme } from "@/shared/theme/useSystemColorScheme"; @@ -31,7 +31,7 @@ const LOADING_TEXT = "Setting up your workspace..."; function AppLoadingGate() { return (
@@ -40,7 +40,7 @@ function AppLoadingGate() { className="mt-6 text-center text-3xl font-semibold tracking-tight text-foreground" > {LOADING_TEXT} -
@@ -292,7 +292,7 @@ export function CreateAgentRuntimeFields({ className="text-xs text-muted-foreground" id="help-agent-parallelism" > - Number of ACP worker subprocesses. sprout-acp allows 1-32. + Number of ACP worker subprocesses. buzz-acp allows 1-32.

@@ -309,7 +309,7 @@ export function CreateAgentRuntimeFields({ value={mcpToolsets} />

- Comma-separated list of toolsets to expose via SPROUT_TOOLSETS. + Comma-separated list of toolsets to expose via BUZZ_TOOLSETS. Available: default, channel_admin, dms, canvas, workflow_admin, identity, forums, social, media. Leave blank for default toolsets (default, canvas, forums, dms, media). @@ -332,7 +332,7 @@ export function CreateAgentRuntimeFields({ className="text-xs text-muted-foreground" id="help-agent-system-prompt" > - Blank means no override. sprout-acp will not add a [System] prompt. + Blank means no override. buzz-acp will not add a [System] prompt.

diff --git a/desktop/src/features/agents/ui/RespondToField.tsx b/desktop/src/features/agents/ui/RespondToField.tsx index 45631cd2e..2e903bfeb 100644 --- a/desktop/src/features/agents/ui/RespondToField.tsx +++ b/desktop/src/features/agents/ui/RespondToField.tsx @@ -17,7 +17,7 @@ import { UserAvatar } from "@/shared/ui/UserAvatar"; * Inbound author gate UI for create/edit agent dialogs. * * Dropdown: - * - Owner only (default; matches `sprout-acp --respond-to=owner-only`) + * - Owner only (default; matches `buzz-acp --respond-to=owner-only`) * - Anyone (`--respond-to=anyone` — fully open bot) * - Allowlist (`--respond-to=allowlist`, plus the chip list as * `--respond-to-allowlist`) diff --git a/desktop/src/features/agents/ui/SecretRevealDialog.tsx b/desktop/src/features/agents/ui/SecretRevealDialog.tsx index ad563879e..bcbe55655 100644 --- a/desktop/src/features/agents/ui/SecretRevealDialog.tsx +++ b/desktop/src/features/agents/ui/SecretRevealDialog.tsx @@ -38,7 +38,7 @@ export function SecretRevealDialog({ Private key (nsec)

- This is the agent identity used by `sprout-acp`. + This is the agent identity used by `buzz-acp`.

right.length - left.length, ); -const SPROUT_TOOL_TITLE_ALIASES: Array<[RegExp, string]> = [ +const BUZZ_TOOL_TITLE_ALIASES: Array<[RegExp, string]> = [ [/\bsending message to channel\b/, "send_message"], [/\bretrieving recent messages from channel\b/, "get_messages"], [/\bgetting channel details\b/, "get_channel"], @@ -139,10 +136,10 @@ const SPROUT_TOOL_TITLE_ALIASES: Array<[RegExp, string]> = [ [/\bremoving reaction\b/, "remove_reaction"], ]; -export function getSproutToolInfo(title: string): SproutToolInfo | null { +export function getBuzzToolInfo(title: string): BuzzToolInfo | null { const name = normalizeToolName(title); - const isRead = SPROUT_READ_TOOLS.has(name); - const isWrite = SPROUT_WRITE_TOOLS.has(name); + const isRead = BUZZ_READ_TOOLS.has(name); + const isWrite = BUZZ_WRITE_TOOLS.has(name); if (!isRead && !isWrite) { return null; } @@ -151,8 +148,8 @@ export function getSproutToolInfo(title: string): SproutToolInfo | null { return { icon: Workflow, label: isRead - ? "Reads workflow state from Sprout." - : "Updates workflow state in Sprout.", + ? "Reads workflow state from Buzz." + : "Updates workflow state in Buzz.", tone: isWrite ? "write" : "read", }; } @@ -164,8 +161,8 @@ export function getSproutToolInfo(title: string): SproutToolInfo | null { return { icon: Hash, label: isRead - ? "Reads channel context from the Sprout relay." - : "Changes channel state in the Sprout relay.", + ? "Reads channel context from the Buzz relay." + : "Changes channel state in the Buzz relay.", tone: isWrite ? "write" : "read", }; } @@ -177,15 +174,15 @@ export function getSproutToolInfo(title: string): SproutToolInfo | null { return { icon: Users, label: isRead - ? "Reads Sprout identity or presence data." - : "Updates Sprout identity or membership data.", + ? "Reads Buzz identity or presence data." + : "Updates Buzz identity or membership data.", tone: isWrite ? "write" : "admin", }; } if (name.includes("search") || name === "get_feed") { return { icon: Search, - label: "Searches relay-visible Sprout history.", + label: "Searches relay-visible Buzz history.", tone: "read", }; } @@ -196,23 +193,23 @@ export function getSproutToolInfo(title: string): SproutToolInfo | null { ) { return { icon: Send, - label: "Publishes relay-visible Sprout activity.", + label: "Publishes relay-visible Buzz activity.", tone: "write", }; } return { icon: MessageSquare, - label: isRead ? "Reads from Sprout." : "Writes to Sprout.", + label: isRead ? "Reads from Buzz." : "Writes to Buzz.", tone: isWrite ? "write" : "read", }; } export function normalizeToolName(title: string): string { - const knownName = findSproutToolName(title, true); + const knownName = findBuzzToolName(title, true); if (knownName) return knownName; - const normalized = normalizeToolNameText(title).replace(/^sprout_/, ""); + const normalized = normalizeToolNameText(title).replace(/^buzz_/, ""); return normalized.match(/[a-z][a-z0-9_]+/)?.[0] ?? normalized; } @@ -225,27 +222,27 @@ export function normalizeToolNameText(value: string): string { .replace(/^_+|_+$/g, ""); } -export function findSproutToolName(value: string, includeShortNames: boolean) { - const alias = findSproutToolAlias(value); +export function findBuzzToolName(value: string, includeShortNames: boolean) { + const alias = findBuzzToolAlias(value); if (alias) return alias; const normalized = normalizeToolNameText(value); return ( - SPROUT_TOOL_NAMES_BY_LENGTH.find( + BUZZ_TOOL_NAMES_BY_LENGTH.find( (name) => (includeShortNames || name.length >= 8) && normalized.includes(name), ) ?? null ); } -function findSproutToolAlias(value: string) { +function findBuzzToolAlias(value: string) { const normalizedPhrase = value .trim() .toLowerCase() .replace(/[_-]+/g, " ") .replace(/\s+/g, " "); return ( - SPROUT_TOOL_TITLE_ALIASES.find(([pattern]) => + BUZZ_TOOL_TITLE_ALIASES.find(([pattern]) => pattern.test(normalizedPhrase), )?.[1] ?? null ); @@ -271,7 +268,7 @@ export function formatToolTitle( fallbackTitle?: string, ): string { const name = normalizeToolName(toolName); - if (SPROUT_READ_TOOLS.has(name) || SPROUT_WRITE_TOOLS.has(name)) { + if (BUZZ_READ_TOOLS.has(name) || BUZZ_WRITE_TOOLS.has(name)) { return name .split("_") .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) diff --git a/desktop/src/features/agents/ui/agentSessionTranscript.ts b/desktop/src/features/agents/ui/agentSessionTranscript.ts index 792447365..60cf7f62b 100644 --- a/desktop/src/features/agents/ui/agentSessionTranscript.ts +++ b/desktop/src/features/agents/ui/agentSessionTranscript.ts @@ -5,7 +5,7 @@ import type { TranscriptItem, } from "./agentSessionTypes"; import { - findSproutToolName, + findBuzzToolName, isGenericToolTitle, normalizeToolStatus, } from "./agentSessionToolCatalog"; @@ -189,7 +189,7 @@ function upsertTool( id: string, title: string, toolName: string, - sproutToolName: string | null, + buzzToolName: string | null, status: ToolStatus, args: Record, result: string, @@ -198,23 +198,23 @@ function upsertTool( channelId: string | null, ) { const existing = d.itemsById.get(id); - const canonicalSproutToolName = - sproutToolName ?? findSproutToolName(toolName, true); + const canonicalBuzzToolName = + buzzToolName ?? findBuzzToolName(toolName, true); if (existing?.type === "tool") { const updatedTitle = !isGenericToolTitle(title) ? title : existing.title; let updatedToolName = existing.toolName; - let updatedSproutToolName = existing.sproutToolName; - if (canonicalSproutToolName) { - updatedSproutToolName = canonicalSproutToolName; - updatedToolName = canonicalSproutToolName; - } else if (!existing.sproutToolName && !isGenericToolTitle(toolName)) { + let updatedBuzzToolName = existing.buzzToolName; + if (canonicalBuzzToolName) { + updatedBuzzToolName = canonicalBuzzToolName; + updatedToolName = canonicalBuzzToolName; + } else if (!existing.buzzToolName && !isGenericToolTitle(toolName)) { updatedToolName = toolName; } replaceItem(d, id, { ...existing, title: updatedTitle, toolName: updatedToolName, - sproutToolName: updatedSproutToolName, + buzzToolName: updatedBuzzToolName, status, args: Object.keys(args).length > 0 ? args : existing.args, result: result || existing.result, @@ -233,8 +233,8 @@ function upsertTool( id, type: "tool", title, - toolName: canonicalSproutToolName ?? toolName, - sproutToolName: canonicalSproutToolName, + toolName: canonicalBuzzToolName ?? toolName, + buzzToolName: canonicalBuzzToolName, status, args, result, @@ -380,7 +380,7 @@ export function processTranscriptEvent( `tool:${ch}:${toolId}`, identity.title, identity.toolName, - identity.sproutToolName, + identity.buzzToolName, normalizeToolStatus(asString(update.status) ?? "executing"), extractToolArgs(update), extractToolResult(update), @@ -399,7 +399,7 @@ export function processTranscriptEvent( `tool:${ch}:${toolId}`, identity.title, identity.toolName, - identity.sproutToolName, + identity.buzzToolName, status, extractToolArgs(update), extractToolResult(update), diff --git a/desktop/src/features/agents/ui/agentSessionTranscriptHelpers.test.mjs b/desktop/src/features/agents/ui/agentSessionTranscriptHelpers.test.mjs index 4485df6fc..e84af1468 100644 --- a/desktop/src/features/agents/ui/agentSessionTranscriptHelpers.test.mjs +++ b/desktop/src/features/agents/ui/agentSessionTranscriptHelpers.test.mjs @@ -25,8 +25,8 @@ test("parsePromptText returns the empty/Prompt fallback for whitespace-only inpu test("parsePromptText wraps header-less free text in a single Prompt section", () => { // Free text with no `[header]` becomes one "Prompt" section. Since no - // section is a "Sprout event", there is no event content to surface, so - // userText is empty and the title falls through to "Sprout event". + // section is a "Buzz event", there is no event content to surface, so + // userText is empty and the title falls through to "Buzz event". const result = parsePromptText("just some free text"); assert.deepEqual( result.sections.map((s) => s.title), @@ -34,18 +34,18 @@ test("parsePromptText wraps header-less free text in a single Prompt section", ( ); assert.equal(result.sections[0].body, "just some free text"); assert.equal(result.userText, ""); - assert.equal(result.userTitle, "Sprout event"); + assert.equal(result.userTitle, "Buzz event"); assert.equal(result.userPubkey, null); }); -// --- parsePromptText: Sprout event section --- +// --- parsePromptText: Buzz event section --- test("parsePromptText extracts content, hex pubkey, and a title-cased kind", () => { const text = [ "[System]", "system preamble here", "", - "[Sprout event: @mention]", + "[Buzz event: @mention]", "Channel: demo", `From: Wes (hex: ${HEX})`, "Content: hello @Brain please look", @@ -61,13 +61,13 @@ test("parsePromptText extracts content, hex pubkey, and a title-cased kind", () // Both headers become sections. assert.deepEqual( result.sections.map((s) => s.title), - ["System", "Sprout event: @mention"], + ["System", "Buzz event: @mention"], ); }); test("parsePromptText lowercases the extracted hex pubkey", () => { const text = [ - "[Sprout event: dm]", + "[Buzz event: dm]", `From: Someone (hex: ${HEX_UPPER})`, "Content: hi", ].join("\n"); @@ -77,7 +77,7 @@ test("parsePromptText lowercases the extracted hex pubkey", () => { }); test("parsePromptText yields a null pubkey when From has no hex", () => { - const text = ["[Sprout event: note]", "From: Someone", "Content: hi"].join( + const text = ["[Buzz event: note]", "From: Someone", "Content: hi"].join( "\n", ); @@ -87,10 +87,10 @@ test("parsePromptText yields a null pubkey when From has no hex", () => { assert.equal(result.userTitle, "Note"); }); -test("parsePromptText defaults the title to 'Sprout event' when no kind is present", () => { - const text = ["[Sprout event]", "Content: x"].join("\n"); +test("parsePromptText defaults the title to 'Buzz event' when no kind is present", () => { + const text = ["[Buzz event]", "Content: x"].join("\n"); const result = parsePromptText(text); - assert.equal(result.userTitle, "Sprout event"); + assert.equal(result.userTitle, "Buzz event"); }); test("parsePromptText leading text before a header becomes a Prompt section", () => { diff --git a/desktop/src/features/agents/ui/agentSessionTranscriptHelpers.ts b/desktop/src/features/agents/ui/agentSessionTranscriptHelpers.ts index 4a69afbb0..00c13c056 100644 --- a/desktop/src/features/agents/ui/agentSessionTranscriptHelpers.ts +++ b/desktop/src/features/agents/ui/agentSessionTranscriptHelpers.ts @@ -1,6 +1,6 @@ import type { ObserverEvent, PromptSection } from "./agentSessionTypes"; import { - findSproutToolName, + findBuzzToolName, isGenericToolTitle, normalizeToolName, } from "./agentSessionToolCatalog"; @@ -29,9 +29,10 @@ export function parsePromptText(text: string): { }; } - const eventSection = sections.find((section) => - section.title.toLowerCase().startsWith("sprout event"), - ); + const eventSection = sections.find((section) => { + const title = section.title.toLowerCase(); + return title.startsWith("buzz event") || title.startsWith("buzz event"); + }); const eventContent = eventSection ? extractEventContent(eventSection.body) : ""; @@ -43,7 +44,7 @@ export function parsePromptText(text: string): { return { sections, userText: eventContent, - userTitle: eventKind ? titleCase(eventKind) : "Sprout event", + userTitle: eventKind ? titleCase(eventKind) : "Buzz event", userPubkey: eventAuthorPubkey, }; } @@ -144,14 +145,14 @@ export function extractToolArgs( export function extractToolIdentity(update: Record): { title: string; toolName: string; - sproutToolName: string | null; + buzzToolName: string | null; } { const candidates = collectToolNameCandidates(update); const knownName = candidates - .map((candidate) => findSproutToolName(candidate, true)) + .map((candidate) => findBuzzToolName(candidate, true)) .find((candidate): candidate is string => Boolean(candidate)) ?? - findSproutToolName(JSON.stringify(update), false); + findBuzzToolName(JSON.stringify(update), false); const firstSpecific = candidates.find( (candidate) => !isGenericToolTitle(candidate), ); @@ -160,7 +161,7 @@ export function extractToolIdentity(update: Record): { return { title, toolName: knownName ?? normalizeToolName(firstSpecific ?? title), - sproutToolName: knownName, + buzzToolName: knownName, }; } diff --git a/desktop/src/features/agents/ui/agentSessionTypes.ts b/desktop/src/features/agents/ui/agentSessionTypes.ts index 381fa4d6d..2ff4ea305 100644 --- a/desktop/src/features/agents/ui/agentSessionTypes.ts +++ b/desktop/src/features/agents/ui/agentSessionTypes.ts @@ -60,7 +60,7 @@ export type TranscriptItem = type: "tool"; title: string; toolName: string; - sproutToolName: string | null; + buzzToolName: string | null; status: ToolStatus; args: Record; result: string; @@ -76,7 +76,7 @@ export type PromptSection = { body: string; }; -export type SproutToolInfo = { +export type BuzzToolInfo = { icon: LucideIcon; label: string; tone: "read" | "write" | "admin"; diff --git a/desktop/src/features/agents/usePreventSleep.ts b/desktop/src/features/agents/usePreventSleep.ts index ff5797b2d..2ac5dfbd1 100644 --- a/desktop/src/features/agents/usePreventSleep.ts +++ b/desktop/src/features/agents/usePreventSleep.ts @@ -5,7 +5,7 @@ import { listen } from "@tauri-apps/api/event"; // Intentionally not scoped per-pubkey — multi-user desktop is rare and the // setting applies to the machine's sleep behavior regardless of account. -const STORAGE_KEY = "sprout-prevent-sleep"; +const STORAGE_KEY = "buzz-prevent-sleep"; function readPreference(): boolean { return window.localStorage.getItem(STORAGE_KEY) === "true"; diff --git a/desktop/src/features/channels/isDmNotifiableKind.test.mjs b/desktop/src/features/channels/isDmNotifiableKind.test.mjs index b51b99c77..ebea80b13 100644 --- a/desktop/src/features/channels/isDmNotifiableKind.test.mjs +++ b/desktop/src/features/channels/isDmNotifiableKind.test.mjs @@ -6,7 +6,7 @@ import { isDmNotifiableKind } from "./isDmNotifiableKind.ts"; // Regression guard for the phantom-DM-notification bug: when kind:5 deletes // gained an `h` tag, they started matching the live DM subscription. Without // this gate, deleting a DM message fires a "New message" toast on the other -// side. Reactions (7), Sprout-native deletes (9005), edits (40003), diffs +// side. Reactions (7), Buzz-native deletes (9005), edits (40003), diffs // (40008), and system messages (40099) hit the same subscription and must // also be filtered. @@ -20,11 +20,7 @@ test("human-visible message kinds fire DM notifications", () => { test("non-message kinds do NOT fire DM notifications", () => { assert.equal(isDmNotifiableKind(5), false, "kind:5 NIP-09 deletion"); assert.equal(isDmNotifiableKind(7), false, "kind:7 reaction"); - assert.equal( - isDmNotifiableKind(9005), - false, - "kind:9005 Sprout-native delete", - ); + assert.equal(isDmNotifiableKind(9005), false, "kind:9005 Buzz-native delete"); assert.equal(isDmNotifiableKind(40003), false, "kind:40003 message edit"); assert.equal(isDmNotifiableKind(40008), false, "kind:40008 message diff"); assert.equal(isDmNotifiableKind(40099), false, "kind:40099 system message"); diff --git a/desktop/src/features/channels/readState/readStateFormat.ts b/desktop/src/features/channels/readState/readStateFormat.ts index 7c3b5d58e..6ce0d69c5 100644 --- a/desktop/src/features/channels/readState/readStateFormat.ts +++ b/desktop/src/features/channels/readState/readStateFormat.ts @@ -11,15 +11,15 @@ export const READ_STATE_HORIZON_SECONDS = 7 * 24 * 60 * 60; const MAX_CONTEXTS = 10_000; export function localReadStateKey(pubkey: string): string { - return `sprout.channel-read-state.v2:${pubkey}`; + return `buzz.channel-read-state.v2:${pubkey}`; } export function localPublishableContextKey(pubkey: string): string { - return `sprout.channel-read-state.publishable.v1:${pubkey}`; + return `buzz.channel-read-state.publishable.v1:${pubkey}`; } export function localSourceCreatedAtKey(pubkey: string): string { - return `sprout.channel-read-state.source-created-at.v1:${pubkey}`; + return `buzz.channel-read-state.source-created-at.v1:${pubkey}`; } export function isPlainRecord( diff --git a/desktop/src/features/channels/readState/readStateManager.ts b/desktop/src/features/channels/readState/readStateManager.ts index b04bb0327..c777e3694 100644 --- a/desktop/src/features/channels/readState/readStateManager.ts +++ b/desktop/src/features/channels/readState/readStateManager.ts @@ -20,8 +20,8 @@ import { writeStoredReadState, } from "@/features/channels/readState/readStateStorage"; -const CLIENT_ID_KEY_PREFIX = "sprout.nip-rs.client-id"; -const SLOT_ID_KEY_PREFIX = "sprout.nip-rs.slot-id"; +const CLIENT_ID_KEY_PREFIX = "buzz.nip-rs.client-id"; +const SLOT_ID_KEY_PREFIX = "buzz.nip-rs.slot-id"; const DEBOUNCE_MS = 5_000; function generateHex(bytes: number): string { diff --git a/desktop/src/features/channels/ui/BotActivityBar.tsx b/desktop/src/features/channels/ui/BotActivityBar.tsx index 54a19c6e9..4111848df 100644 --- a/desktop/src/features/channels/ui/BotActivityBar.tsx +++ b/desktop/src/features/channels/ui/BotActivityBar.tsx @@ -28,7 +28,7 @@ const HEADLINE_ROTATION_MS = 2200; function getActivityHeadline(item: TranscriptItem): string | null { if (item.type === "tool") { - return formatToolTitle(item.sproutToolName ?? item.toolName, item.title); + return formatToolTitle(item.buzzToolName ?? item.toolName, item.title); } if (item.type === "message") { diff --git a/desktop/src/features/channels/ui/RightAuxiliaryPane.tsx b/desktop/src/features/channels/ui/RightAuxiliaryPane.tsx index c862c7c5d..6dc74b073 100644 --- a/desktop/src/features/channels/ui/RightAuxiliaryPane.tsx +++ b/desktop/src/features/channels/ui/RightAuxiliaryPane.tsx @@ -23,7 +23,7 @@ export function RightAuxiliaryPane({ }: RightAuxiliaryPaneProps) { return (