mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
217 lines
7.0 KiB
Bash
Executable File
217 lines
7.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Build the Sprout Agent Bundle — a tarball containing the three binaries
|
|
# needed to run a Sprout agent end-to-end:
|
|
#
|
|
# sprout-acp ACP harness
|
|
# sprout-agent ACP-compliant agent (spawns MCP, calls LLMs)
|
|
# sprout-dev-mcp Developer MCP server (multicall: rg/tree/sprout/git-*)
|
|
#
|
|
# Usage:
|
|
# ./scripts/build-agent-bundle.sh [version] [target]
|
|
#
|
|
# Environment overrides:
|
|
# TARGET cross-compile target (defaults to host)
|
|
# USE_CROSS=1 use `cross` instead of `cargo` for the build
|
|
# SKIP_BUILD=1 skip the cargo/cross build (use prebuilt binaries
|
|
# already present in target/[<target>/]release)
|
|
# ARCHIVE_BASENAME override the archive basename (sans .tar.gz). Useful
|
|
# for rolling releases where the asset filename should
|
|
# be stable across builds (e.g. `sprout-agent-bundle-
|
|
# <target>`). Defaults to
|
|
# `sprout-agent-bundle-<version>-<target>`.
|
|
# DIST_DIR output directory (default: dist)
|
|
#
|
|
# Output:
|
|
# ${DIST_DIR}/${ARCHIVE_BASENAME}.tar.gz
|
|
# ${DIST_DIR}/${ARCHIVE_BASENAME}.tar.gz.sha256
|
|
#
|
|
# The tarball contains:
|
|
# sprout-acp
|
|
# sprout-agent
|
|
# sprout-dev-mcp
|
|
# README.md
|
|
# bundle.json { version, git_sha, target, binaries: [{name, sha256, size}] }
|
|
|
|
set -euo pipefail
|
|
|
|
VERSION="${1:-${VERSION:-0.0.0-dev}}"
|
|
HOST_TARGET="$(rustc -vV | sed -n 's|host: ||p')"
|
|
TARGET="${2:-${TARGET:-$HOST_TARGET}}"
|
|
DIST_DIR="${DIST_DIR:-dist}"
|
|
|
|
# Resolve git SHA (best effort — works in CI checkout and local clones).
|
|
if GIT_SHA="$(git rev-parse HEAD 2>/dev/null)"; then
|
|
:
|
|
else
|
|
GIT_SHA="unknown"
|
|
fi
|
|
|
|
BINARIES=(sprout-acp sprout-agent sprout-dev-mcp)
|
|
|
|
echo "==> Building Sprout Agent Bundle v${VERSION} for ${TARGET}"
|
|
echo " git_sha=${GIT_SHA}"
|
|
echo " binaries=${BINARIES[*]}"
|
|
|
|
# Pick build driver. `cross` is required for cross-compilation in CI;
|
|
# for host builds we use plain `cargo` so contributors don't need Docker.
|
|
if [[ "${USE_CROSS:-0}" == "1" ]] || [[ "$TARGET" != "$HOST_TARGET" ]]; then
|
|
if ! command -v cross >/dev/null 2>&1; then
|
|
echo "error: cross-compiling to $TARGET requires \`cross\` (install: cargo install cross --version 0.2.5)" >&2
|
|
exit 1
|
|
fi
|
|
BUILDER=(cross build --release --target "$TARGET")
|
|
BIN_DIR="target/${TARGET}/release"
|
|
else
|
|
BUILDER=(cargo build --release)
|
|
BIN_DIR="target/release"
|
|
fi
|
|
|
|
PKG_ARGS=()
|
|
for bin in "${BINARIES[@]}"; do
|
|
PKG_ARGS+=(-p "$bin")
|
|
done
|
|
|
|
if [[ "${SKIP_BUILD:-0}" == "1" ]]; then
|
|
echo " (SKIP_BUILD=1 set — expecting prebuilt binaries in ${BIN_DIR}/)"
|
|
else
|
|
"${BUILDER[@]}" "${PKG_ARGS[@]}"
|
|
fi
|
|
|
|
# Verify all binaries exist.
|
|
for bin in "${BINARIES[@]}"; do
|
|
if [[ ! -f "${BIN_DIR}/${bin}" ]]; then
|
|
echo "error: ${BIN_DIR}/${bin} not found after build" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# Stage into a tempdir.
|
|
mkdir -p "${DIST_DIR}"
|
|
STAGING="$(mktemp -d)"
|
|
trap 'rm -rf "${STAGING}"' EXIT
|
|
|
|
# sha256 helper: prefer sha256sum (linux), fall back to shasum -a 256 (macos).
|
|
sha256_of() {
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
sha256sum "$1" | awk '{print $1}'
|
|
else
|
|
shasum -a 256 "$1" | awk '{print $1}'
|
|
fi
|
|
}
|
|
|
|
# Copy + strip binaries, collect manifest entries.
|
|
MANIFEST_ENTRIES=()
|
|
for bin in "${BINARIES[@]}"; do
|
|
cp "${BIN_DIR}/${bin}" "${STAGING}/${bin}"
|
|
chmod 0755 "${STAGING}/${bin}"
|
|
# Best-effort strip. `cross` images include the cross-target strip; on host
|
|
# we use the system strip. Skip silently if unavailable (e.g. cross-arch
|
|
# local builds on macOS).
|
|
if command -v strip >/dev/null 2>&1; then
|
|
strip "${STAGING}/${bin}" 2>/dev/null || true
|
|
fi
|
|
sha="$(sha256_of "${STAGING}/${bin}")"
|
|
size="$(wc -c < "${STAGING}/${bin}" | tr -d ' ')"
|
|
MANIFEST_ENTRIES+=("{\"name\":\"${bin}\",\"sha256\":\"${sha}\",\"size\":${size}}")
|
|
done
|
|
|
|
# bundle.json — machine-readable manifest.
|
|
ENTRIES_JSON="$(IFS=,; echo "${MANIFEST_ENTRIES[*]}")"
|
|
cat > "${STAGING}/bundle.json" <<JSON
|
|
{
|
|
"name": "sprout-agent-bundle",
|
|
"version": "${VERSION}",
|
|
"git_sha": "${GIT_SHA}",
|
|
"target": "${TARGET}",
|
|
"binaries": [${ENTRIES_JSON}]
|
|
}
|
|
JSON
|
|
|
|
# README — human-readable.
|
|
cat > "${STAGING}/README.md" <<'EOF'
|
|
# Sprout Agent Bundle
|
|
|
|
Linux build of the three binaries needed to run a Sprout agent end-to-end:
|
|
|
|
- `sprout-acp` — ACP harness that bridges Sprout channel events to an
|
|
ACP-compliant agent over stdio.
|
|
- `sprout-agent` — ACP-compliant agent (spawns MCP servers, calls LLMs).
|
|
- `sprout-dev-mcp` — Developer MCP server (shell, str_replace, todo) and
|
|
multicall entrypoint for `rg`, `tree`, `sprout`, `git-credential-nostr`,
|
|
`git-sign-nostr`.
|
|
|
|
See `bundle.json` for binary SHA-256s, sizes, and the source git SHA.
|
|
|
|
## Install
|
|
|
|
```bash
|
|
tar -xzf sprout-agent-bundle-*.tar.gz -C /opt/sprout-agent
|
|
export PATH="/opt/sprout-agent:$PATH"
|
|
```
|
|
|
|
## Configure
|
|
|
|
```bash
|
|
# Agent provider
|
|
export SPROUT_AGENT_PROVIDER=anthropic # or openai
|
|
export ANTHROPIC_API_KEY=sk-...
|
|
export ANTHROPIC_MODEL=claude-sonnet-4-20250514
|
|
|
|
# Nostr identity (shared by sprout-acp, git auth, signing, and sprout CLI)
|
|
export NOSTR_PRIVATE_KEY=nsec1...
|
|
export SPROUT_PRIVATE_KEY="$NOSTR_PRIVATE_KEY"
|
|
export SPROUT_RELAY_URL=https://your-relay.example.com
|
|
```
|
|
|
|
## Git Integration
|
|
|
|
When `NOSTR_PRIVATE_KEY` is set, `sprout-dev-mcp` automatically configures
|
|
git to use nostr-based credential auth and commit signing for all shell
|
|
commands. This is ephemeral (session-scoped via `GIT_CONFIG_*` env vars) —
|
|
your persistent git config is never modified.
|
|
|
|
The nostr credential helper is additive: it silently declines non-Sprout
|
|
remotes so git falls through to your system credential helpers for GitHub,
|
|
GitLab, etc. `NOSTR_PRIVATE_KEY` is written to a 0600 keyfile and removed
|
|
from the process environment — shell commands cannot read it from env.
|
|
|
|
## Multicall Binary
|
|
|
|
`sprout-dev-mcp` is a multicall binary. When symlinked/invoked as:
|
|
|
|
- `rg` — ripgrep-compatible search
|
|
- `tree` — directory tree with line counts
|
|
- `sprout` — Sprout relay CLI
|
|
- `git-credential-nostr` — NIP-98 git credential helper
|
|
- `git-sign-nostr` — NIP-GS git commit/tag signing
|
|
|
|
…it dispatches to the corresponding subcommand. The installer is free to
|
|
symlink these names next to `sprout-dev-mcp` on the PATH.
|
|
EOF
|
|
|
|
# Tar.
|
|
ARCHIVE_BASENAME="${ARCHIVE_BASENAME:-sprout-agent-bundle-${VERSION}-${TARGET}}"
|
|
ARCHIVE_NAME="${ARCHIVE_BASENAME}.tar.gz"
|
|
ARCHIVE_PATH="${DIST_DIR}/${ARCHIVE_NAME}"
|
|
|
|
# Deterministic-ish tar: sorted entries, no owner/group info.
|
|
tar \
|
|
--sort=name \
|
|
--owner=0 --group=0 --numeric-owner \
|
|
-czf "${ARCHIVE_PATH}" \
|
|
-C "${STAGING}" \
|
|
. 2>/dev/null || \
|
|
tar -czf "${ARCHIVE_PATH}" -C "${STAGING}" . # fallback for BSD tar (macOS)
|
|
|
|
# Sidecar checksum.
|
|
sha256_of "${ARCHIVE_PATH}" > "${ARCHIVE_PATH}.sha256"
|
|
# Pretty-print the form `<sha> <filename>` like sha256sum -c expects.
|
|
echo "$(cat "${ARCHIVE_PATH}.sha256") ${ARCHIVE_NAME}" > "${ARCHIVE_PATH}.sha256"
|
|
|
|
echo ""
|
|
echo "==> Built: ${ARCHIVE_PATH}"
|
|
ls -lh "${ARCHIVE_PATH}" "${ARCHIVE_PATH}.sha256"
|
|
echo ""
|
|
echo "==> bundle.json:"
|
|
sed 's/^/ /' "${STAGING}/bundle.json"
|