Created
April 27, 2026 11:53
-
-
Save sorrycc/27944a584ad9c22e5ffc0c90fa33f007 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bun | |
| import { mkdir, writeFile, readFile, rm } from "node:fs/promises"; | |
| import { existsSync } from "node:fs"; | |
| import { join, dirname, basename } from "node:path"; | |
| const BUCKET = | |
| "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases"; | |
| const API = | |
| "https://storage.googleapis.com/storage/v1/b/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/o"; | |
| interface ParsedArgs { | |
| help: boolean; | |
| version?: string; | |
| out?: string; | |
| force: boolean; | |
| keepBin: boolean; | |
| noFormat: boolean; | |
| } | |
| function parseArgs(): ParsedArgs { | |
| const argv = Bun.argv.slice(2); | |
| const a: ParsedArgs = { | |
| help: false, | |
| force: false, | |
| keepBin: false, | |
| noFormat: false, | |
| }; | |
| for (let i = 0; i < argv.length; i++) { | |
| const v = argv[i]; | |
| if (v === "-h" || v === "--help") a.help = true; | |
| else if (v === "--force") a.force = true; | |
| else if (v === "--keep-bin") a.keepBin = true; | |
| else if (v === "--no-format") a.noFormat = true; | |
| else if (v === "--version") a.version = argv[++i]; | |
| else if (v === "--out") a.out = argv[++i]; | |
| else if (v.startsWith("--version=")) a.version = v.slice(10); | |
| else if (v.startsWith("--out=")) a.out = v.slice(6); | |
| else throw new Error(`unknown argument: ${v}`); | |
| } | |
| return a; | |
| } | |
| function showHelp(): void { | |
| console.log(` | |
| Usage: bun scripts/unbundle-claude-code.ts [options] | |
| Downloads the latest Claude Code binary from Anthropic's CDN, extracts the | |
| embedded Bun standalone module graph (\`__BUN\` Mach-O segment), and formats | |
| the main bundle with oxfmt. Output goes to ./<version>-unbundled/. | |
| Options: | |
| -h, --help Show this help | |
| --version VER Use a specific version instead of latest (e.g. 2.1.119) | |
| --out DIR Output directory (default: ./<version>-unbundled) | |
| --force Re-extract even if output dir exists | |
| --keep-bin Keep the downloaded binary alongside the output | |
| --no-format Skip the oxfmt step | |
| Examples: | |
| bun scripts/unbundle-claude-code.ts | |
| bun scripts/unbundle-claude-code.ts --version 2.1.119 | |
| bun scripts/unbundle-claude-code.ts --out /tmp/cc --force | |
| `); | |
| } | |
| function detectPlatform(): string { | |
| const os = process.platform; | |
| const arch = process.arch; | |
| if (os === "darwin" && arch === "arm64") return "darwin-arm64"; | |
| if (os === "darwin" && arch === "x64") return "darwin-x64"; | |
| if (os === "linux" && arch === "x64") return "linux-x64"; | |
| if (os === "linux" && arch === "arm64") return "linux-arm64"; | |
| throw new Error(`unsupported platform: ${os}-${arch}`); | |
| } | |
| function semverCmp(a: string, b: string): number { | |
| const pa = a.split(".").map(Number); | |
| const pb = b.split(".").map(Number); | |
| for (let i = 0; i < Math.max(pa.length, pb.length); i++) { | |
| const x = pa[i] ?? 0, | |
| y = pb[i] ?? 0; | |
| if (x !== y) return x - y; | |
| } | |
| return 0; | |
| } | |
| async function fetchLatestVersion(): Promise<string> { | |
| const url = `${API}?prefix=claude-code-releases/2.&delimiter=/&maxResults=500`; | |
| const res = await fetch(url); | |
| if (!res.ok) throw new Error(`version listing failed: ${res.status}`); | |
| const data = (await res.json()) as { prefixes?: string[] }; | |
| const versions = (data.prefixes ?? []) | |
| .map((p) => p.replace("claude-code-releases/", "").replace(/\/$/, "")) | |
| .filter((v) => /^\d+\.\d+\.\d+$/.test(v)) | |
| .sort(semverCmp); | |
| if (!versions.length) throw new Error("no versions found"); | |
| return versions[versions.length - 1]; | |
| } | |
| async function fetchManifest( | |
| version: string, | |
| ): Promise<{ platforms: Record<string, { checksum: string; size: number }> }> { | |
| const res = await fetch(`${BUCKET}/${version}/manifest.json`); | |
| if (!res.ok) throw new Error(`manifest fetch failed: ${res.status}`); | |
| return (await res.json()) as any; | |
| } | |
| async function sha256Hex(buf: Uint8Array): Promise<string> { | |
| const h = await crypto.subtle.digest("SHA-256", buf); | |
| return Array.from(new Uint8Array(h)) | |
| .map((b) => b.toString(16).padStart(2, "0")) | |
| .join(""); | |
| } | |
| async function downloadBinary( | |
| version: string, | |
| platform: string, | |
| destPath: string, | |
| ): Promise<void> { | |
| const manifest = await fetchManifest(version); | |
| const info = manifest.platforms?.[platform]; | |
| if (!info) throw new Error(`platform ${platform} not in manifest`); | |
| const url = `${BUCKET}/${version}/${platform}/claude`; | |
| console.log( | |
| `→ downloading ${url} (${(info.size / 1048576).toFixed(1)} MiB)`, | |
| ); | |
| await mkdir(dirname(destPath), { recursive: true }); | |
| // Use aria2c for parallel-segment downloads (much faster than fetch over a | |
| // single connection on slow links). Falls back to fetch if aria2c is missing. | |
| const aria = Bun.which("aria2c"); | |
| if (aria) { | |
| const proc = Bun.spawn({ | |
| cmd: [ | |
| aria, | |
| "-x", "16", // 16 connections per server | |
| "-s", "16", // 16 splits | |
| "-k", "1M", // 1 MiB chunk size | |
| "--allow-overwrite=true", | |
| "--auto-file-renaming=false", | |
| "--summary-interval=1", | |
| "--console-log-level=warn", | |
| "-d", dirname(destPath), | |
| "-o", basename(destPath), | |
| url, | |
| ], | |
| stdout: "inherit", | |
| stderr: "inherit", | |
| }); | |
| const code = await proc.exited; | |
| if (code !== 0) throw new Error(`aria2c exited with code ${code}`); | |
| } else { | |
| console.log(" (aria2c not found; falling back to fetch)"); | |
| const res = await fetch(url); | |
| if (!res.ok) throw new Error(`download failed: ${res.status}`); | |
| await writeFile(destPath, new Uint8Array(await res.arrayBuffer())); | |
| } | |
| // Verify size + sha256 from disk | |
| const buf = new Uint8Array(await readFile(destPath)); | |
| if (buf.length !== info.size) | |
| throw new Error(`size mismatch: got ${buf.length}, want ${info.size}`); | |
| const got = await sha256Hex(buf); | |
| if (got !== info.checksum) | |
| throw new Error(`checksum mismatch: got ${got}, want ${info.checksum}`); | |
| console.log(` ok sha256=${got.slice(0, 16)}…`); | |
| } | |
| interface BunSegment { | |
| fileOffset: number; | |
| size: number; | |
| } | |
| const MH_MAGIC_64 = 0xfeedfacf; | |
| const LC_SEGMENT_64 = 0x19; | |
| function findBunSegmentMachO(buf: Uint8Array): BunSegment { | |
| const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength); | |
| const magic = dv.getUint32(0, true); | |
| if (magic !== MH_MAGIC_64) | |
| throw new Error( | |
| `not a thin Mach-O 64-bit binary (magic=0x${magic.toString(16)})`, | |
| ); | |
| const ncmds = dv.getUint32(16, true); | |
| let off = 32; | |
| for (let i = 0; i < ncmds; i++) { | |
| const cmd = dv.getUint32(off, true); | |
| const cmdsize = dv.getUint32(off + 4, true); | |
| if (cmd === LC_SEGMENT_64) { | |
| const segname = new TextDecoder() | |
| .decode(buf.subarray(off + 8, off + 24)) | |
| .replace(/\0+$/, ""); | |
| if (segname === "__BUN") { | |
| const fileoff = Number(dv.getBigUint64(off + 40, true)); | |
| const filesize = Number(dv.getBigUint64(off + 48, true)); | |
| return { fileOffset: fileoff, size: filesize }; | |
| } | |
| } | |
| off += cmdsize; | |
| } | |
| throw new Error("__BUN segment not found"); | |
| } | |
| const MAGIC = new TextEncoder().encode("\n---- Bun! ----\n"); | |
| function lastIndexOf(haystack: Uint8Array, needle: Uint8Array): number { | |
| outer: for (let i = haystack.length - needle.length; i >= 0; i--) { | |
| for (let j = 0; j < needle.length; j++) { | |
| if (haystack[i + j] !== needle[j]) continue outer; | |
| } | |
| return i; | |
| } | |
| return -1; | |
| } | |
| const LOADERS: Record<number, string> = { | |
| 0: "jsx", | |
| 1: "js", | |
| 2: "ts", | |
| 3: "tsx", | |
| 4: "css", | |
| 5: "file", | |
| 6: "json", | |
| 7: "jsonc", | |
| 8: "toml", | |
| 9: "wasm", | |
| 10: "napi", | |
| 11: "base64", | |
| 12: "dataurl", | |
| 13: "text", | |
| 14: "bunsh", | |
| 15: "sqlite", | |
| 16: "sqlite_embedded", | |
| 17: "html", | |
| 18: "yaml", | |
| }; | |
| const ENC: Record<number, string> = { 0: "binary", 1: "latin1", 2: "utf8" }; | |
| const MF: Record<number, string> = { 0: "none", 1: "esm", 2: "cjs" }; | |
| const SIDE: Record<number, string> = { 0: "server", 1: "client" }; | |
| const EXT: Record<string, string> = { | |
| jsx: ".jsx", | |
| js: ".js", | |
| ts: ".ts", | |
| tsx: ".tsx", | |
| wasm: ".wasm", | |
| json: ".json", | |
| napi: ".node", | |
| text: ".txt", | |
| css: ".css", | |
| html: ".html", | |
| yaml: ".yaml", | |
| toml: ".toml", | |
| }; | |
| interface BunModule { | |
| index: number; | |
| virtualPath: string; | |
| relPath: string; | |
| loader: string; | |
| encoding: string; | |
| moduleFormat: string; | |
| side: string; | |
| contents: Uint8Array; | |
| sourcemap?: Uint8Array; | |
| bytecode?: Uint8Array; | |
| moduleInfo?: Uint8Array; | |
| } | |
| function parseBunSegment(seg: Uint8Array): BunModule[] { | |
| const dv = new DataView(seg.buffer, seg.byteOffset, seg.byteLength); | |
| const magicPos = lastIndexOf(seg, MAGIC); | |
| if (magicPos < 0) throw new Error("Bun trailer magic not found"); | |
| const offsetsAt = magicPos - 32; | |
| // Offsets struct: byte_count u64, modules_ptr {off u32, len u32}, | |
| // entry_id u32, argv_ptr {off u32, len u32}, flags u32 = 32 bytes. | |
| const mpOff = dv.getUint32(offsetsAt + 8, true); | |
| const mpLen = dv.getUint32(offsetsAt + 12, true); | |
| const MOD = 52; | |
| // The modules block has an 8-byte header before the first 52-byte entry. | |
| const headerSize = 8; | |
| const count = Math.floor((mpLen - headerSize) / MOD); | |
| // Pre-scan all "/$bunfs/root/..." virtual paths in the byte buffer so we can | |
| // label each module by its actual path. The module struct's `name` field is | |
| // packed but the `bpath` (bytecode_origin_path) of the entry-point — and | |
| // a copy of every shim path — is laid out as null-terminated strings. | |
| const vpaths: { start: number; end: number; path: string }[] = []; | |
| const td = new TextDecoder("utf-8", { fatal: false }); | |
| const re = /\/\$bunfs\/root\/[\w./_-]+/g; | |
| // Decode as latin1 so byte indices align with UTF-8 ASCII paths. | |
| const asLatin1 = new TextDecoder("latin1").decode(seg); | |
| for (const m of asLatin1.matchAll(re)) { | |
| vpaths.push({ | |
| start: m.index!, | |
| end: m.index! + m[0].length, | |
| path: m[0], | |
| }); | |
| } | |
| function labelFor(co: number, fallback: number): string { | |
| let best: { start: number; end: number; path: string } | null = null; | |
| for (const v of vpaths) { | |
| // The path string ends at or just before the content start (the inline | |
| // 8-byte filename-tail prefix overlaps the path's last 7 chars + \0). | |
| if (v.end <= co + 8 && (!best || v.end > best.end)) best = v; | |
| } | |
| return best ? best.path : `/$bunfs/root/module_${fallback}`; | |
| } | |
| const out: BunModule[] = []; | |
| for (let i = 0; i < count; i++) { | |
| const base = mpOff + headerSize + i * MOD; | |
| const co = dv.getUint32(base + 8, true); | |
| const cl = dv.getUint32(base + 12, true); | |
| const so = dv.getUint32(base + 16, true); | |
| const sl = dv.getUint32(base + 20, true); | |
| const bco = dv.getUint32(base + 24, true); | |
| const bcl = dv.getUint32(base + 28, true); | |
| const mio = dv.getUint32(base + 32, true); | |
| const mil = dv.getUint32(base + 36, true); | |
| const enc = seg[base + 48]; | |
| const loader = seg[base + 49]; | |
| const mf = seg[base + 50]; | |
| const side = seg[base + 51]; | |
| // Each content region begins with an 8-byte inline `<name_tail>\0` prefix | |
| // that is NOT counted in `cl`. Real content = seg[co+8 : co+8+cl]. | |
| const contents = seg.slice(co + 8, co + 8 + cl); | |
| const sourcemap = sl ? seg.slice(so + 8, so + 8 + sl) : undefined; | |
| const bytecode = bcl ? seg.slice(bco, bco + bcl) : undefined; | |
| const moduleInfo = mil ? seg.slice(mio, mio + mil) : undefined; | |
| const loaderName = LOADERS[loader] ?? `loader${loader}`; | |
| const vp = labelFor(co, i); | |
| let rel = vp.replace(/^\/\$bunfs\/root\//, ""); | |
| if (!/\.[^./]+$/.test(rel)) rel += EXT[loaderName] ?? ".bin"; | |
| out.push({ | |
| index: i, | |
| virtualPath: vp, | |
| relPath: rel, | |
| loader: loaderName, | |
| encoding: ENC[enc] ?? String(enc), | |
| moduleFormat: MF[mf] ?? String(mf), | |
| side: SIDE[side] ?? String(side), | |
| contents, | |
| sourcemap, | |
| bytecode, | |
| moduleInfo, | |
| }); | |
| } | |
| return out; | |
| } | |
| async function writeModules(modules: BunModule[], extractDir: string) { | |
| for (const m of modules) { | |
| const target = join(extractDir, m.relPath); | |
| await mkdir(dirname(target), { recursive: true }); | |
| await writeFile(target, m.contents); | |
| if (m.sourcemap) await writeFile(`${target}.map`, m.sourcemap); | |
| if (m.bytecode) await writeFile(`${target}.jsc_bytecode.bin`, m.bytecode); | |
| if (m.moduleInfo) await writeFile(`${target}.module_info.bin`, m.moduleInfo); | |
| console.log( | |
| ` [${m.index}] ${m.relPath} ${m.contents.length.toLocaleString()} B ${m.loader}` + | |
| (m.bytecode ? ` (+${m.bytecode.length.toLocaleString()}B bytecode)` : ""), | |
| ); | |
| } | |
| } | |
| async function runOxfmt(file: string): Promise<void> { | |
| console.log(`→ oxfmt ${file}`); | |
| const proc = Bun.spawn({ | |
| cmd: ["npx", "--yes", "oxfmt", file], | |
| stdout: "inherit", | |
| stderr: "inherit", | |
| }); | |
| const code = await proc.exited; | |
| if (code !== 0) throw new Error(`oxfmt exited with code ${code}`); | |
| } | |
| async function main(): Promise<void> { | |
| const args = parseArgs(); | |
| if (args.help) { | |
| showHelp(); | |
| return; | |
| } | |
| const platform = detectPlatform(); | |
| const version = args.version ?? (await fetchLatestVersion()); | |
| const outDir = args.out ?? join(process.cwd(), `${version}-unbundled`); | |
| console.log(`claude-code ${version} platform=${platform} out=${outDir}`); | |
| if (existsSync(outDir) && !args.force) { | |
| console.log("output dir already exists — skipping (use --force to redo)"); | |
| return; | |
| } | |
| if (existsSync(outDir) && args.force) { | |
| await rm(outDir, { recursive: true, force: true }); | |
| } | |
| if (platform !== "darwin-arm64" && platform !== "darwin-x64") { | |
| throw new Error( | |
| `only darwin (Mach-O) is supported in this script; got ${platform}`, | |
| ); | |
| } | |
| await mkdir(outDir, { recursive: true }); | |
| const binPath = join(outDir, `claude-${version}`); | |
| await downloadBinary(version, platform, binPath); | |
| console.log(`→ parsing Mach-O ${binPath}`); | |
| const buf = new Uint8Array(await readFile(binPath)); | |
| const seg = findBunSegmentMachO(buf); | |
| console.log( | |
| ` __BUN segment: fileoff=${seg.fileOffset.toLocaleString()} size=${seg.size.toLocaleString()}`, | |
| ); | |
| const segData = buf.subarray(seg.fileOffset, seg.fileOffset + seg.size); | |
| const modules = parseBunSegment(segData); | |
| console.log(`→ extracting ${modules.length} modules`); | |
| const extractDir = join(outDir, "extracted"); | |
| await writeModules(modules, extractDir); | |
| // Write a compact manifest | |
| const manifest = { | |
| version, | |
| platform, | |
| extractedAt: new Date().toISOString(), | |
| modules: modules.map((m) => ({ | |
| index: m.index, | |
| virtual: m.virtualPath, | |
| path: m.relPath, | |
| loader: m.loader, | |
| encoding: m.encoding, | |
| moduleFormat: m.moduleFormat, | |
| side: m.side, | |
| contentsLen: m.contents.length, | |
| sourcemapLen: m.sourcemap?.length ?? 0, | |
| bytecodeLen: m.bytecode?.length ?? 0, | |
| moduleInfoLen: m.moduleInfo?.length ?? 0, | |
| })), | |
| }; | |
| await writeFile( | |
| join(outDir, "manifest.json"), | |
| JSON.stringify(manifest, null, 2), | |
| ); | |
| if (!args.noFormat) { | |
| const cliJs = join(extractDir, "src/entrypoints/cli.js"); | |
| if (existsSync(cliJs)) await runOxfmt(cliJs); | |
| else console.warn(`! cli.js not found at ${cliJs}; skipping oxfmt`); | |
| } | |
| if (!args.keepBin) { | |
| await rm(binPath, { force: true }); | |
| } | |
| console.log(`✓ done. output: ${outDir}`); | |
| } | |
| main().catch((err) => { | |
| console.error("Error:", err.message); | |
| process.exit(1); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment