Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
name: Sprout Agent Bundle
name: Sprig

# Builds and publishes the Sprout Agent Bundle — a Linux tarball containing
# the three binaries an external service (e.g. sprout-backend-blox) needs to
# run a Sprout agent end-to-end:
#
# Builds and publishes Sprig — one deploy-anywhere Linux multicall binary for:
# sprout-acp ACP harness that bridges Sprout events to the LLM agent
# sprout-agent ACP-compliant agent (spawns MCP, calls LLMs)
# sprout-dev-mcp Developer MCP server (multicall: rg, tree, sprout,
Expand All @@ -13,14 +10,14 @@ name: Sprout Agent Bundle
# musl so the tarball runs on any modern Linux without libc surprises).
#
# Triggers:
# - push to main → updates rolling `sprout-agent-bundle-latest` release
# - tag `sprout-agent-bundle-v*` → versioned release
# - workflow_dispatch → manual canary build (no release publish)
# - push to main → updates rolling `sprig-latest` release
# - tag `sprig-v*` → versioned release
# - workflow_dispatch → manual canary build (no release publish unless asked)

on:
push:
branches: [main]
tags: ["sprout-agent-bundle-v*"]
tags: ["sprig-v*"]
workflow_dispatch:
inputs:
publish:
Expand Down Expand Up @@ -59,16 +56,11 @@ jobs:
run: |
set -euo pipefail
WORKSPACE_VERSION=$(cargo metadata --no-deps --format-version=1 \
| jq -r '.workspace_default_members[0] as $m | .packages[] | select(.id==$m) | .version')
if [[ -z "$WORKSPACE_VERSION" || "$WORKSPACE_VERSION" == "null" ]]; then
# Fallback: read sprout-acp's version directly.
WORKSPACE_VERSION=$(cargo metadata --no-deps --format-version=1 \
| jq -r '.packages[] | select(.name=="sprout-acp") | .version')
fi
| jq -r '.packages[] | select(.name=="sprig") | .version')

REF="${GITHUB_REF#refs/tags/}"
if [[ "$GITHUB_REF" == refs/tags/sprout-agent-bundle-v* ]]; then
VERSION="${REF#sprout-agent-bundle-v}"
if [[ "$GITHUB_REF" == refs/tags/sprig-v* ]]; then
VERSION="${REF#sprig-v}"
CHANNEL="tag"
else
SHORT_SHA="${GITHUB_SHA::7}"
Expand All @@ -82,7 +74,7 @@ jobs:
} >> "$GITHUB_OUTPUT"
echo "Resolved version=$VERSION channel=$CHANNEL"

- name: Build & package bundle
- name: Build & package Sprig
id: pkg
env:
TARGET: ${{ matrix.target }}
Expand All @@ -91,17 +83,13 @@ jobs:
GIT_SHA: ${{ github.sha }}
run: |
set -euo pipefail
# Rolling releases use stable, version-less filenames so the
# asset overwrites cleanly on every push to main. Tagged
# releases keep the version in the filename for traceability.
# The git SHA + version always live inside bundle.json.
if [[ "$CHANNEL" == "tag" ]]; then
ARCHIVE_BASENAME="sprout-agent-bundle-${VERSION}-${TARGET}"
ARCHIVE_BASENAME="sprig-${VERSION}-${TARGET}"
else
ARCHIVE_BASENAME="sprout-agent-bundle-${TARGET}"
ARCHIVE_BASENAME="sprig-${TARGET}"
fi
ARCHIVE_BASENAME="$ARCHIVE_BASENAME" \
./scripts/build-agent-bundle.sh "$VERSION" "$TARGET"
./scripts/build-sprig.sh "$VERSION" "$TARGET"
ARCHIVE="dist/${ARCHIVE_BASENAME}.tar.gz"
test -f "$ARCHIVE"
echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT"
Expand All @@ -110,7 +98,7 @@ jobs:
- name: Upload workflow artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: sprout-agent-bundle-${{ matrix.target }}
name: sprig-${{ matrix.target }}
path: |
${{ steps.pkg.outputs.archive }}
${{ steps.pkg.outputs.archive }}.sha256
Expand All @@ -130,11 +118,11 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Download all bundle artifacts
- name: Download all Sprig artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
path: dist
pattern: sprout-agent-bundle-*
pattern: sprig-*
merge-multiple: true

- name: List assets
Expand All @@ -147,14 +135,10 @@ jobs:
SHA: ${{ github.sha }}
run: |
set -euo pipefail
TAG="sprout-agent-bundle-latest"
TITLE="Sprout Agent Bundle (rolling)"
NOTES="Rolling Linux build of the Sprout Agent Bundle (sprout-acp + sprout-agent + sprout-dev-mcp), tracking \`main\` (\`${SHA}\`)."

# Force-move the underlying git tag to the current SHA before
# we touch the release. `gh release edit` updates release
# metadata but does *not* move the tag, so without this the
# tag would stick to whatever SHA first created the release.
TAG="sprig-latest"
TITLE="Sprig (rolling)"
NOTES="Rolling Linux build of Sprig (all-in-one sprout-acp + sprout-agent + sprout-dev-mcp), tracking \`main\` (\`${SHA}\`)."

if gh api "repos/${REPO}/git/refs/tags/${TAG}" >/dev/null 2>&1; then
gh api -X PATCH "repos/${REPO}/git/refs/tags/${TAG}" \
-f sha="${SHA}" -F force=true >/dev/null
Expand All @@ -163,7 +147,6 @@ jobs:
-f ref="refs/tags/${TAG}" -f sha="${SHA}" >/dev/null
fi

# Create the release if it doesn't exist; otherwise reuse it.
if ! gh release view "$TAG" >/dev/null 2>&1; then
gh release create "$TAG" \
--prerelease \
Expand All @@ -177,34 +160,31 @@ jobs:
--title "$TITLE" \
--notes "$NOTES"
fi
# Asset filenames are stable for rolling builds (no version
# in filename — see the package step), so --clobber overwrites
# them in place. No stale-asset accumulation.
gh release upload "$TAG" dist/* --clobber

publish-tag:
name: Publish tagged release
needs: build
if: startsWith(github.ref, 'refs/tags/sprout-agent-bundle-v')
if: startsWith(github.ref, 'refs/tags/sprig-v')
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Download all bundle artifacts
- name: Download all Sprig artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
path: dist
pattern: sprout-agent-bundle-*
pattern: sprig-*
merge-multiple: true

- name: Resolve tag version
id: ver
run: |
REF="${GITHUB_REF#refs/tags/}"
VERSION="${REF#sprout-agent-bundle-v}"
VERSION="${REF#sprig-v}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag=$REF" >> "$GITHUB_OUTPUT"

Expand All @@ -216,6 +196,6 @@ jobs:
run: |
set -euo pipefail
gh release create "$TAG" \
--title "Sprout Agent Bundle v${VERSION}" \
--notes "Sprout Agent Bundle v${VERSION} — Linux builds of sprout-acp + sprout-agent + sprout-dev-mcp." \
--title "Sprig v${VERSION}" \
--notes "Sprig v${VERSION} — Linux all-in-one builds of sprout-acp + sprout-agent + sprout-dev-mcp." \
dist/*
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"crates/sprout-mcp",
"crates/sprout-acp",
"crates/sprout-agent",
"crates/sprig",
"crates/sprout-proxy",
"crates/sprout-test-client",
"crates/sprout-admin",
Expand Down Expand Up @@ -125,3 +126,16 @@ sprout-sdk = { path = "crates/sprout-sdk" }
[profile.ci]
inherits = "release"
lto = false

# Sprig profile — optimized for deploy-anywhere Sprig release artifacts.
# Sprig is distributed over the network and installed on fresh hosts, so binary
# size matters more than compile speed here. Keep this separate from the normal
# `release` profile so desktop/dev release builds do not inherit the slower
# size-focused settings unless they opt in explicitly.
[profile.sprig]
inherits = "release"
opt-level = "z"
lto = "fat"
codegen-units = 1
panic = "abort"
strip = true
17 changes: 17 additions & 0 deletions crates/sprig/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "sprig"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
description = "All-in-one Sprout ACP harness, agent, and developer MCP"

[[bin]]
name = "sprig"
path = "src/main.rs"

[dependencies]
sprout-acp = { path = "../sprout-acp" }
sprout-agent = { path = "../sprout-agent" }
sprout-dev-mcp = { path = "../sprout-dev-mcp" }
53 changes: 53 additions & 0 deletions crates/sprig/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
fn main() {
if let Err(e) = dispatch() {
eprintln!("{e}");
std::process::exit(1);
}
}

fn dispatch() -> Result<(), String> {
let argv0 = std::env::args().next().unwrap_or_default();
let cmd = std::path::Path::new(&argv0)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("")
.to_ascii_lowercase();

match cmd.as_str() {
"sprout-acp" => sprout_acp::run().map_err(|e| e.to_string()),
"sprout-agent" => sprout_agent::run().map_err(|e| e.to_string()),
"sprig" => match std::env::args().nth(1).as_deref() {
Some("-V") | Some("--version") => {
println!("sprig {}", env!("CARGO_PKG_VERSION"));
Ok(())
}
Some("-h") | Some("--help") | None => {
print_usage();
if std::env::args().len() <= 1 {
Err("error: invoke Sprig via a personality symlink".into())
} else {
Ok(())
}
}
Some(other) => {
print_usage();
Err(format!(
"error: unknown Sprig option or personality: {other}"
))
}
},
// sprout-dev-mcp also handles its own multicall names: rg, tree,
// sprout, git-credential-nostr, and git-sign-nostr.
_ => sprout_dev_mcp::run().map_err(|e| e.to_string()),
}
}

fn print_usage() {
println!(
"Sprig — all-in-one Sprout ACP harness, agent, and developer MCP\n\n\
Sprig is a multicall binary. Invoke it through one of the personality names:\n\n\
sprout-acp ACP harness\n sprout-agent ACP-compliant agent\n sprout-dev-mcp Developer MCP server\n\n\
Developer MCP helper names are also supported: rg, tree, sprout, git-credential-nostr, git-sign-nostr.\n\n\
Installers can create links with:\n ln -s sprig sprout-acp\n ln -s sprig sprout-agent\n ln -s sprig sprout-dev-mcp"
);
}
4 changes: 4 additions & 0 deletions crates/sprout-acp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ license.workspace = true
repository.workspace = true
description = "ACP harness that bridges Sprout events to AI agents"

[lib]
name = "sprout_acp"
path = "src/lib.rs"

[[bin]]
name = "sprout-acp"
path = "src/main.rs"
Expand Down
Loading
Loading