Add single-file executable using bun (#332)

This adds a single-file executable using bun. The trick is to use a
separate flag for ACP `--acp` and spawn the regular `cli.js` bundled by
the Agent SDK if no flags are passed. This was, we can pass ourselves as
pathToClaudeCodeExecutable.
This commit is contained in:
Steffen Deusch
2026-02-20 12:23:04 +01:00
committed by GitHub
parent d36204f6da
commit 101f09dbf8
4 changed files with 120 additions and 10 deletions

View File

@@ -1,11 +1,81 @@
name: Publish Package to npmjs
name: Publish and Release
on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
jobs:
publish-npm:
build-binaries:
name: Build (${{ matrix.target }})
runs-on: ubuntu-latest
permissions:
contents: read
timeout-minutes: 15
strategy:
fail-fast: true
matrix:
include:
- target: bun-linux-x64
artifact: claude-agent-acp-linux-x64
archive: claude-agent-acp-linux-x64.tar.gz
- target: bun-linux-arm64
artifact: claude-agent-acp-linux-arm64
archive: claude-agent-acp-linux-arm64.tar.gz
- target: bun-linux-x64-musl
artifact: claude-agent-acp-linux-x64-musl
archive: claude-agent-acp-linux-x64-musl.tar.gz
- target: bun-linux-arm64-musl
artifact: claude-agent-acp-linux-arm64-musl
archive: claude-agent-acp-linux-arm64-musl.tar.gz
- target: bun-darwin-x64
artifact: claude-agent-acp-darwin-x64
archive: claude-agent-acp-darwin-x64.zip
- target: bun-darwin-arm64
artifact: claude-agent-acp-darwin-arm64
archive: claude-agent-acp-darwin-arm64.zip
- target: bun-windows-x64
artifact: claude-agent-acp-windows-x64.exe
archive: claude-agent-acp-windows-x64.zip
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Build binary
run: |
bun build src/static-entry.ts \
--compile \
--target ${{ matrix.target }} \
--define 'process.env.CLAUDE_AGENT_ACP_IS_SINGLE_FILE_BUN="true"' \
--outfile ${{ matrix.artifact }}
- name: Compress binary
run: |
if [[ "${{ matrix.archive }}" == *.zip ]]; then
zip ${{ matrix.archive }} ${{ matrix.artifact }}
else
tar czf ${{ matrix.archive }} ${{ matrix.artifact }}
fi
- name: Upload artifact
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
name: ${{ matrix.archive }}
path: ${{ matrix.archive }}
publish-npm:
name: Publish to npm
runs-on: ubuntu-latest
needs: [build-binaries]
if: startsWith(github.ref, 'refs/tags/')
environment: release # Optional: for enhanced security
permissions:
contents: read
@@ -22,15 +92,26 @@ jobs:
- run: npm ci
- run: npm run build
- run: npm publish
release:
name: Create Release
runs-on: ubuntu-latest
needs: [publish-npm]
needs: [build-binaries, publish-npm]
if: startsWith(github.ref, 'refs/tags/')
permissions:
contents: write
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- name: Download all artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
with:
path: artifacts
merge-multiple: true
- name: Create Release
id: create_release
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090
with:
generate_release_notes: true
files: artifacts/*

View File

@@ -48,6 +48,11 @@ You can then use `claude-agent-acp` as a regular ACP agent:
ANTHROPIC_API_KEY=sk-... claude-agent-acp
```
#### Single-file executable
Pre-built single-file binaries are available on the [Releases](https://github.com/zed-industries/claude-agent-acp/releases) page for Linux, macOS, and Windows.
These binaries bundle everything needed and don't require Node.js.
## License
Apache-2.0

View File

@@ -230,6 +230,10 @@ export type ToolUseCache = {
};
};
function isStaticBinary(): boolean {
return process.env.CLAUDE_AGENT_ACP_IS_SINGLE_FILE_BUN !== undefined;
}
// Bypass Permissions doesn't work if we are a root/sudo user
const IS_ROOT = (process.geteuid?.() ?? process.getuid?.()) === 0;
const ALLOW_BYPASS = !IS_ROOT || !!process.env.IS_SANDBOX;
@@ -264,12 +268,22 @@ export class ClaudeAcpAgent implements Agent {
// If client supports terminal-auth capability, use that instead.
if (request.clientCapabilities?._meta?.["terminal-auth"] === true) {
const cliPath = fileURLToPath(import.meta.resolve("@anthropic-ai/claude-agent-sdk/cli.js"));
let command: string;
let args: string[];
if (isStaticBinary()) {
command = process.execPath;
args = ["--cli"];
} else {
const cliPath = fileURLToPath(import.meta.resolve("@anthropic-ai/claude-agent-sdk/cli.js"));
command = "node";
args = [cliPath];
}
authMethod._meta = {
"terminal-auth": {
command: "node",
args: [cliPath],
command,
args,
label: "Claude Login",
},
};
@@ -1231,9 +1245,12 @@ export class ClaudeAcpAgent implements Agent {
// note: although not documented by the types, passing an absolute path
// here works to find zed's managed node version.
executable: process.execPath as any,
...(process.env.CLAUDE_CODE_EXECUTABLE && {
pathToClaudeCodeExecutable: process.env.CLAUDE_CODE_EXECUTABLE,
}),
executableArgs: isStaticBinary() ? ["--cli"] : undefined,
...(process.env.CLAUDE_CODE_EXECUTABLE
? { pathToClaudeCodeExecutable: process.env.CLAUDE_CODE_EXECUTABLE }
: isStaticBinary()
? { pathToClaudeCodeExecutable: process.execPath }
: {}),
disallowedTools,
tools: { type: "preset", preset: "claude_code" },
hooks: {

7
src/static-entry.ts Normal file
View File

@@ -0,0 +1,7 @@
if (process.argv.includes("--cli")) {
process.argv = process.argv.filter((arg) => arg !== "--cli");
// @ts-expect-error -- no types
await import("@anthropic-ai/claude-agent-sdk/cli.js");
} else {
await import("./index.js");
}