fix: bundle node runtime for mac app

This commit is contained in:
Peter Steinberger
2026-01-10 15:28:37 +01:00
parent c782404bee
commit 449bee9645
13 changed files with 258 additions and 99 deletions

View File

@@ -71,6 +71,12 @@ enum GatewayEnvironment {
return FileManager.default.isExecutableFile(atPath: path) ? path : nil
}
static func bundledNodeExecutable() -> String? {
guard let res = Bundle.main.resourceURL else { return nil }
let path = res.appendingPathComponent("Relay/node").path
return FileManager.default.isExecutableFile(atPath: path) ? path : nil
}
static func gatewayPort() -> Int {
if let raw = ProcessInfo.processInfo.environment["CLAWDBOT_GATEWAY_PORT"] {
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
@@ -107,13 +113,15 @@ enum GatewayEnvironment {
if let bundled = self.bundledGatewayExecutable() {
let installed = self.readGatewayVersion(binary: bundled)
let bundledNode = self.bundledNodeExecutable()
let bundledNodeVersion = bundledNode.flatMap { self.readNodeVersion(binary: $0) }
if let expected, let installed, !installed.compatible(with: expected) {
let message =
"Bundled gateway \(installed.description) is incompatible with app " +
"\(expected.description); rebuild the app bundle."
return GatewayEnvironmentStatus(
kind: .incompatible(found: installed.description, required: expected.description),
nodeVersion: nil,
nodeVersion: bundledNodeVersion,
gatewayVersion: installed.description,
requiredGateway: expected.description,
message: message)
@@ -121,10 +129,10 @@ enum GatewayEnvironment {
let gatewayVersionText = installed?.description ?? "unknown"
return GatewayEnvironmentStatus(
kind: .ok,
nodeVersion: nil,
nodeVersion: bundledNodeVersion,
gatewayVersion: gatewayVersionText,
requiredGateway: expected?.description,
message: "Bundled gateway \(gatewayVersionText) (bun)")
message: "Bundled gateway \(gatewayVersionText) (node \(bundledNodeVersion ?? "unknown"))")
}
let projectRoot = CommandResolver.projectRoot()
@@ -351,4 +359,26 @@ enum GatewayEnvironment {
else { return nil }
return Semver.parse(version)
}
private static func readNodeVersion(binary: String) -> String? {
let process = Process()
process.executableURL = URL(fileURLWithPath: binary)
process.arguments = ["--version"]
process.environment = ["PATH": CommandResolver.preferredPaths().joined(separator: ":")]
let pipe = Pipe()
process.standardOutput = pipe
process.standardError = pipe
do {
try process.run()
process.waitUntilExit()
let data = pipe.fileHandleForReading.readToEndSafely()
let raw = String(data: data, encoding: .utf8)?
.trimmingCharacters(in: .whitespacesAndNewlines)
.replacingOccurrences(of: "^v", with: "", options: .regularExpression)
return raw?.isEmpty == false ? raw : nil
} catch {
return nil
}
}
}

View File

@@ -187,7 +187,7 @@ Notes:
- `daemon install` is a no-op when already installed; use `clawdbot daemon install --force` to reinstall (profile/env/path changes).
Bundled mac app:
- Clawdbot.app can bundle a bun-compiled gateway binary and install a per-user LaunchAgent labeled `com.clawdbot.gateway`.
- Clawdbot.app can bundle a Node-based gateway relay and install a per-user LaunchAgent labeled `com.clawdbot.gateway`.
- To stop it cleanly, use `clawdbot daemon stop` (or `launchctl bootout gui/$UID/com.clawdbot.gateway`).
- To restart, use `clawdbot daemon restart` (or `launchctl kickstart -k gui/$UID/com.clawdbot.gateway`).
- `launchctl` only works if the LaunchAgent is installed; otherwise use `clawdbot daemon install` first.

View File

@@ -322,7 +322,7 @@ kill -9 <PID> # last resort
```
**Fix 3: Check embedded gateway**
Ensure the gateway relay was properly bundled. Run [`./scripts/package-mac-app.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/package-mac-app.sh) and ensure `bun` is installed.
Ensure the gateway relay was properly bundled. Run [`./scripts/package-mac-app.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/package-mac-app.sh) and ensure Node is available (the script downloads a bundled runtime by default).
## Debug Mode

View File

@@ -1,32 +1,43 @@
---
summary: "Bundled bun gateway: packaging, launchd, signing, and bytecode"
summary: "Bundled Node gateway: packaging, launchd, signing, and bundling"
read_when:
- Packaging Clawdbot.app
- Debugging the bundled gateway binary
- Changing bun build flags or codesigning
- Changing relay bundling flags or codesigning
---
# Bundled bun Gateway (macOS)
# Bundled Node Gateway (macOS)
Goal: ship **Clawdbot.app** with a self-contained relay binary that can run both the CLI and the Gateway daemon. No global `npm install -g clawdbot`, no system Node requirement.
Goal: ship **Clawdbot.app** with a self-contained relay that can run the CLI and
Gateway daemon. No global `npm install -g clawdbot`, no system Node requirement.
## What gets bundled
App bundle layout:
- `Clawdbot.app/Contents/Resources/Relay/node`
- Node runtime binary (downloaded during packaging, stripped for size)
- `Clawdbot.app/Contents/Resources/Relay/dist/`
- Compiled CLI/gateway payload from `pnpm exec tsc`
- `Clawdbot.app/Contents/Resources/Relay/node_modules/`
- Production dependencies staged via `pnpm deploy --prod --no-optional --legacy`
- `Clawdbot.app/Contents/Resources/Relay/clawdbot`
- bun `--compile` relay executable built from `dist/macos/relay.js`
- Supports:
- `clawdbot …` (CLI)
- `clawdbot gateway …` (LaunchAgent daemon)
- Wrapper script that execs the bundled Node + dist entrypoint
- `Clawdbot.app/Contents/Resources/Relay/package.json`
- tiny “p runtime compatibility” file (see below)
- tiny “Pi runtime compatibility” file (see below, includes `"type": "module"`)
- `Clawdbot.app/Contents/Resources/Relay/skills/`
- Bundled skills payload (required for Pi tools)
- `Clawdbot.app/Contents/Resources/Relay/theme/`
- p TUI theme payload (optional, but strongly recommended)
- Pi TUI theme payload (optional, but strongly recommended)
- `Clawdbot.app/Contents/Resources/Relay/a2ui/`
- A2UI host assets (served by the gateway)
- `Clawdbot.app/Contents/Resources/Relay/control-ui/`
- Control UI build output (served by the gateway)
Why the sidecar files matter:
- The embedded p runtime detects “bun binary mode” and then looks for `package.json` + `theme/` **next to `process.execPath`** (i.e. next to `clawdbot`).
- So even if bun can embed assets, the runtime expects filesystem paths. Keep the sidecar files.
- The embedded Pi runtime detects “bundled relay mode” and then looks for
`package.json` + `theme/` **next to `process.execPath`** (i.e. next to
`node`). Keep the sidecar files.
## Build pipeline
@@ -36,18 +47,18 @@ Packaging script:
It builds:
- TS: `pnpm exec tsc`
- Swift app + helper: `swift build …`
- bun relay: `bun build dist/macos/relay.js --compile --bytecode …`
- Relay payload: `pnpm deploy --prod --no-optional --legacy` + copy `dist/`
- Node runtime: downloads the latest Node release (override via `NODE_VERSION`)
Important bundler flags:
- `--compile`: produces a standalone executable
- `--bytecode`: reduces startup time / parsing overhead (works here)
- externals:
- `-e electron`
- Reason: avoid bundling Electron stubs in the relay binary
Important knobs:
- `NODE_VERSION=22.12.0` → pin a specific Node version
- `NODE_DIST_MIRROR=…` → mirror for downloads (default: nodejs.org)
- `STRIP_NODE=0` → keep symbols (default strips to reduce size)
- `BUNDLED_RUNTIME=bun` → switch the relay build back to Bun (`bun --compile`)
Version injection:
- `--define "__CLAWDBOT_VERSION__=\"<pkg version>\""`
- The relay honors `__CLAWDBOT_VERSION__` / `CLAWDBOT_BUNDLED_VERSION` so `--version` doesnt depend on reading `package.json` at runtime.
- The relay wrapper exports `CLAWDBOT_BUNDLED_VERSION` so `--version` works
without reading `package.json` at runtime.
## Launchd (Gateway as LaunchAgent)
@@ -63,40 +74,26 @@ Manager:
Behavior:
- “Clawdbot Active” enables/disables the LaunchAgent.
- App quit does **not** stop the gateway (launchd keeps it alive).
- CLI install (`clawdbot daemon install`) writes the same LaunchAgent; `clawdbot daemon install --force` rewrites it.
- `clawdbot doctor` audits the LaunchAgent config and can update it to current defaults.
- CLI install (`clawdbot daemon install`) writes the same LaunchAgent; `--force` rewrites it.
Logging:
- launchd stdout/err: `/tmp/clawdbot/clawdbot-gateway.log`
Default LaunchAgent env:
- `CLAWDBOT_IMAGE_BACKEND=sips` (avoid sharp native addon under bun)
- `CLAWDBOT_IMAGE_BACKEND=sips` (avoid sharp native addon inside the bundle)
## Codesigning (hardened runtime + bun)
## Codesigning (hardened runtime + Node)
Symptom (when mis-signed):
- `Ran out of executable memory …` on launch
Node uses JIT. The bundled runtime is signed with:
- `com.apple.security.cs.allow-jit`
- `com.apple.security.cs.allow-unsigned-executable-memory`
Fix:
- The bun executable needs JIT-ish permissions under hardened runtime.
- `scripts/codesign-mac-app.sh` signs `Relay/clawdbot` with:
- `com.apple.security.cs.allow-jit`
- `com.apple.security.cs.allow-unsigned-executable-memory`
This is applied by `scripts/codesign-mac-app.sh`.
## Image processing under bun
## Image processing
Problem:
- bun cant load some native Node addons like `sharp` (and we dont want to ship native addon trees for the gateway).
Solution:
- Image operations prefer `/usr/bin/sips` on macOS (especially under bun).
- When running in Node/dev, `sharp` is used when available.
- This affects inbound/outbound media, screenshots, and tool image sanitization.
## Browser control server
The Gateway starts the browser control server (loopback only) from the relay daemon process,
so the relay binary includes Playwright deps.
To avoid shipping native `sharp` addons inside the bundle, the gateway defaults
to `/usr/bin/sips` for image ops when run from the app (via launchd env + wrapper).
## Tests / smoke checks
@@ -115,17 +112,3 @@ Then, in another shell:
```bash
pnpm -s clawdbot gateway call health --url ws://127.0.0.1:18999 --timeout 3000
```
## Repo hygiene
Bun may leave dotfiles like `*.bun-build` in the repo root or subfolders.
- These are ignored via `.gitignore` (`*.bun-build`).
## DMG styling (human installer)
`scripts/create-dmg.sh` styles the DMG via Finder AppleScript.
Rules of thumb:
- Use a **72dpi** background image that matches the Finder window size in points.
- Preferred asset: `assets/dmg-background-small.png` (**500×320**).
- Default icon positions: app `{125,160}`, Applications `{375,160}`.

View File

@@ -13,10 +13,7 @@ Before building the app, ensure you have the following installed:
1. **Xcode 26.2+**: Required for Swift development.
2. **Node.js & pnpm**: Required for the gateway and CLI components.
3. **Bun**: Required to package the embedded gateway relay.
```bash
curl -fsSL https://bun.sh/install | bash
```
3. **Node**: Required to package the embedded gateway relay (the script can download a bundled runtime).
## 1. Initialize Submodules
@@ -42,6 +39,8 @@ To build the macOS app and package it into `dist/Clawdbot.app`, run:
./scripts/package-mac-app.sh
```
Use `BUNDLED_RUNTIME=node|bun` to switch the embedded gateway runtime (default: node).
If you don't have an Apple Developer ID certificate, the script will automatically use **ad-hoc signing** (`-`).
> **Note**: Ad-hoc signed apps may trigger security prompts. If the app crashes immediately with "Abort trap 6", see the [Troubleshooting](#troubleshooting) section.

View File

@@ -12,7 +12,7 @@ This app is usually built from [`scripts/package-mac-app.sh`](https://github.com
- calls [`scripts/codesign-mac-app.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/codesign-mac-app.sh) to sign the main binary, bundled CLI, and app bundle so macOS treats each rebuild as the same signed bundle and keeps TCC permissions (notifications, accessibility, screen recording, mic, speech). For stable permissions, use a real signing identity; ad-hoc is opt-in and fragile (see [`docs/mac/permissions.md`](/platforms/mac/permissions)).
- uses `CODESIGN_TIMESTAMP=auto` by default; it enables trusted timestamps for Developer ID signatures. Set `CODESIGN_TIMESTAMP=off` to skip timestamping (offline debug builds).
- inject build metadata into Info.plist: `ClawdbotBuildTimestamp` (UTC) and `ClawdbotGitCommit` (short hash) so the About pane can show build, git, and debug/release channel.
- **Packaging requires Bun**: The embedded gateway relay is compiled using `bun`. Ensure it is installed (`curl -fsSL https://bun.sh/install | bash`).
- **Packaging requires Node**: The embedded gateway relay is bundled with Node. Ensure Node is available for the packaging script (or set `NODE_VERSION` to pin the download).
- reads `SIGN_IDENTITY` from the environment. Add `export SIGN_IDENTITY="Apple Development: Your Name (TEAMID)"` (or your Developer ID Application cert) to your shell rc to always sign with your cert. Ad-hoc signing requires explicit opt-in via `ALLOW_ADHOC_SIGNING=1` or `SIGN_IDENTITY="-"` (not recommended for permission testing).
## Usage

View File

@@ -88,10 +88,11 @@ Safety:
- `cd apps/macos && swift build`
- `swift run Clawdbot` (or Xcode)
- Package app + CLI: `scripts/package-mac-app.sh`
- Switch bundled gateway runtime with `BUNDLED_RUNTIME=node|bun` (default: node).
## Related docs
- [Gateway runbook](/gateway)
- [Bundled bun Gateway](/platforms/mac/bun)
- [Bundled Node Gateway](/platforms/mac/bun)
- [macOS permissions](/platforms/mac/permissions)
- [Canvas](/platforms/mac/canvas)

View File

@@ -141,7 +141,7 @@ Use these hubs to discover every page, including deep dives and reference docs t
- [macOS remote](https://docs.clawd.bot/platforms/mac/remote)
- [macOS signing](https://docs.clawd.bot/platforms/mac/signing)
- [macOS release](https://docs.clawd.bot/platforms/mac/release)
- [macOS bun gateway](https://docs.clawd.bot/platforms/mac/bun)
- [macOS bundled gateway (Node)](https://docs.clawd.bot/platforms/mac/bun)
- [macOS XPC](https://docs.clawd.bot/platforms/mac/xpc)
- [macOS skills](https://docs.clawd.bot/platforms/mac/skills)
- [macOS Peekaboo](https://docs.clawd.bot/platforms/mac/peekaboo)

View File

@@ -7,7 +7,7 @@ TIMESTAMP_MODE="${CODESIGN_TIMESTAMP:-auto}"
ENT_TMP_BASE=$(mktemp -t clawdbot-entitlements-base.XXXXXX)
ENT_TMP_APP=$(mktemp -t clawdbot-entitlements-app.XXXXXX)
ENT_TMP_APP_BASE=$(mktemp -t clawdbot-entitlements-app-base.XXXXXX)
ENT_TMP_BUN=$(mktemp -t clawdbot-entitlements-bun.XXXXXX)
ENT_TMP_RUNTIME=$(mktemp -t clawdbot-entitlements-runtime.XXXXXX)
if [ ! -d "$APP_BUNDLE" ]; then
echo "App bundle not found: $APP_BUNDLE" >&2
@@ -150,7 +150,7 @@ cat > "$ENT_TMP_APP_BASE" <<'PLIST'
</plist>
PLIST
cat > "$ENT_TMP_BUN" <<'PLIST'
cat > "$ENT_TMP_RUNTIME" <<'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
@@ -215,8 +215,11 @@ if [ -d "$APP_BUNDLE/Contents/Resources/Relay" ]; then
find "$APP_BUNDLE/Contents/Resources/Relay" -type f \( -name "*.node" -o -name "*.dylib" \) -print0 | while IFS= read -r -d '' f; do
echo "Signing gateway payload: $f"; sign_item "$f" "$ENT_TMP_BASE"
done
if [ -f "$APP_BUNDLE/Contents/Resources/Relay/node" ]; then
echo "Signing embedded node"; sign_item "$APP_BUNDLE/Contents/Resources/Relay/node" "$ENT_TMP_RUNTIME"
fi
if [ -f "$APP_BUNDLE/Contents/Resources/Relay/clawdbot" ]; then
echo "Signing embedded relay"; sign_item "$APP_BUNDLE/Contents/Resources/Relay/clawdbot" "$ENT_TMP_BUN"
echo "Signing embedded relay wrapper"; sign_plain_item "$APP_BUNDLE/Contents/Resources/Relay/clawdbot"
fi
fi
@@ -246,5 +249,5 @@ fi
# Finally sign the bundle
sign_item "$APP_BUNDLE" "$APP_ENTITLEMENTS"
rm -f "$ENT_TMP_BASE" "$ENT_TMP_APP_BASE" "$ENT_TMP_APP" "$ENT_TMP_BUN"
rm -f "$ENT_TMP_BASE" "$ENT_TMP_APP_BASE" "$ENT_TMP_APP" "$ENT_TMP_RUNTIME"
echo "Codesign complete for $APP_BUNDLE"

View File

@@ -22,6 +22,7 @@ if [[ "${BUILD_ARCHS_VALUE}" == "all" ]]; then
fi
IFS=' ' read -r -a BUILD_ARCHS <<< "$BUILD_ARCHS_VALUE"
PRIMARY_ARCH="${BUILD_ARCHS[0]}"
BUNDLED_RUNTIME="${BUNDLED_RUNTIME:-node}"
SPARKLE_PUBLIC_ED_KEY="${SPARKLE_PUBLIC_ED_KEY:-AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj/Qs67XI=}"
SPARKLE_FEED_URL="${SPARKLE_FEED_URL:-https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml}"
AUTO_CHECKS=true
@@ -136,6 +137,116 @@ build_relay_binary() {
fi
}
resolve_node_version() {
if [[ -n "${NODE_VERSION:-}" ]]; then
echo "${NODE_VERSION#v}"
return
fi
local mirror="${NODE_DIST_MIRROR:-https://nodejs.org/dist}"
local latest
if latest="$(/usr/bin/curl -fsSL "$mirror/index.tab" 2>/dev/null | /usr/bin/awk 'NR==2 {print $1}')" && [[ -n "$latest" ]]; then
echo "${latest#v}"
return
fi
if command -v node >/dev/null 2>&1; then
node -p "process.versions.node"
return
fi
echo "22.12.0"
}
node_dist_filename() {
local version="$1"
local arch="$2"
local node_arch="$arch"
if [[ "$arch" == "x86_64" ]]; then
node_arch="x64"
fi
echo "node-v${version}-darwin-${node_arch}.tar.gz"
}
download_node_binary() {
local version="$1"
local arch="$2"
local out="$3"
local mirror="${NODE_DIST_MIRROR:-https://nodejs.org/dist}"
local tarball
tarball="$(node_dist_filename "$version" "$arch")"
local tmp_dir
tmp_dir="$(mktemp -d)"
local url="$mirror/v${version}/${tarball}"
echo "⬇️ Downloading Node ${version} (${arch})"
/usr/bin/curl -fsSL "$url" -o "$tmp_dir/node.tgz"
/usr/bin/tar -xzf "$tmp_dir/node.tgz" -C "$tmp_dir"
local node_arch="$arch"
if [[ "$arch" == "x86_64" ]]; then
node_arch="x64"
fi
local node_src="$tmp_dir/node-v${version}-darwin-${node_arch}/bin/node"
if [[ ! -f "$node_src" ]]; then
echo "ERROR: Node binary missing in $tarball" >&2
rm -rf "$tmp_dir"
exit 1
fi
cp "$node_src" "$out"
chmod +x "$out"
rm -rf "$tmp_dir"
}
stage_relay_payload() {
local relay_dir="$1"
if [[ "${SKIP_RELAY_DEPS:-0}" != "1" ]]; then
local stage_dir="$relay_dir/.relay-deploy"
rm -rf "$stage_dir"
mkdir -p "$stage_dir"
echo "📦 Staging relay dependencies (pnpm deploy --prod --no-optional --legacy)"
(cd "$ROOT_DIR" && pnpm --filter . deploy "$stage_dir" --prod --no-optional --legacy)
rm -rf "$relay_dir/node_modules"
cp -a "$stage_dir/node_modules" "$relay_dir/node_modules"
rm -rf "$stage_dir"
else
echo "📦 Skipping relay dependency staging (SKIP_RELAY_DEPS=1)"
fi
echo "📦 Copying relay dist payload"
rm -rf "$relay_dir/dist"
cp -R "$ROOT_DIR/dist" "$relay_dir/dist"
}
write_relay_wrapper() {
local relay_dir="$1"
local wrapper="$relay_dir/clawdbot"
cat > "$wrapper" <<SH
#!/bin/sh
set -e
DIR="\$(cd "\$(dirname "\$0")" && pwd)"
NODE="\$DIR/node"
REL="\$DIR/dist/macos/relay.js"
export CLAWDBOT_BUNDLED_VERSION="\${CLAWDBOT_BUNDLED_VERSION:-$PKG_VERSION}"
export CLAWDBOT_IMAGE_BACKEND="\${CLAWDBOT_IMAGE_BACKEND:-sips}"
NODE_PATH="\$DIR/node_modules\${NODE_PATH:+:\$NODE_PATH}"
export NODE_PATH
exec "\$NODE" "\$REL" "\$@"
SH
chmod +x "$wrapper"
}
validate_bundled_runtime() {
case "$BUNDLED_RUNTIME" in
node|bun) return 0 ;;
*)
echo "ERROR: Unsupported BUNDLED_RUNTIME=$BUNDLED_RUNTIME (use node|bun)" >&2
exit 1
;;
esac
}
echo "📦 Ensuring deps (pnpm install)"
(cd "$ROOT_DIR" && pnpm install --no-frozen-lockfile --config.node-linker=hoisted)
if [[ "${SKIP_TSC:-0}" != "1" ]]; then
@@ -249,31 +360,58 @@ fi
RELAY_DIR="$APP_ROOT/Contents/Resources/Relay"
if [[ "${SKIP_GATEWAY_PACKAGE:-0}" != "1" ]]; then
if ! command -v bun >/dev/null 2>&1; then
echo "ERROR: bun missing. Install bun to package the embedded gateway." >&2
exit 1
fi
echo "🧰 Building bundled relay (bun --compile)"
validate_bundled_runtime
mkdir -p "$RELAY_DIR"
RELAY_OUT="$RELAY_DIR/clawdbot"
RELAY_BUILD_DIR="$RELAY_DIR/.relay-build"
rm -rf "$RELAY_BUILD_DIR"
mkdir -p "$RELAY_BUILD_DIR"
for arch in "${BUILD_ARCHS[@]}"; do
RELAY_ARCH_OUT="$RELAY_BUILD_DIR/clawdbot-$arch"
build_relay_binary "$arch" "$RELAY_ARCH_OUT"
chmod +x "$RELAY_ARCH_OUT"
done
if [[ "${#BUILD_ARCHS[@]}" -gt 1 ]]; then
/usr/bin/lipo -create "$RELAY_BUILD_DIR"/clawdbot-* -output "$RELAY_OUT"
RELAY_CMD="$RELAY_DIR/clawdbot"
if [[ "$BUNDLED_RUNTIME" == "bun" ]]; then
if ! command -v bun >/dev/null 2>&1; then
echo "ERROR: bun missing. Install bun or set BUNDLED_RUNTIME=node." >&2
exit 1
fi
echo "🧰 Building bundled relay (bun --compile)"
RELAY_BUILD_DIR="$RELAY_DIR/.relay-build"
rm -rf "$RELAY_BUILD_DIR"
mkdir -p "$RELAY_BUILD_DIR"
for arch in "${BUILD_ARCHS[@]}"; do
RELAY_ARCH_OUT="$RELAY_BUILD_DIR/clawdbot-$arch"
build_relay_binary "$arch" "$RELAY_ARCH_OUT"
chmod +x "$RELAY_ARCH_OUT"
done
if [[ "${#BUILD_ARCHS[@]}" -gt 1 ]]; then
/usr/bin/lipo -create "$RELAY_BUILD_DIR"/clawdbot-* -output "$RELAY_CMD"
else
cp "$RELAY_BUILD_DIR/clawdbot-${BUILD_ARCHS[0]}" "$RELAY_CMD"
fi
rm -rf "$RELAY_BUILD_DIR"
else
cp "$RELAY_BUILD_DIR/clawdbot-${BUILD_ARCHS[0]}" "$RELAY_OUT"
NODE_VERSION="$(resolve_node_version)"
echo "🧰 Preparing bundled Node runtime (v${NODE_VERSION})"
RELAY_NODE="$RELAY_DIR/node"
RELAY_NODE_BUILD_DIR="$RELAY_DIR/.node-build"
rm -rf "$RELAY_NODE_BUILD_DIR"
mkdir -p "$RELAY_NODE_BUILD_DIR"
for arch in "${BUILD_ARCHS[@]}"; do
NODE_ARCH_OUT="$RELAY_NODE_BUILD_DIR/node-$arch"
download_node_binary "$NODE_VERSION" "$arch" "$NODE_ARCH_OUT"
done
if [[ "${#BUILD_ARCHS[@]}" -gt 1 ]]; then
/usr/bin/lipo -create "$RELAY_NODE_BUILD_DIR"/node-* -output "$RELAY_NODE"
else
cp "$RELAY_NODE_BUILD_DIR/node-${BUILD_ARCHS[0]}" "$RELAY_NODE"
fi
chmod +x "$RELAY_NODE"
if [[ "${STRIP_NODE:-1}" == "1" ]]; then
/usr/bin/strip -x "$RELAY_NODE" 2>/dev/null || true
fi
rm -rf "$RELAY_NODE_BUILD_DIR"
stage_relay_payload "$RELAY_DIR"
write_relay_wrapper "$RELAY_DIR"
fi
rm -rf "$RELAY_BUILD_DIR"
echo "🧪 Verifying bundled relay (version)"
"$RELAY_OUT" --version >/dev/null
"$RELAY_CMD" --version >/dev/null
echo "🎨 Copying gateway A2UI host assets"
rm -rf "$RELAY_DIR/a2ui"
@@ -292,6 +430,7 @@ if [[ "${SKIP_GATEWAY_PACKAGE:-0}" != "1" ]]; then
{
"name": "clawdbot-embedded",
"version": "$PKG_VERSION",
"type": "module",
"piConfig": {
"name": "pi",
"configDir": ".pi"

View File

@@ -57,7 +57,7 @@ function candidateBinDirs(opts: EnsureClawdbotPathOpts): string[] {
const candidates: string[] = [];
// Bun bundled (macOS app): `clawdbot` lives in the Relay dir (process.execPath).
// Bundled macOS app: `clawdbot` lives in the Relay dir (process.execPath).
try {
const execDir = path.dirname(execPath);
const siblingClawdbot = path.join(execDir, "clawdbot");
@@ -95,7 +95,7 @@ function candidateBinDirs(opts: EnsureClawdbotPathOpts): string[] {
/**
* Best-effort PATH bootstrap so skills that require the `clawdbot` CLI can run
* under launchd/minimal environments (and inside the macOS bun bundle).
* under launchd/minimal environments (and inside the macOS app bundle).
*/
export function ensureClawdbotCliOnPath(opts: EnsureClawdbotPathOpts = {}) {
if (process.env.CLAWDBOT_PATH_BOOTSTRAPPED === "1") return;

View File

@@ -4,7 +4,9 @@ import process from "node:process";
declare const __CLAWDBOT_VERSION__: string;
const BUNDLED_VERSION =
typeof __CLAWDBOT_VERSION__ === "string" ? __CLAWDBOT_VERSION__ : "0.0.0";
(typeof __CLAWDBOT_VERSION__ === "string" && __CLAWDBOT_VERSION__) ||
process.env.CLAWDBOT_BUNDLED_VERSION ||
"0.0.0";
function argValue(args: string[], flag: string): string | undefined {
const idx = args.indexOf(flag);

View File

@@ -4,7 +4,9 @@ import process from "node:process";
declare const __CLAWDBOT_VERSION__: string | undefined;
const BUNDLED_VERSION =
typeof __CLAWDBOT_VERSION__ === "string" ? __CLAWDBOT_VERSION__ : "0.0.0";
(typeof __CLAWDBOT_VERSION__ === "string" && __CLAWDBOT_VERSION__) ||
process.env.CLAWDBOT_BUNDLED_VERSION ||
"0.0.0";
function hasFlag(args: string[], flag: string): boolean {
return args.includes(flag);