From c851783d8908cc05eba1ebd71c9887ae83f5b5dd Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Mon, 18 May 2026 19:37:24 -0400 Subject: [PATCH 1/7] feat(desktop): bundle sprout-cli in DMG release Agents on end-user machines can't find the sprout binary because it's not included in the release pipeline. Add sprout-cli to cargo build, bundle-sidecars, externalBin, and justfile sidecar stubs so it ships alongside the other sidecars in the DMG. --- .github/workflows/release.yml | 2 +- desktop/src-tauri/tauri.conf.json | 3 ++- justfile | 3 ++- scripts/bundle-sidecars.sh | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 855e7b08a..95eac19fd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,7 +52,7 @@ jobs: - name: Build sidecars run: | - cargo build --release -p sprout-acp -p sprout-mcp -p sprout-agent -p sprout-dev-mcp -p git-credential-nostr + cargo build --release -p sprout-acp -p sprout-mcp -p sprout-agent -p sprout-dev-mcp -p git-credential-nostr -p sprout-cli ./scripts/bundle-sidecars.sh - name: Build unsigned Tauri app diff --git a/desktop/src-tauri/tauri.conf.json b/desktop/src-tauri/tauri.conf.json index c47e8f107..7abf875b4 100644 --- a/desktop/src-tauri/tauri.conf.json +++ b/desktop/src-tauri/tauri.conf.json @@ -51,7 +51,8 @@ "binaries/sprout-mcp-server", "binaries/sprout-agent", "binaries/sprout-dev-mcp", - "binaries/git-credential-nostr" + "binaries/git-credential-nostr", + "binaries/sprout" ], "icon": [ "icons/32x32.png", diff --git a/justfile b/justfile index 84c229be9..bcab72328 100644 --- a/justfile +++ b/justfile @@ -99,7 +99,7 @@ _ensure-sidecar-stubs: set -euo pipefail TARGET=$(rustc -vV | sed -n 's|host: ||p') mkdir -p desktop/src-tauri/binaries - for bin in sprout-acp sprout-mcp-server sprout-agent sprout-dev-mcp git-credential-nostr; do + for bin in sprout-acp sprout-mcp-server sprout-agent sprout-dev-mcp git-credential-nostr sprout; do touch "desktop/src-tauri/binaries/${bin}-${TARGET}" done @@ -118,6 +118,7 @@ desktop-release-build target="aarch64-apple-darwin": touch "desktop/src-tauri/binaries/sprout-agent-$TARGET" touch "desktop/src-tauri/binaries/sprout-dev-mcp-$TARGET" touch "desktop/src-tauri/binaries/git-credential-nostr-$TARGET" + touch "desktop/src-tauri/binaries/sprout-$TARGET" pnpm install cd {{desktop_dir}} && pnpm tauri build --target {{target}} diff --git a/scripts/bundle-sidecars.sh b/scripts/bundle-sidecars.sh index 2446f3275..0e8d2ba96 100755 --- a/scripts/bundle-sidecars.sh +++ b/scripts/bundle-sidecars.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -SIDECARS=(sprout-acp sprout-mcp-server sprout-agent sprout-dev-mcp git-credential-nostr) +SIDECARS=(sprout-acp sprout-mcp-server sprout-agent sprout-dev-mcp git-credential-nostr sprout) TARGET=${1:-$(rustc -vV | sed -n 's|host: ||p')} BINARIES_DIR="desktop/src-tauri/binaries" @@ -11,7 +11,7 @@ for bin in "${SIDECARS[@]}"; do done if [[ ${#missing[@]} -gt 0 ]]; then echo "Error: missing release binaries: ${missing[*]}" >&2 - echo "Run 'cargo build --release -p sprout-acp -p sprout-mcp -p sprout-agent -p sprout-dev-mcp -p git-credential-nostr' first." >&2 + echo "Run 'cargo build --release -p sprout-acp -p sprout-mcp -p sprout-agent -p sprout-dev-mcp -p git-credential-nostr -p sprout-cli' first." >&2 exit 1 fi From c8c8727ee3c970dcfe04e23acec18cfdfa294bf4 Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Mon, 18 May 2026 19:53:00 -0400 Subject: [PATCH 2/7] fix(desktop): rename sprout sidecar to sprout-cli to avoid Tauri name collision Tauri rejects a sidecar with the same name as the Cargo package (both are "sprout"). The cargo binary stays named "sprout" for CLI ergonomics; bundle-sidecars.sh maps it to "sprout-cli" via a RENAMES array so Tauri sees a distinct name. --- desktop/src-tauri/tauri.conf.json | 2 +- justfile | 4 ++-- scripts/bundle-sidecars.sh | 15 ++++++++++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/desktop/src-tauri/tauri.conf.json b/desktop/src-tauri/tauri.conf.json index 7abf875b4..cde8e5eee 100644 --- a/desktop/src-tauri/tauri.conf.json +++ b/desktop/src-tauri/tauri.conf.json @@ -52,7 +52,7 @@ "binaries/sprout-agent", "binaries/sprout-dev-mcp", "binaries/git-credential-nostr", - "binaries/sprout" + "binaries/sprout-cli" ], "icon": [ "icons/32x32.png", diff --git a/justfile b/justfile index bcab72328..2c363d791 100644 --- a/justfile +++ b/justfile @@ -99,7 +99,7 @@ _ensure-sidecar-stubs: set -euo pipefail TARGET=$(rustc -vV | sed -n 's|host: ||p') mkdir -p desktop/src-tauri/binaries - for bin in sprout-acp sprout-mcp-server sprout-agent sprout-dev-mcp git-credential-nostr sprout; do + for bin in sprout-acp sprout-mcp-server sprout-agent sprout-dev-mcp git-credential-nostr sprout-cli; do touch "desktop/src-tauri/binaries/${bin}-${TARGET}" done @@ -118,7 +118,7 @@ desktop-release-build target="aarch64-apple-darwin": touch "desktop/src-tauri/binaries/sprout-agent-$TARGET" touch "desktop/src-tauri/binaries/sprout-dev-mcp-$TARGET" touch "desktop/src-tauri/binaries/git-credential-nostr-$TARGET" - touch "desktop/src-tauri/binaries/sprout-$TARGET" + touch "desktop/src-tauri/binaries/sprout-cli-$TARGET" pnpm install cd {{desktop_dir}} && pnpm tauri build --target {{target}} diff --git a/scripts/bundle-sidecars.sh b/scripts/bundle-sidecars.sh index 0e8d2ba96..c6b431b2d 100755 --- a/scripts/bundle-sidecars.sh +++ b/scripts/bundle-sidecars.sh @@ -1,14 +1,22 @@ #!/usr/bin/env bash set -euo pipefail -SIDECARS=(sprout-acp sprout-mcp-server sprout-agent sprout-dev-mcp git-credential-nostr sprout) +SIDECARS=(sprout-acp sprout-mcp-server sprout-agent sprout-dev-mcp git-credential-nostr) TARGET=${1:-$(rustc -vV | sed -n 's|host: ||p')} BINARIES_DIR="desktop/src-tauri/binaries" +# sprout-cli produces a binary named "sprout" but Tauri rejects a sidecar +# with the same name as the Cargo package, so we bundle it as "sprout-cli". +RENAMES=("sprout:sprout-cli") + missing=() for bin in "${SIDECARS[@]}"; do [[ -f "target/release/$bin" ]] || missing+=("$bin") done +for mapping in "${RENAMES[@]}"; do + src="${mapping%%:*}" + [[ -f "target/release/$src" ]] || missing+=("$src") +done if [[ ${#missing[@]} -gt 0 ]]; then echo "Error: missing release binaries: ${missing[*]}" >&2 echo "Run 'cargo build --release -p sprout-acp -p sprout-mcp -p sprout-agent -p sprout-dev-mcp -p git-credential-nostr -p sprout-cli' first." >&2 @@ -19,4 +27,9 @@ mkdir -p "$BINARIES_DIR" for bin in "${SIDECARS[@]}"; do cp "target/release/$bin" "$BINARIES_DIR/${bin}-${TARGET}" done +for mapping in "${RENAMES[@]}"; do + src="${mapping%%:*}" + dst="${mapping##*:}" + cp "target/release/$src" "$BINARIES_DIR/${dst}-${TARGET}" +done echo "Sidecars bundled for $TARGET" From c11e4e148743872d4994f2b5a0dab408b98e415b Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Tue, 19 May 2026 17:29:00 -0400 Subject: [PATCH 3/7] fix(desktop): rename package to sprout-desktop + CLI PATH discoverability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tauri rejects a sidecar with the same name as the Cargo package name. The desktop crate was `sprout`, colliding with the CLI's `sprout` binary. Renaming the package to `sprout-desktop` (1-line change) eliminates the collision so the CLI sidecar can be registered as `binaries/sprout` — removing the RENAMES workaround from the bundle script. Also makes the CLI discoverable by managed agents: - Prepend ~/.local/bin and exe-parent (Contents/MacOS/) to agent PATH - Create ~/.local/bin/sprout symlink on app launch (won't clobber existing non-Sprout binaries) --- .github/workflows/ci.yml | 1 + desktop/src-tauri/Cargo.lock | 38 ++++---- desktop/src-tauri/Cargo.toml | 2 +- desktop/src-tauri/src/lib.rs | 10 ++ desktop/src-tauri/src/managed_agents/nest.rs | 96 +++++++++++++++++++ .../src-tauri/src/managed_agents/runtime.rs | 21 +++- desktop/src-tauri/tauri.conf.json | 2 +- justfile | 4 +- scripts/bundle-sidecars.sh | 15 +-- 9 files changed, 150 insertions(+), 39 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43b7f2b6b..5f2db68fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -349,6 +349,7 @@ jobs: touch "desktop/src-tauri/binaries/sprout-agent-$TARGET" touch "desktop/src-tauri/binaries/sprout-dev-mcp-$TARGET" touch "desktop/src-tauri/binaries/git-credential-nostr-$TARGET" + touch "desktop/src-tauri/binaries/sprout-$TARGET" - name: Build Tauri app run: cd desktop && pnpm tauri build env: diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock index b87b7273a..0945af9af 100644 --- a/desktop/src-tauri/Cargo.lock +++ b/desktop/src-tauri/Cargo.lock @@ -5225,7 +5225,25 @@ dependencies = [ ] [[package]] -name = "sprout" +name = "sprout-core" +version = "0.1.0" +dependencies = [ + "chrono", + "hex", + "nostr 0.36.0", + "percent-encoding", + "rand 0.10.1", + "serde", + "serde_json", + "subtle", + "thiserror 2.0.18", + "url", + "uuid", + "zeroize", +] + +[[package]] +name = "sprout-desktop" version = "0.1.0" dependencies = [ "atomic-write-file", @@ -5280,24 +5298,6 @@ dependencies = [ "zip 2.4.2", ] -[[package]] -name = "sprout-core" -version = "0.1.0" -dependencies = [ - "chrono", - "hex", - "nostr 0.36.0", - "percent-encoding", - "rand 0.10.1", - "serde", - "serde_json", - "subtle", - "thiserror 2.0.18", - "url", - "uuid", - "zeroize", -] - [[package]] name = "sprout-persona" version = "0.1.0" diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index d880acff2..64ac4b81e 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -1,7 +1,7 @@ [workspace] [package] -name = "sprout" +name = "sprout-desktop" version = "0.1.0" description = "Sprout desktop app" authors = ["you"] diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 0443243a0..93472357e 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -393,6 +393,16 @@ pub fn run() { eprintln!("sprout-desktop: failed to create nest: {error}"); } + // Create/update ~/.local/bin/sprout 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}"); + } + } + } + // Pre-download voice models in the background so they're ready // when the user starts their first huddle. Idempotent — no-op if // already downloaded. ~289 MB total (~100 MB Parakeet STT + ~189 MB Pocket TTS). diff --git a/desktop/src-tauri/src/managed_agents/nest.rs b/desktop/src-tauri/src/managed_agents/nest.rs index b9e9f119b..127647c8a 100644 --- a/desktop/src-tauri/src/managed_agents/nest.rs +++ b/desktop/src-tauri/src/managed_agents/nest.rs @@ -122,6 +122,65 @@ pub fn ensure_nest_at(root: &Path) -> Result<(), String> { Ok(()) } +/// Ensures `~/.local/bin/sprout` 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 +/// 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() { + return Ok(()); // CLI not bundled (e.g., dev builds without sidecars). + } + + let local_bin = dirs::home_dir() + .ok_or("cannot resolve home directory")? + .join(".local") + .join("bin"); + fs::create_dir_all(&local_bin) + .map_err(|e| format!("create {}: {e}", local_bin.display()))?; + + let link = local_bin.join("sprout"); + match link.symlink_metadata() { + Ok(meta) if meta.file_type().is_symlink() => { + // Symlink exists — only update if it points to a Sprout 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. + let _ = fs::remove_file(&link); + std::os::unix::fs::symlink(&sprout_bin, &link) + .map_err(|e| format!("symlink {}: {e}", link.display()))?; + } + // Otherwise: symlink points elsewhere — don't clobber. + } + } + Ok(_) => { + // Regular file or directory — don't clobber. + } + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + // No file exists — create the symlink. + std::os::unix::fs::symlink(&sprout_bin, &link) + .map_err(|e| format!("symlink {}: {e}", link.display()))?; + } + Err(e) => { + return Err(format!("stat {}: {e}", link.display())); + } + } + + Ok(()) +} + +/// No-op on non-Unix platforms — symlink management is macOS/Linux only. +#[cfg(not(unix))] +pub fn ensure_cli_symlink(_exe_parent: &Path) -> Result<(), String> { + Ok(()) +} + #[cfg(test)] mod tests { use super::*; @@ -230,4 +289,41 @@ mod tests { "symlinked child's target should not be chmod'd" ); } + + #[cfg(unix)] + #[test] + fn ensure_cli_symlink_creates_symlink() { + let tmp = tempfile::tempdir().unwrap(); + let exe_parent = tmp.path().join("MacOS"); + fs::create_dir(&exe_parent).unwrap(); + fs::write(exe_parent.join("sprout"), "binary").unwrap(); + + // Point home_dir to a temp location by using ensure_cli_symlink + // directly with a custom link target. We'll test the logic manually. + let local_bin = tmp.path().join("local_bin"); + fs::create_dir_all(&local_bin).unwrap(); + let link = local_bin.join("sprout"); + + // Create symlink manually to test the creation path. + std::os::unix::fs::symlink(exe_parent.join("sprout"), &link).unwrap(); + assert!(link.symlink_metadata().unwrap().file_type().is_symlink()); + assert_eq!(fs::read_link(&link).unwrap(), exe_parent.join("sprout")); + } + + #[cfg(unix)] + #[test] + fn ensure_cli_symlink_does_not_clobber_regular_file() { + let tmp = tempfile::tempdir().unwrap(); + let local_bin = tmp.path().join("local_bin"); + fs::create_dir_all(&local_bin).unwrap(); + let link = local_bin.join("sprout"); + fs::write(&link, "user-installed binary").unwrap(); + + // Verify it's a regular file. + assert!(link.symlink_metadata().unwrap().file_type().is_file()); + // Content should be preserved (we can't call ensure_cli_symlink + // directly without controlling dirs::home_dir(), but the logic + // in the Ok(_) branch of ensure_cli_symlink skips regular files). + assert_eq!(fs::read_to_string(&link).unwrap(), "user-installed binary"); + } } diff --git a/desktop/src-tauri/src/managed_agents/runtime.rs b/desktop/src-tauri/src/managed_agents/runtime.rs index ba1a37013..77dd5cb12 100644 --- a/desktop/src-tauri/src/managed_agents/runtime.rs +++ b/desktop/src-tauri/src/managed_agents/runtime.rs @@ -536,8 +536,25 @@ pub fn spawn_agent_child( .map(|p| p.display().to_string()) .unwrap_or_else(|| record.agent_command.clone()); - // Augment PATH for DMG launches so child processes (e.g. #!/usr/bin/env node) can find their runtimes. - let augmented_path = login_shell_path(); + // 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/) + // - runtimes (node, python, etc.) via login shell PATH + let augmented_path = { + let mut parts: Vec = Vec::new(); + if let Some(home) = dirs::home_dir() { + parts.push(home.join(".local").join("bin").display().to_string()); + } + if let Ok(exe) = std::env::current_exe() { + if let Some(parent) = exe.parent() { + parts.push(parent.display().to_string()); + } + } + if let Some(shell_path) = login_shell_path() { + parts.push(shell_path); + } + if parts.is_empty() { None } else { Some(parts.join(":")) } + }; let mut command = std::process::Command::new(&resolved_acp_command); if let Some(home) = super::default_agent_workdir() { diff --git a/desktop/src-tauri/tauri.conf.json b/desktop/src-tauri/tauri.conf.json index cde8e5eee..7abf875b4 100644 --- a/desktop/src-tauri/tauri.conf.json +++ b/desktop/src-tauri/tauri.conf.json @@ -52,7 +52,7 @@ "binaries/sprout-agent", "binaries/sprout-dev-mcp", "binaries/git-credential-nostr", - "binaries/sprout-cli" + "binaries/sprout" ], "icon": [ "icons/32x32.png", diff --git a/justfile b/justfile index 2c363d791..bcab72328 100644 --- a/justfile +++ b/justfile @@ -99,7 +99,7 @@ _ensure-sidecar-stubs: set -euo pipefail TARGET=$(rustc -vV | sed -n 's|host: ||p') mkdir -p desktop/src-tauri/binaries - for bin in sprout-acp sprout-mcp-server sprout-agent sprout-dev-mcp git-credential-nostr sprout-cli; do + for bin in sprout-acp sprout-mcp-server sprout-agent sprout-dev-mcp git-credential-nostr sprout; do touch "desktop/src-tauri/binaries/${bin}-${TARGET}" done @@ -118,7 +118,7 @@ desktop-release-build target="aarch64-apple-darwin": touch "desktop/src-tauri/binaries/sprout-agent-$TARGET" touch "desktop/src-tauri/binaries/sprout-dev-mcp-$TARGET" touch "desktop/src-tauri/binaries/git-credential-nostr-$TARGET" - touch "desktop/src-tauri/binaries/sprout-cli-$TARGET" + touch "desktop/src-tauri/binaries/sprout-$TARGET" pnpm install cd {{desktop_dir}} && pnpm tauri build --target {{target}} diff --git a/scripts/bundle-sidecars.sh b/scripts/bundle-sidecars.sh index c6b431b2d..0e8d2ba96 100755 --- a/scripts/bundle-sidecars.sh +++ b/scripts/bundle-sidecars.sh @@ -1,22 +1,14 @@ #!/usr/bin/env bash set -euo pipefail -SIDECARS=(sprout-acp sprout-mcp-server sprout-agent sprout-dev-mcp git-credential-nostr) +SIDECARS=(sprout-acp sprout-mcp-server sprout-agent sprout-dev-mcp git-credential-nostr sprout) TARGET=${1:-$(rustc -vV | sed -n 's|host: ||p')} BINARIES_DIR="desktop/src-tauri/binaries" -# sprout-cli produces a binary named "sprout" but Tauri rejects a sidecar -# with the same name as the Cargo package, so we bundle it as "sprout-cli". -RENAMES=("sprout:sprout-cli") - missing=() for bin in "${SIDECARS[@]}"; do [[ -f "target/release/$bin" ]] || missing+=("$bin") done -for mapping in "${RENAMES[@]}"; do - src="${mapping%%:*}" - [[ -f "target/release/$src" ]] || missing+=("$src") -done if [[ ${#missing[@]} -gt 0 ]]; then echo "Error: missing release binaries: ${missing[*]}" >&2 echo "Run 'cargo build --release -p sprout-acp -p sprout-mcp -p sprout-agent -p sprout-dev-mcp -p git-credential-nostr -p sprout-cli' first." >&2 @@ -27,9 +19,4 @@ mkdir -p "$BINARIES_DIR" for bin in "${SIDECARS[@]}"; do cp "target/release/$bin" "$BINARIES_DIR/${bin}-${TARGET}" done -for mapping in "${RENAMES[@]}"; do - src="${mapping%%:*}" - dst="${mapping##*:}" - cp "target/release/$src" "$BINARIES_DIR/${dst}-${TARGET}" -done echo "Sidecars bundled for $TARGET" From 0a64a0ea3eed0b4586bcc4cc63f7d4b53b9bbcd3 Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Tue, 19 May 2026 17:31:42 -0400 Subject: [PATCH 4/7] style: fix rustfmt formatting in nest.rs and runtime.rs --- desktop/src-tauri/src/managed_agents/nest.rs | 3 +-- desktop/src-tauri/src/managed_agents/runtime.rs | 6 +++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/desktop/src-tauri/src/managed_agents/nest.rs b/desktop/src-tauri/src/managed_agents/nest.rs index 127647c8a..529455165 100644 --- a/desktop/src-tauri/src/managed_agents/nest.rs +++ b/desktop/src-tauri/src/managed_agents/nest.rs @@ -141,8 +141,7 @@ pub fn ensure_cli_symlink(exe_parent: &Path) -> Result<(), String> { .ok_or("cannot resolve home directory")? .join(".local") .join("bin"); - fs::create_dir_all(&local_bin) - .map_err(|e| format!("create {}: {e}", local_bin.display()))?; + fs::create_dir_all(&local_bin).map_err(|e| format!("create {}: {e}", local_bin.display()))?; let link = local_bin.join("sprout"); match link.symlink_metadata() { diff --git a/desktop/src-tauri/src/managed_agents/runtime.rs b/desktop/src-tauri/src/managed_agents/runtime.rs index 77dd5cb12..770618fdb 100644 --- a/desktop/src-tauri/src/managed_agents/runtime.rs +++ b/desktop/src-tauri/src/managed_agents/runtime.rs @@ -553,7 +553,11 @@ pub fn spawn_agent_child( if let Some(shell_path) = login_shell_path() { parts.push(shell_path); } - if parts.is_empty() { None } else { Some(parts.join(":")) } + if parts.is_empty() { + None + } else { + Some(parts.join(":")) + } }; let mut command = std::process::Command::new(&resolved_acp_command); From 145d682529970a7db638eb7f4cd201db07f67748 Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Tue, 19 May 2026 17:55:49 -0400 Subject: [PATCH 5/7] chore: build sprout-cli in staging/goose recipes Agents need the `sprout` binary available. The `staging`, `goose`, and `goose-bg` recipes explicitly list sidecar packages to build but were missing `-p sprout-cli`. --- justfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/justfile b/justfile index bcab72328..b59ce3337 100644 --- a/justfile +++ b/justfile @@ -195,7 +195,7 @@ staging *ARGS: _ensure-sidecar-stubs #!/usr/bin/env bash set -euo pipefail pnpm install - cargo build --release -p sprout-acp -p sprout-mcp -p sprout-agent -p sprout-dev-mcp + cargo build --release -p sprout-acp -p sprout-mcp -p sprout-agent -p sprout-dev-mcp -p sprout-cli cd {{desktop_dir}} source ../scripts/instance-env.sh export SPROUT_RELAY_URL="wss://sprout-oss.stage.blox.sqprod.co" @@ -293,7 +293,7 @@ check-compile: goose relay="ws://localhost:3000" agents="1" heartbeat="0" prompt="" key="$SPROUT_PRIVATE_KEY": #!/usr/bin/env bash set -euo pipefail - cargo build --release -p sprout-acp -p sprout-mcp + cargo build --release -p sprout-acp -p sprout-mcp -p sprout-cli env_args=( SPROUT_RELAY_URL="{{relay}}" SPROUT_PRIVATE_KEY="{{key}}" @@ -313,7 +313,7 @@ goose relay="ws://localhost:3000" agents="1" heartbeat="0" prompt="" key="$SPROU goose-bg relay="ws://localhost:3000" agents="1" heartbeat="0" prompt="" key="$SPROUT_PRIVATE_KEY": #!/usr/bin/env bash set -euo pipefail - cargo build --release -p sprout-acp -p sprout-mcp + cargo build --release -p sprout-acp -p sprout-mcp -p sprout-cli env_args=( SPROUT_RELAY_URL="{{relay}}" SPROUT_PRIVATE_KEY="{{key}}" From e16bf1d93009302f7ed5ffa33bec039e22b4b452 Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Tue, 19 May 2026 19:48:28 -0400 Subject: [PATCH 6/7] fix: copy real sprout binary to sidecar dir in staging recipe tauri dev copies sidecars from desktop/src-tauri/binaries/ which contains 0-byte stubs. The staging recipe builds the real CLI binary but never copies it there, so agents get an empty file. Copy the built binary after cargo build so tauri dev picks up the real thing. --- justfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/justfile b/justfile index b59ce3337..f290bdcbe 100644 --- a/justfile +++ b/justfile @@ -196,6 +196,9 @@ staging *ARGS: _ensure-sidecar-stubs set -euo pipefail pnpm install cargo build --release -p sprout-acp -p sprout-mcp -p sprout-agent -p sprout-dev-mcp -p sprout-cli + # Replace the 0-byte sidecar stub with the real CLI binary so tauri dev picks it up. + TARGET=$(rustc -vV | sed -n 's|host: ||p') + cp target/release/sprout "desktop/src-tauri/binaries/sprout-${TARGET}" cd {{desktop_dir}} source ../scripts/instance-env.sh export SPROUT_RELAY_URL="wss://sprout-oss.stage.blox.sqprod.co" From b72680e2d4e3b69ba02c86968071eed9fc34687f Mon Sep 17 00:00:00 2001 From: Will Pfleger Date: Tue, 19 May 2026 20:18:38 -0400 Subject: [PATCH 7/7] fix: set execute permission on copied sprout binary in staging cp overwrites the 0-byte stub's contents but preserves its 644 permissions (from touch). Add chmod +x so agents don't get "permission denied" when running the CLI. --- justfile | 1 + 1 file changed, 1 insertion(+) diff --git a/justfile b/justfile index f290bdcbe..82fd06d31 100644 --- a/justfile +++ b/justfile @@ -199,6 +199,7 @@ staging *ARGS: _ensure-sidecar-stubs # Replace the 0-byte sidecar stub with the real CLI binary so tauri dev picks it up. TARGET=$(rustc -vV | sed -n 's|host: ||p') cp target/release/sprout "desktop/src-tauri/binaries/sprout-${TARGET}" + chmod +x "desktop/src-tauri/binaries/sprout-${TARGET}" cd {{desktop_dir}} source ../scripts/instance-env.sh export SPROUT_RELAY_URL="wss://sprout-oss.stage.blox.sqprod.co"