Repository navigation
feat(dist): standalone npm-free bundles for squad-cli (#1593) - #1595
Conversation
Adds a build path that produces self-contained Squad bundles vendoring their own Node.js runtime, so installing Squad touches github.com only and never registry.npmjs.org. This is the foundation for the distribution work proposed in #1593, and it closes the limitation Brady documented and deferred in #1587: the gh-aw activation job currently requires npx, so it cannot run on a self-hosted runner behind a firewall that blocks the npm registry. What is here: - scripts/build-standalone.mjs — builds a bundle for a platform/arch pair. Packs the workspace packages, installs them with --omit=dev --omit=optional, vendors an official Node runtime, and writes a launcher. - scripts/install.sh — POSIX installer that resolves the platform, downloads the matching release asset, verifies it against SHA256SUMS.txt, and links squad onto PATH. Mirrors the shape of gh.io/copilot-install. - .github/workflows/squad-standalone-release.yml — 6-target matrix build that attaches archives plus a combined SHA256SUMS.txt to a release. - docs/src/content/docs/features/standalone-install.md — feature docs, with known limitations stated. Design notes: squad-cli and squad-sdk locate templates/, presets/builtin/ and package.json by walking up from import.meta.url at runtime. The bundle therefore preserves a real directory layout instead of compiling to a single executable, which is why this needs zero source changes. It is also the same shape GitHub's own Copilot CLI publishes. Optional dependencies are omitted by default. That drops the Copilot CLI platform binary pulled in transitively through @github/copilot-sdk (~318 MB), node-pty prebuilds, the OpenTelemetry SDK and sql.js — 546 MB down to 114 MB, in line with Copilot CLI's own ~110 MB archives. Squad already requires the copilot CLI on PATH and Copilot CLI ships npm-free installers of its own, so vendoring a second copy would only add weight and version skew. --ignore-scripts is safe here: the vscode-jsonrpc ESM fix that squad-cli's postinstall applies is also applied at runtime by cli-entry.js via a Module._resolveFilename hook, so the bundle does not depend on it having run. Verified locally on win32-x64: - squad --version from the bundle returns 0.11.0 on its own vendored runtime - squad init --preset default --state-backend local scaffolds 5 agents in a clean directory with no npm involved - generated .github/workflows/squad-heartbeat.yml is byte-identical (sha256 5CE0F5D1...) to the repo template source, confirming the bundle reproduces its source faithfully - docs-build test passes, cspell and markdownlint clean on the new page No packages/*/src changes, so no changeset is required. Refs #1593, #1587 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d2a1f506-af41-4d6d-b497-d0a5b255d4cf
🟠 Impact Analysis — PR #1595Risk tier: 🟠 HIGH 📊 Summary
🎯 Risk Factors
📦 Modules Affectedci-workflows (1 file)
docs (2 files)
root (2 files)
scripts (2 files)
tests (1 file)
This report is generated automatically for every PR. See #733 for details. |
🛫 PR Readiness Check
PR Scope: 🔧 Infrastructure
|
| Status | Check | Details |
|---|---|---|
| ✅ | Single commit | 1 commit — clean history |
| ✅ | Not in draft | Ready for review |
| ✅ | Branch up to date | Up to date with dev |
| ❌ | Copilot review | No Copilot review yet — it may still be processing |
| ✅ | Changeset present | No source files changed — changeset not required |
| ✅ | Scope clean | No .squad/ or docs/proposals/ files |
| ✅ | No merge conflicts | No merge conflicts |
| ❌ | Copilot threads resolved | 3 unresolved Copilot thread(s) — fix and resolve before merging |
| ✅ | CI passing | All checks passing |
Files Changed (8 files, +778 −0)
| File | +/− |
|---|---|
.gitattributes |
+3 −0 |
.github/workflows/squad-standalone-release.yml |
+180 −0 |
.gitignore |
+1 −0 |
docs/src/content/docs/features/standalone-install.md |
+140 −0 |
docs/src/navigation.ts |
+1 −0 |
scripts/build-standalone.mjs |
+331 −0 |
scripts/install.sh |
+121 −0 |
test/docs-build.test.ts |
+1 −0 |
Total: +778 −0
This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.
🏗️ Architectural Review
Automated architectural review — informational only. |
🔒 Security Review🔒 Security review: 1 info.
Automated security review — informational only. |
There was a problem hiding this comment.
Pull request overview
This PR introduces a new distribution path for standalone, npm-free Squad CLI bundles (including a vendored Node.js runtime), plus a POSIX installer, release automation, and documentation updates to make the new install method discoverable.
Changes:
- Add a standalone bundle builder script that stages workspace tarballs into a self-contained on-disk layout and optionally vendors a Node.js runtime.
- Add a POSIX
curl | shinstaller that downloads release artifacts (and optionally verifies checksums) and installs them under a prefix. - Add a release workflow that builds, packages, smoke-tests (native target only), and optionally uploads the bundles +
SHA256SUMS.txt, plus docs/nav/test updates.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
test/docs-build.test.ts |
Adds the new docs feature slug to the expected list so docs-build tests stay in sync. |
scripts/install.sh |
New POSIX installer for downloading and installing standalone bundles from GitHub Releases. |
scripts/build-standalone.mjs |
New bundle builder that packs workspace tarballs, installs runtime deps, and vendors a Node runtime. |
docs/src/navigation.ts |
Adds the standalone install page to the docs navigation. |
docs/src/content/docs/features/standalone-install.md |
New documentation page describing standalone install and bundle contents/limitations. |
.gitignore |
Ignores dist-standalone/ output directory. |
.github/workflows/squad-standalone-release.yml |
New workflow to build/package/upload standalone bundles on release or manual dispatch. |
.gitattributes |
Enforces LF line endings for .sh to prevent shebang breakage on POSIX systems. |
| step('Extracting runtime'); | ||
| run('tar', ['-xf', archivePath, '-C', stagingDir], stagingDir); |
| if fetch "https://github.com/${REPO}/releases/download/${VERSION}/SHA256SUMS.txt" "$tmp/SHA256SUMS.txt" 2>/dev/null; then | ||
| if command -v sha256sum >/dev/null 2>&1; then | ||
| expected="$(grep " ${asset}\$" "$tmp/SHA256SUMS.txt" | awk '{print $1}')" | ||
| actual="$(sha256sum "$tmp/$asset" | awk '{print $1}')" | ||
| elif command -v shasum >/dev/null 2>&1; then | ||
| expected="$(grep " ${asset}\$" "$tmp/SHA256SUMS.txt" | awk '{print $1}')" | ||
| actual="$(shasum -a 256 "$tmp/$asset" | awk '{print $1}')" | ||
| else | ||
| expected=""; actual="" | ||
| fi | ||
| if [ -n "$expected" ] && [ "$expected" != "$actual" ]; then | ||
| err "Checksum mismatch for ${asset}. Expected ${expected}, got ${actual}." | ||
| fi | ||
| [ -n "$expected" ] && info "Checksum verified" | ||
| fi |
| for (const entry of readdirSync(dir, { withFileTypes: true })) { | ||
| const full = path.join(dir, entry.name); | ||
| if (entry.isDirectory()) total += directorySize(full); | ||
| else if (entry.isFile()) total += readFileSync(full).byteLength; |
What
Adds a build path that produces self-contained Squad bundles which vendor their own Node.js runtime. Installing from one touches
github.comonly — neverregistry.npmjs.org— and needs neither Node nor npm present on the target machine.Four pieces: a bundle builder, a POSIX installer, a release workflow, and docs.
npm install -g @bradygaster/squad-cliis untouched and stays the recommended developer path.Why
Closes the limitation @bradygaster documented and explicitly deferred in #1587:
That blocks gh-aw on self-hosted runners inside a corp network. There is currently no plan B:
npm i -gandnpxare the only ways to get Squad onto a machine.This is the foundation layer proposed in #1593 — the artifacts every other channel (install script, container, winget, Homebrew, a
squad-initaction, and an npm stub) would consume.Worth noting the precedent: the CLI we depend on already ships five ways (winget, Homebrew,
curl | bash, npm, and direct binaries), so Squad is now the only npm requirement left in the stack.Refs #1593. Does not close it — #1593 asks for a go/no-go on the whole direction, and this is one slice of it.
How
Why a directory tree, not a single compiled executable.
squad-cliandsquad-sdklocatetemplates/,presets/builtin/andpackage.jsonby walking up fromimport.meta.urlat runtime (getTemplatesDir,getPackageVersion,getBuiltinPresetsDir). Preserving a real on-disk layout keeps every one of those lookups working, which is why this needs zero source changes. It is also the same shape GitHub's own Copilot CLI publishes.Size. A naive build came out at 546 MB.
@github/copilot-sdktransitively vendors the entire Copilot CLI platform binary (318 MB), plus node-pty prebuilds (61 MB), OpenTelemetry (22 MB) and sql.js (18 MB). Omitting optional dependencies by default brings it to 114 MB, in line with Copilot CLI's own ~110 MB archives. Squad already requirescopiloton PATH and Copilot CLI ships its own npm-free installers, so shipping a second copy would add only weight and version skew.--include-optionalrestores the fat build.--ignore-scriptsis safe here. squad-cli's postinstall patchesvscode-jsonrpcfor strict-ESM resolution, butcli-entry.jsapplies the same fix at runtime via aModule._resolveFilenamehook, so the bundle never depends on the install-time patch having run.Verification
Built and run locally on
win32-x64:squad --versionfrom bundle, on its vendored runtime0.11.0squad init --preset default --state-backend localin a clean dir, no npm--skip-runtimevariantFidelity check. Generated
.github/workflows/squad-heartbeat.yml:dev5CE0F5D1…5CE0F5D1…✅ identical@bradygaster/squad-cli@0.11.0A31D7C75…A full A/B against npx showed 148 differing files — all attributable to
devhaving moved ahead of published0.11.0(e.g.squad.agent.mdis 1088 lines ondevvs 1047 published), not to bundling. The byte-identical match against the repo's own template source is the meaningful control.Repo checks:
npm run build✅ ·npm run lint✅ ·npm run lint:eslint✅ 0 errors ·test/docs-build.test.ts✅ 6 passed · cspell ✅ · markdownlint ✅On
npm test: the suite does not fully pass on Windows, on this branch or on cleandev. I measured both:dev(clean baseline)Same 41 failing files either way — these are pre-existing Windows path-separator failures (e.g.
watch-capabilities.test.tsexpects.squad/ralph-instructions.mdbut gets\some\repo\.squad\ralph-instructions.md), which I reproduced on cleandevto confirm. This branch introduces no new failures. The small delta is flaky timing-sensitive tests, not a real improvement. CI onubuntu-latestis the authority here, not my box.Known limitations — stated honestly
win32-x64has actually been executed. The other five targets are cross-built and verified structurally; the workflow smoke-testslinux-x64by invoking the CLI on a real runner. The workflow itself has never run — it needs a maintainer to dispatch it.--include-optionalis untested. I only exercised the default lean build.squad-clideclares"@bradygaster/squad-sdk": ">=0.11.0", so these bundles are not yet reproducible across an SDK minor bump. That issue gates the reproducibility claim, not the mechanism.Scope
New files plus three one-line additions (
.gitignore,docs/src/navigation.ts,test/docs-build.test.ts— the last two are the required sync for a new docs page). Also pins*.sh text eol=lfin.gitattributes:install.shwas authored on Windows and CRLF would break the shebang on Linux.No
packages/*/src/changes → no changeset required.PR Readiness Checklist
Branch & Commit
dev(notmain)devgit diff --cached --stat)Build & Test
npm run buildpassesnpm testpasses — see the note above. 41 test files fail identically on cleandevon Windows; this branch adds none. Deferring to CI.npm run lintpasses (type check clean)npm run lint:eslintpasses (0 errors; 2089 pre-existing warnings, none introduced here)Changeset
packages/*/src/changes. Happy to addskip-changelogif the bot wants it.Docs
features/standalone-install), nav + docs-build test syncedExports
Breaking Changes
None. Purely additive; the npm install path is unchanged.
Waivers
None requested. Flagging rather than waiving: the release workflow has never executed, so the six-target matrix is unproven beyond YAML validation and a locally-verified
win32-x64build. I would rather a maintainer dispatch it once before this merges than have me assert it works.