diff --git a/apps/macos/Sources/Clawdbot/GatewayEnvironment.swift b/apps/macos/Sources/Clawdbot/GatewayEnvironment.swift index e1548d0ae..bf80f5743 100644 --- a/apps/macos/Sources/Clawdbot/GatewayEnvironment.swift +++ b/apps/macos/Sources/Clawdbot/GatewayEnvironment.swift @@ -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 + } + } } diff --git a/docs/gateway/index.md b/docs/gateway/index.md index 5c4e7dc24..200a42da5 100644 --- a/docs/gateway/index.md +++ b/docs/gateway/index.md @@ -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. diff --git a/docs/gateway/troubleshooting.md b/docs/gateway/troubleshooting.md index 93179e577..592d326cb 100644 --- a/docs/gateway/troubleshooting.md +++ b/docs/gateway/troubleshooting.md @@ -322,7 +322,7 @@ kill -9 # 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 diff --git a/docs/platforms/mac/bun.md b/docs/platforms/mac/bun.md index 9613fc711..e5295a231 100644 --- a/docs/platforms/mac/bun.md +++ b/docs/platforms/mac/bun.md @@ -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__=\"\""` -- The relay honors `__CLAWDBOT_VERSION__` / `CLAWDBOT_BUNDLED_VERSION` so `--version` doesn’t 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 can’t load some native Node addons like `sharp` (and we don’t 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}`. diff --git a/docs/platforms/mac/dev-setup.md b/docs/platforms/mac/dev-setup.md index 68b318974..d643e2324 100644 --- a/docs/platforms/mac/dev-setup.md +++ b/docs/platforms/mac/dev-setup.md @@ -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. diff --git a/docs/platforms/mac/signing.md b/docs/platforms/mac/signing.md index f8f67704d..677d96289 100644 --- a/docs/platforms/mac/signing.md +++ b/docs/platforms/mac/signing.md @@ -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 diff --git a/docs/platforms/macos.md b/docs/platforms/macos.md index 121a8dd93..a9f1b9869 100644 --- a/docs/platforms/macos.md +++ b/docs/platforms/macos.md @@ -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) diff --git a/docs/start/hubs.md b/docs/start/hubs.md index 4dea3e8f2..45cbe363c 100644 --- a/docs/start/hubs.md +++ b/docs/start/hubs.md @@ -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) diff --git a/scripts/codesign-mac-app.sh b/scripts/codesign-mac-app.sh index 8718d7f53..5b55d86e3 100755 --- a/scripts/codesign-mac-app.sh +++ b/scripts/codesign-mac-app.sh @@ -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 -cat > "$ENT_TMP_BUN" <<'PLIST' +cat > "$ENT_TMP_RUNTIME" <<'PLIST' @@ -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" diff --git a/scripts/package-mac-app.sh b/scripts/package-mac-app.sh index bf399e95c..6b7adb306 100755 --- a/scripts/package-mac-app.sh +++ b/scripts/package-mac-app.sh @@ -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" <&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" diff --git a/src/infra/path-env.ts b/src/infra/path-env.ts index a6cf36ed0..66b7a254e 100644 --- a/src/infra/path-env.ts +++ b/src/infra/path-env.ts @@ -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; diff --git a/src/macos/gateway-daemon.ts b/src/macos/gateway-daemon.ts index 47eb85154..912f6ff6e 100644 --- a/src/macos/gateway-daemon.ts +++ b/src/macos/gateway-daemon.ts @@ -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); diff --git a/src/macos/relay.ts b/src/macos/relay.ts index 65627999b..a8fd4a19b 100644 --- a/src/macos/relay.ts +++ b/src/macos/relay.ts @@ -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);