From 0c46076a5c85ec8c670948f2e1bebc68dc20494b Mon Sep 17 00:00:00 2001 From: Tim Beyer Date: Mon, 16 Mar 2026 00:19:10 +0100 Subject: [PATCH 1/9] chore: add task for embedding claw binary into clawctl Co-Authored-By: Claude Opus 4.6 (1M context) --- .../2026-03-16_0018_embed-claw-binary/TASK.md | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tasks/2026-03-16_0018_embed-claw-binary/TASK.md diff --git a/tasks/2026-03-16_0018_embed-claw-binary/TASK.md b/tasks/2026-03-16_0018_embed-claw-binary/TASK.md new file mode 100644 index 0000000..d0701c6 --- /dev/null +++ b/tasks/2026-03-16_0018_embed-claw-binary/TASK.md @@ -0,0 +1,42 @@ +# Fix `claw` binary bundling into `clawctl` + +## Status: In Progress + +## Scope + +Embed the `claw` guest CLI binary into the compiled `clawctl` executable using +Bun's `import ... with { type: "file" }` asset embedding. This makes the release +binary self-contained — no sibling `dist/claw` file needed. + +**Out of scope**: multi-binary VM assets, cross-platform builds. + +## Plan + +1. Create `packages/host-core/src/claw-binary.ts` — isolated module with the + embedded file import. +2. Update `packages/host-core/src/provision.ts` — replace `import.meta.dir` + path resolution with the embedded import. +3. Export `clawPath` from `packages/host-core/src/index.ts`. +4. Fix `build:release` in root `package.json` to build `claw` first. + +## Steps + +- [ ] Create `claw-binary.ts` +- [ ] Update `provision.ts` +- [ ] Add re-export to `index.ts` +- [ ] Fix `build:release` script +- [ ] Commit task + plan +- [ ] Commit implementation +- [ ] Verify lint/format + +## Notes + +- `import.meta.dir` in a compiled Bun binary points to the binary's directory, + not the source tree. The current relative path traversal breaks silently. +- Bun's embed assets feature returns the original path in dev mode and extracts + to a temp location in compiled mode — no code changes needed for dev vs prod. +- Binary size will grow from ~64MB to ~160MB (claw is ~96MB compiled). + +## Outcome + +_To be written on resolution._ From 3ced543638d95cf9492301655ef23985b6b783b8 Mon Sep 17 00:00:00 2001 From: Tim Beyer Date: Mon, 16 Mar 2026 00:19:17 +0100 Subject: [PATCH 2/9] fix: embed claw binary into clawctl using Bun asset imports Replace the broken `import.meta.dir` path resolution (which fails in compiled binaries) with Bun's `import ... with { type: "file" }` asset embedding. In dev mode, returns the original `dist/claw` path; in compiled mode, extracts to a temp location automatically. Also fixes `build:release` to build `claw` before `clawctl`, ensuring the asset exists at bundle time. Co-Authored-By: Claude Opus 4.6 (1M context) --- package.json | 2 +- packages/host-core/src/claw-binary.ts | 2 ++ packages/host-core/src/index.ts | 3 +++ packages/host-core/src/provision.ts | 8 +++----- 4 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 packages/host-core/src/claw-binary.ts diff --git a/package.json b/package.json index ad14172..1bde19e 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "format": "prettier --write .", "format:check": "prettier --check .", "test:vm": "CLAWCTL_VM_TESTS=1 bun test packages/host-core/tests/vm/", - "build:release": "bun build ./packages/cli/bin/cli.tsx --compile --target=bun-darwin-arm64 --outfile dist/clawctl", + "build:release": "bun run build:claw && bun build ./packages/cli/bin/cli.tsx --compile --target=bun-darwin-arm64 --outfile dist/clawctl", "release": "release-it" }, "dependencies": { diff --git a/packages/host-core/src/claw-binary.ts b/packages/host-core/src/claw-binary.ts new file mode 100644 index 0000000..760987a --- /dev/null +++ b/packages/host-core/src/claw-binary.ts @@ -0,0 +1,2 @@ +import clawPath from "../../../dist/claw" with { type: "file" }; +export { clawPath }; diff --git a/packages/host-core/src/index.ts b/packages/host-core/src/index.ts index 6e3ffc0..56575cc 100644 --- a/packages/host-core/src/index.ts +++ b/packages/host-core/src/index.ts @@ -33,6 +33,9 @@ export type { SecretRef, ResolvedSecretRef } from "./secrets.js"; export { provisionVM } from "./provision.js"; export type { ProvisionCallbacks, ProvisionFeatures } from "./provision.js"; +// Claw binary (embedded asset in compiled mode, direct path in dev mode) +export { clawPath } from "./claw-binary.js"; + // Verify export { verifyProvisioning } from "./verify.js"; export type { VerifyResult } from "./verify.js"; diff --git a/packages/host-core/src/provision.ts b/packages/host-core/src/provision.ts index af4040e..33b7f02 100644 --- a/packages/host-core/src/provision.ts +++ b/packages/host-core/src/provision.ts @@ -1,13 +1,11 @@ import { access, mkdir, writeFile } from "fs/promises"; import { constants } from "fs"; -import { join, resolve } from "path"; +import { join } from "path"; import type { VMConfig, ProvisionConfig } from "@clawctl/types"; import { CLAW_BIN_PATH, PROVISION_CONFIG_FILE } from "@clawctl/types"; import type { VMDriver, VMCreateOptions, OnLine } from "./drivers/types.js"; import { initGitRepo } from "./git.js"; - -/** Resolve the claw binary path from the monorepo root (host-core/src/ → ../../.. → dist/claw). */ -const DEFAULT_CLAW_BINARY = resolve(import.meta.dir, "..", "..", "..", "dist", "claw"); +import { clawPath } from "./claw-binary.js"; export interface ProvisionFeatures { onePassword: boolean; @@ -93,7 +91,7 @@ export async function provisionVM( config: VMConfig, callbacks: ProvisionCallbacks = {}, createOptions: VMCreateOptions = {}, - clawBinaryPath: string = DEFAULT_CLAW_BINARY, + clawBinaryPath: string = clawPath, features: ProvisionFeatures = { onePassword: false, tailscale: false }, ): Promise { const { onPhase, onStep, onLine } = callbacks; From 5bde930f360effcf8f96283f8baf979bbc5b6341 Mon Sep 17 00:00:00 2001 From: Tim Beyer Date: Mon, 16 Mar 2026 00:19:34 +0100 Subject: [PATCH 3/9] docs: mark embed-claw-binary task as resolved Co-Authored-By: Claude Opus 4.6 (1M context) --- .../2026-03-16_0018_embed-claw-binary/TASK.md | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tasks/2026-03-16_0018_embed-claw-binary/TASK.md b/tasks/2026-03-16_0018_embed-claw-binary/TASK.md index d0701c6..74388fd 100644 --- a/tasks/2026-03-16_0018_embed-claw-binary/TASK.md +++ b/tasks/2026-03-16_0018_embed-claw-binary/TASK.md @@ -1,6 +1,6 @@ # Fix `claw` binary bundling into `clawctl` -## Status: In Progress +## Status: Resolved ## Scope @@ -21,13 +21,13 @@ binary self-contained — no sibling `dist/claw` file needed. ## Steps -- [ ] Create `claw-binary.ts` -- [ ] Update `provision.ts` -- [ ] Add re-export to `index.ts` -- [ ] Fix `build:release` script -- [ ] Commit task + plan -- [ ] Commit implementation -- [ ] Verify lint/format +- [x] Create `claw-binary.ts` +- [x] Update `provision.ts` +- [x] Add re-export to `index.ts` +- [x] Fix `build:release` script +- [x] Commit task + plan +- [x] Commit implementation +- [x] Verify lint/format ## Notes @@ -39,4 +39,11 @@ binary self-contained — no sibling `dist/claw` file needed. ## Outcome -_To be written on resolution._ +All four changes delivered as planned: + +1. **`claw-binary.ts`** — isolated module with `import ... with { type: "file" }` for the claw binary +2. **`provision.ts`** — removed `import.meta.dir` + `resolve` path hack, uses `clawPath` from the embedded import; cleaned up unused `resolve` import +3. **`index.ts`** — re-exports `clawPath` for consumers that need to reference or override +4. **`package.json`** — `build:release` now runs `build:claw` first so the asset exists at bundle time + +Lint and format pass clean. No changes needed to callers (`headless.ts`, `create-vm.tsx`) since they pass `undefined` for `clawBinaryPath` and get the default. From 504d96bfdd7a707e95d546a78ede2cb2a00997b9 Mon Sep 17 00:00:00 2001 From: Tim Beyer Date: Mon, 16 Mar 2026 00:23:48 +0100 Subject: [PATCH 4/9] fix: materialize embedded claw binary to temp file for limactl In compiled mode, Bun's `import ... with { type: "file" }` returns a `/$bunfs/` virtual path. Bun's own fs polyfills can read it, but external tools like `limactl copy` cannot. Detect the virtual path and extract the binary to a real temp file before handing it to the driver. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/host-core/src/claw-binary.ts | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/host-core/src/claw-binary.ts b/packages/host-core/src/claw-binary.ts index 760987a..3286afd 100644 --- a/packages/host-core/src/claw-binary.ts +++ b/packages/host-core/src/claw-binary.ts @@ -1,2 +1,25 @@ -import clawPath from "../../../dist/claw" with { type: "file" }; -export { clawPath }; +import embeddedClawPath from "../../../dist/claw" with { type: "file" }; +import { readFileSync, writeFileSync, mkdtempSync } from "node:fs"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; + +/** + * In dev mode, the import resolves to the real `dist/claw` path. + * In compiled mode, Bun embeds the file and exposes it via a virtual + * `/$bunfs/` path that only Bun's own fs polyfills can read. External + * tools (like `limactl copy`) need a real filesystem path, so we + * extract the binary to a temp file. + */ +function resolveClawPath(): string { + if (!embeddedClawPath.startsWith("/$bunfs/")) { + return embeddedClawPath; + } + + const content = readFileSync(embeddedClawPath); + const dir = mkdtempSync(join(tmpdir(), "clawctl-")); + const outPath = join(dir, "claw"); + writeFileSync(outPath, content, { mode: 0o755 }); + return outPath; +} + +export const clawPath = resolveClawPath(); From 74621f1c8f3ed4872073d77263cba6bc4f937120 Mon Sep 17 00:00:00 2001 From: Tim Beyer Date: Mon, 16 Mar 2026 00:32:16 +0100 Subject: [PATCH 5/9] build: add --minify --sourcemap --bytecode to compiled builds Follows Bun's production deployment recommendations. Minify reduces JS payload size, sourcemaps preserve original locations in stack traces (zstd-compressed, zero cost until an error), and bytecode compilation moves parsing from runtime to build time for faster startup. Applied to build:claw, build, and build:release. Co-Authored-By: Claude Opus 4.6 (1M context) --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 1bde19e..ffb7bb1 100644 --- a/package.json +++ b/package.json @@ -11,14 +11,14 @@ }, "scripts": { "dev": "bun packages/cli/bin/cli.tsx", - "build:claw": "bun build ./packages/vm-cli/bin/claw.ts --compile --target=bun-linux-arm64 --outfile dist/claw", - "build": "bun run build:claw && bun build ./packages/cli/bin/cli.tsx --compile --outfile dist/clawctl", + "build:claw": "bun build ./packages/vm-cli/bin/claw.ts --compile --target=bun-linux-arm64 --minify --sourcemap --bytecode --outfile dist/claw", + "build": "bun run build:claw && bun build ./packages/cli/bin/cli.tsx --compile --minify --sourcemap --bytecode --outfile dist/clawctl", "test": "bun test", "lint": "eslint .", "format": "prettier --write .", "format:check": "prettier --check .", "test:vm": "CLAWCTL_VM_TESTS=1 bun test packages/host-core/tests/vm/", - "build:release": "bun run build:claw && bun build ./packages/cli/bin/cli.tsx --compile --target=bun-darwin-arm64 --outfile dist/clawctl", + "build:release": "bun run build:claw && bun build ./packages/cli/bin/cli.tsx --compile --target=bun-darwin-arm64 --minify --sourcemap --bytecode --outfile dist/clawctl", "release": "release-it" }, "dependencies": { From 694677dbc578fec9d06cf07c606fdfbeb9928ebe Mon Sep 17 00:00:00 2001 From: Tim Beyer Date: Mon, 16 Mar 2026 00:34:04 +0100 Subject: [PATCH 6/9] fix: drop --minify and --bytecode from clawctl builds Bun's minifier and bytecode compiler don't support top-level await, which yoga-layout (Ink dependency) uses. Keep all three flags on build:claw (no TLA in our code), use only --sourcemap for clawctl. Wrap claw.ts entry point in async IIFE to satisfy the minifier. Co-Authored-By: Claude Opus 4.6 (1M context) --- package.json | 4 ++-- packages/vm-cli/bin/claw.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ffb7bb1..fbabe6e 100644 --- a/package.json +++ b/package.json @@ -12,13 +12,13 @@ "scripts": { "dev": "bun packages/cli/bin/cli.tsx", "build:claw": "bun build ./packages/vm-cli/bin/claw.ts --compile --target=bun-linux-arm64 --minify --sourcemap --bytecode --outfile dist/claw", - "build": "bun run build:claw && bun build ./packages/cli/bin/cli.tsx --compile --minify --sourcemap --bytecode --outfile dist/clawctl", + "build": "bun run build:claw && bun build ./packages/cli/bin/cli.tsx --compile --sourcemap --outfile dist/clawctl", "test": "bun test", "lint": "eslint .", "format": "prettier --write .", "format:check": "prettier --check .", "test:vm": "CLAWCTL_VM_TESTS=1 bun test packages/host-core/tests/vm/", - "build:release": "bun run build:claw && bun build ./packages/cli/bin/cli.tsx --compile --target=bun-darwin-arm64 --minify --sourcemap --bytecode --outfile dist/clawctl", + "build:release": "bun run build:claw && bun build ./packages/cli/bin/cli.tsx --compile --target=bun-darwin-arm64 --sourcemap --outfile dist/clawctl", "release": "release-it" }, "dependencies": { diff --git a/packages/vm-cli/bin/claw.ts b/packages/vm-cli/bin/claw.ts index 8d2c8b6..092f71b 100644 --- a/packages/vm-cli/bin/claw.ts +++ b/packages/vm-cli/bin/claw.ts @@ -14,4 +14,6 @@ registerProvisionCommand(program); registerDoctorCommand(program); registerCheckpointCommand(program); -await program.parseAsync(); +(async () => { + await program.parseAsync(); +})(); From 941d3d1e97a6dab4eab995924cd10dd925ac80ce Mon Sep 17 00:00:00 2001 From: Tim Beyer Date: Mon, 16 Mar 2026 00:37:42 +0100 Subject: [PATCH 7/9] fix: use --format=esm to enable all production build flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bun's bytecode and minifier require --format=esm to support top-level await (fixed in Bun 1.3.9, oven-sh/bun#14412). With the flag set, all three production flags (--minify --sourcemap --bytecode) now work for both claw and clawctl builds. Reverts the async IIFE workaround in claw.ts — no longer needed. Co-Authored-By: Claude Opus 4.6 (1M context) --- package.json | 6 +++--- packages/vm-cli/bin/claw.ts | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index fbabe6e..c976842 100644 --- a/package.json +++ b/package.json @@ -11,14 +11,14 @@ }, "scripts": { "dev": "bun packages/cli/bin/cli.tsx", - "build:claw": "bun build ./packages/vm-cli/bin/claw.ts --compile --target=bun-linux-arm64 --minify --sourcemap --bytecode --outfile dist/claw", - "build": "bun run build:claw && bun build ./packages/cli/bin/cli.tsx --compile --sourcemap --outfile dist/clawctl", + "build:claw": "bun build ./packages/vm-cli/bin/claw.ts --compile --target=bun-linux-arm64 --format=esm --minify --sourcemap --bytecode --outfile dist/claw", + "build": "bun run build:claw && bun build ./packages/cli/bin/cli.tsx --compile --format=esm --minify --sourcemap --bytecode --outfile dist/clawctl", "test": "bun test", "lint": "eslint .", "format": "prettier --write .", "format:check": "prettier --check .", "test:vm": "CLAWCTL_VM_TESTS=1 bun test packages/host-core/tests/vm/", - "build:release": "bun run build:claw && bun build ./packages/cli/bin/cli.tsx --compile --target=bun-darwin-arm64 --sourcemap --outfile dist/clawctl", + "build:release": "bun run build:claw && bun build ./packages/cli/bin/cli.tsx --compile --target=bun-darwin-arm64 --format=esm --minify --sourcemap --bytecode --outfile dist/clawctl", "release": "release-it" }, "dependencies": { diff --git a/packages/vm-cli/bin/claw.ts b/packages/vm-cli/bin/claw.ts index 092f71b..8d2c8b6 100644 --- a/packages/vm-cli/bin/claw.ts +++ b/packages/vm-cli/bin/claw.ts @@ -14,6 +14,4 @@ registerProvisionCommand(program); registerDoctorCommand(program); registerCheckpointCommand(program); -(async () => { - await program.parseAsync(); -})(); +await program.parseAsync(); From ee8764f1118c541bcaec87e0a5c2586ce152a0a9 Mon Sep 17 00:00:00 2001 From: Tim Beyer Date: Mon, 16 Mar 2026 00:43:04 +0100 Subject: [PATCH 8/9] refactor: replace inline build commands with scripts/build.ts Move build configuration into a proper Bun.build() script. Shared options (format, minify, sourcemap, bytecode) are defined once. Sequencing (claw before clawctl) is handled in code, not shell &&. Usage: bun scripts/build.ts [claw|release|all] Co-Authored-By: Claude Opus 4.6 (1M context) --- package.json | 6 ++-- scripts/build.ts | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 scripts/build.ts diff --git a/package.json b/package.json index c976842..2b28c14 100644 --- a/package.json +++ b/package.json @@ -11,14 +11,14 @@ }, "scripts": { "dev": "bun packages/cli/bin/cli.tsx", - "build:claw": "bun build ./packages/vm-cli/bin/claw.ts --compile --target=bun-linux-arm64 --format=esm --minify --sourcemap --bytecode --outfile dist/claw", - "build": "bun run build:claw && bun build ./packages/cli/bin/cli.tsx --compile --format=esm --minify --sourcemap --bytecode --outfile dist/clawctl", + "build:claw": "bun scripts/build.ts claw", + "build": "bun scripts/build.ts", "test": "bun test", "lint": "eslint .", "format": "prettier --write .", "format:check": "prettier --check .", "test:vm": "CLAWCTL_VM_TESTS=1 bun test packages/host-core/tests/vm/", - "build:release": "bun run build:claw && bun build ./packages/cli/bin/cli.tsx --compile --target=bun-darwin-arm64 --format=esm --minify --sourcemap --bytecode --outfile dist/clawctl", + "build:release": "bun scripts/build.ts release", "release": "release-it" }, "dependencies": { diff --git a/scripts/build.ts b/scripts/build.ts new file mode 100644 index 0000000..23cc3bd --- /dev/null +++ b/scripts/build.ts @@ -0,0 +1,71 @@ +/** + * Build script for clawctl binaries. + * + * Usage: + * bun scripts/build.ts Build claw + clawctl (current platform) + * bun scripts/build.ts claw Build claw only (linux-arm64) + * bun scripts/build.ts release Build claw + clawctl (darwin-arm64) + */ + +const SHARED = { + format: "esm", + minify: true, + sourcemap: "linked", + bytecode: true, +} as const; + +async function buildClaw() { + console.log("Building claw (linux-arm64)..."); + const result = await Bun.build({ + entrypoints: ["./packages/vm-cli/bin/claw.ts"], + compile: { + target: "bun-linux-arm64", + outfile: "./dist/claw", + }, + ...SHARED, + }); + if (!result.success) { + console.error("claw build failed:"); + for (const log of result.logs) console.error(log); + process.exit(1); + } + console.log(" ✓ dist/claw"); +} + +async function buildClawctl(target?: string) { + console.log(`Building clawctl${target ? ` (${target})` : ""}...`); + const result = await Bun.build({ + entrypoints: ["./packages/cli/bin/cli.tsx"], + compile: { + ...(target && { target: target as Bun.Target }), + outfile: "./dist/clawctl", + }, + ...SHARED, + }); + if (!result.success) { + console.error("clawctl build failed:"); + for (const log of result.logs) console.error(log); + process.exit(1); + } + console.log(" ✓ dist/clawctl"); +} + +const command = process.argv[2] ?? "all"; + +switch (command) { + case "claw": + await buildClaw(); + break; + case "release": + await buildClaw(); + await buildClawctl("bun-darwin-arm64"); + break; + case "all": + await buildClaw(); + await buildClawctl(); + break; + default: + console.error(`Unknown command: ${command}`); + console.error("Usage: bun scripts/build.ts [claw|release|all]"); + process.exit(1); +} From 1b584c7812994eb0faf6dd7a79390b508859cf0e Mon Sep 17 00:00:00 2001 From: Tim Beyer Date: Mon, 16 Mar 2026 00:44:09 +0100 Subject: [PATCH 9/9] fix: use inline sourcemaps for compiled binaries With --compile, linked sourcemaps produce stray .map files that won't travel with the standalone binary. Use inline to embed them directly. (Bun still creates the external files as a side effect, but they're gitignored and the maps are embedded in the binary either way.) Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/build.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build.ts b/scripts/build.ts index 23cc3bd..75e4b49 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -10,7 +10,7 @@ const SHARED = { format: "esm", minify: true, - sourcemap: "linked", + sourcemap: "inline", bytecode: true, } as const;