Summary
Add a native single-binary build target for agentloop using Bun's --compile flag, producing self-contained executables for Linux, macOS, and Windows that require no Node.js or npm install on the target machine.
Motivation
Currently, running agentloop requires:
- Node.js 20+ installed
npm install to materialize node_modules
npx tsx ... or node dist/... to launch
A compiled binary removes all three steps — users just download and run a single file. This is particularly valuable for:
- Distribution as a CLI tool
- CI/CD environments where installing Node + deps is undesirable
- End-users who are not Node.js developers
Why Bun --compile
The existing dependency profile (ink, react, js-tiktoken WASM, ESM-only packages) rules out legacy tools like pkg (archived) and nexe (no ESM support). Node.js built-in SEA is too early-stage for this dependency mix.
Bun handles:
- ✅ TypeScript natively — no
tsc pre-step needed
- ✅ ESM modules (
ink, react, tidy-url, MCP SDK)
- ✅ WASM files (
js-tiktoken) — embedded automatically with --compile
- ✅ Cross-compilation to Linux, macOS (arm64/x64), and Windows from a single build machine
- ✅
.env files still work (read from filesystem next to the binary)
Implementation Steps
1. Verify Bun compatibility
- Run
bun install and confirm all dependencies resolve under Bun
- Run
bun run src/start-cli.ts to validate runtime compatibility
- Pay special attention to
jsdom (used by web-fetch) — test that it initializes correctly under Bun
2. Add build:binary scripts to package.json
"build:binary": "bun build src/start-cli.ts --compile --outfile dist/agentloop",
"build:binary:linux": "bun build src/start-cli.ts --compile --target=bun-linux-x64 --outfile dist/agentloop-linux",
"build:binary:macos": "bun build src/start-cli.ts --compile --target=bun-darwin-arm64 --outfile dist/agentloop-macos",
"build:binary:win": "bun build src/start-cli.ts --compile --target=bun-windows-x64 --outfile dist/agentloop.exe"
3. Handle tool auto-discovery (loadFromDirectory)
AgentProfileRegistry, SkillRegistry, and ToolRegistry all use fs.readdir on paths resolved relative to __dirname. Inside a Bun binary, __dirname resolves correctly to the binary's directory — but the src/tools/, src/agents/builtin/, and src/skills/builtin/ directories are embedded in the bundle, not on disk.
Options to investigate:
- Preferred: Use Bun's
import.meta.dir and verify directory scanning works inside the compiled bundle
- Fallback: Eagerly register all builtin tools/profiles/skills at compile time (static imports instead of directory scan) when running as a compiled binary, detected via
process.isBun
4. Handle .env / config
dotenv reads .env from process.cwd() — this works unchanged next to the binary
- Document that users should place
.env in the same directory as the binary
5. Add bun as a dev dependency (or document install)
"devDependencies": {
"bun-types": "^1.0.0"
}
Or document curl -fsSL https://bun.sh/install | bash in the build prerequisites.
6. Add a GitHub Actions workflow for cross-platform binary releases
# .github/workflows/release-binaries.yml
name: Build release binaries
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install
- run: bun run build:binary:linux
- run: bun run build:binary:macos
- run: bun run build:binary:win
- uses: softprops/action-gh-release@v2
with:
files: |
dist/agentloop-linux
dist/agentloop-macos
dist/agentloop.exe
7. Update docs/getting-started.md
Add a "Download pre-built binary" section as the first / easiest install path.
Known Risks & Mitigations
| Risk |
Mitigation |
jsdom native internals break under Bun |
Lazy-import jsdom only when web-fetch tool is invoked; add a Bun smoke test |
loadFromDirectory() finds no files inside binary |
Detect process.isBun + compiled mode, fall back to static registration |
| Binary size too large |
Acceptable trade-off; bun build --minify can reduce size |
TUI (ink + react) rendering differences under Bun |
Test npm run startTui equivalent under Bun before shipping |
Acceptance Criteria
Summary
Add a native single-binary build target for
agentloopusing Bun's--compileflag, producing self-contained executables for Linux, macOS, and Windows that require no Node.js ornpm installon the target machine.Motivation
Currently, running
agentlooprequires:npm installto materializenode_modulesnpx tsx ...ornode dist/...to launchA compiled binary removes all three steps — users just download and run a single file. This is particularly valuable for:
Why Bun
--compileThe existing dependency profile (
ink,react,js-tiktokenWASM, ESM-only packages) rules out legacy tools likepkg(archived) andnexe(no ESM support). Node.js built-in SEA is too early-stage for this dependency mix.Bun handles:
tscpre-step neededink,react,tidy-url, MCP SDK)js-tiktoken) — embedded automatically with--compile.envfiles still work (read from filesystem next to the binary)Implementation Steps
1. Verify Bun compatibility
bun installand confirm all dependencies resolve under Bunbun run src/start-cli.tsto validate runtime compatibilityjsdom(used byweb-fetch) — test that it initializes correctly under Bun2. Add
build:binaryscripts topackage.json3. Handle tool auto-discovery (
loadFromDirectory)AgentProfileRegistry,SkillRegistry, andToolRegistryall usefs.readdiron paths resolved relative to__dirname. Inside a Bun binary,__dirnameresolves correctly to the binary's directory — but thesrc/tools/,src/agents/builtin/, andsrc/skills/builtin/directories are embedded in the bundle, not on disk.Options to investigate:
import.meta.dirand verify directory scanning works inside the compiled bundleprocess.isBun4. Handle
.env/ configdotenvreads.envfromprocess.cwd()— this works unchanged next to the binary.envin the same directory as the binary5. Add
bunas a dev dependency (or document install)Or document
curl -fsSL https://bun.sh/install | bashin the build prerequisites.6. Add a GitHub Actions workflow for cross-platform binary releases
7. Update
docs/getting-started.mdAdd a "Download pre-built binary" section as the first / easiest install path.
Known Risks & Mitigations
jsdomnative internals break under Bunjsdomonly whenweb-fetchtool is invoked; add a Bun smoke testloadFromDirectory()finds no files inside binaryprocess.isBun+ compiled mode, fall back to static registrationbun build --minifycan reduce sizeink+react) rendering differences under Bunnpm run startTuiequivalent under Bun before shippingAcceptance Criteria
bun run build:binaryproduces a workingdist/agentloopbinary on the build machinejsdom-backedweb-fetchtool works inside the compiled binarydocs/getting-started.mddocuments the binary install path