|
| 1 | +# bun-decompile |
| 2 | + |
| 3 | +Extract and de-minify source code from Bun-compiled executables. |
| 4 | + |
| 5 | +Originally developed to inspect [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code), which ships as a Bun-compiled binary. |
| 6 | + |
| 7 | +## Features |
| 8 | + |
| 9 | +- **Binary Extraction**: Parse Bun's embedded module graph to extract all bundled sources |
| 10 | +- **Sourcemap Recovery**: Automatically recover original TypeScript/JSX sources from embedded sourcemaps |
| 11 | +- **AI De-minification**: Use OpenAI or Anthropic to rename minified identifiers back to meaningful names |
| 12 | +- **Functional Equivalence**: Babel-based renaming guarantees the de-minified code works identically |
| 13 | + |
| 14 | +## Installation |
| 15 | + |
| 16 | +```bash |
| 17 | +# Install locally |
| 18 | +bun add @shepherdjerred/bun-decompile |
| 19 | + |
| 20 | +# Or install globally |
| 21 | +bun add -g @shepherdjerred/bun-decompile |
| 22 | +``` |
| 23 | + |
| 24 | +Requires [Bun](https://bun.sh) runtime. |
| 25 | + |
| 26 | +## Quick Start |
| 27 | + |
| 28 | +```bash |
| 29 | +# Extract sources from a compiled binary |
| 30 | +bun-decompile ./my-app -o ./extracted |
| 31 | + |
| 32 | +# De-minify with OpenAI (default) |
| 33 | +export OPENAI_API_KEY=sk-... |
| 34 | +bun-decompile ./my-app --deminify |
| 35 | + |
| 36 | +# De-minify with Anthropic Claude |
| 37 | +export ANTHROPIC_API_KEY=sk-ant-... |
| 38 | +bun-decompile ./my-app --deminify --provider anthropic |
| 39 | + |
| 40 | +# Batch mode (Anthropic only, 50% cheaper) |
| 41 | +bun-decompile ./my-app --deminify --provider anthropic --batch |
| 42 | + |
| 43 | +# De-minify a standalone JS file |
| 44 | +bun-decompile -f ./minified.js --deminify --yes |
| 45 | +``` |
| 46 | + |
| 47 | +## How It Works |
| 48 | + |
| 49 | +### Binary Parsing |
| 50 | + |
| 51 | +Bun embeds a module graph at the end of compiled executables. The parser: |
| 52 | + |
| 53 | +1. Searches backwards from file end for the Bun trailer signature (`packages by bun`) |
| 54 | +2. Reads the offsets structure (32 bytes before trailer) containing: |
| 55 | + - Total embedded data size |
| 56 | + - Pointer to module array |
| 57 | + - Entry point index |
| 58 | + - Compile-time arguments |
| 59 | +3. Iterates the module entries (40 bytes each) extracting: |
| 60 | + - Module path and contents |
| 61 | + - Sourcemap and bytecode (if present) |
| 62 | + - Loader type (js/ts/jsx/tsx/css/json/etc) |
| 63 | + - Module format (ESM/CJS) |
| 64 | + |
| 65 | +### Sourcemap Recovery |
| 66 | + |
| 67 | +When sourcemaps are embedded (compiled with `--sourcemap`), the tool extracts original source files including: |
| 68 | +- TypeScript with full type annotations |
| 69 | +- JSX/TSX templates |
| 70 | +- Original comments and formatting |
| 71 | + |
| 72 | +### AI De-minification Pipeline |
| 73 | + |
| 74 | +The de-minification process uses a novel approach that guarantees functional equivalence: |
| 75 | + |
| 76 | +1. **Call Graph Analysis**: Build a dependency graph of all functions in the source |
| 77 | +2. **Bottom-Up Processing**: Process leaf functions first, so parent functions see renamed callees |
| 78 | +3. **LLM Rename Suggestions**: The LLM analyzes function behavior and outputs JSON rename mappings: |
| 79 | + ```json |
| 80 | + { |
| 81 | + "processItems_40_120": { |
| 82 | + "functionName": "filterValidItems", |
| 83 | + "description": "Filters array to valid items", |
| 84 | + "renames": { "t": "items", "r": "predicate", "n": "result" } |
| 85 | + } |
| 86 | + } |
| 87 | + ``` |
| 88 | +4. **Babel Transformation**: Babel's `scope.rename()` applies mappings, handling all scope complexity |
| 89 | + |
| 90 | +This approach (inspired by [humanify](https://github.com/jehna/humanify)) means the LLM never outputs code directly—only suggestions—eliminating LLM-introduced bugs. |
| 91 | + |
| 92 | +## CLI Reference |
| 93 | + |
| 94 | +``` |
| 95 | +bun-decompile <binary> [options] |
| 96 | +bun-decompile --file <js-file> --deminify [options] |
| 97 | +``` |
| 98 | + |
| 99 | +### Options |
| 100 | + |
| 101 | +| Option | Description | |
| 102 | +|--------|-------------| |
| 103 | +| `-o, --output <dir>` | Output directory (default: `./decompiled`) | |
| 104 | +| `-f, --file <path>` | De-minify a JS file directly (skip binary extraction) | |
| 105 | +| `-v, --verbose` | Show detailed information | |
| 106 | +| `-q, --quiet` | Suppress progress display | |
| 107 | +| `-h, --help` | Show help message | |
| 108 | + |
| 109 | +### De-minification Options |
| 110 | + |
| 111 | +| Option | Description | |
| 112 | +|--------|-------------| |
| 113 | +| `--deminify` | Enable AI de-minification | |
| 114 | +| `--provider <name>` | LLM provider: `openai` or `anthropic` (default: openai) | |
| 115 | +| `--api-key <key>` | API key (or set `OPENAI_API_KEY` / `ANTHROPIC_API_KEY`) | |
| 116 | +| `--model <model>` | Model to use (default: gpt-5-nano) | |
| 117 | +| `--batch` | Use Anthropic batch API (50% cheaper, async) | |
| 118 | +| `--resume <batch-id>` | Resume a pending batch job | |
| 119 | +| `--no-cache` | Disable result caching | |
| 120 | +| `--concurrency <n>` | Parallel API requests (default: 3, max: 20) | |
| 121 | +| `--yes` | Skip cost confirmation prompt | |
| 122 | + |
| 123 | + |
| 124 | +## Programmatic API |
| 125 | + |
| 126 | +### Extraction |
| 127 | + |
| 128 | +```typescript |
| 129 | +import { |
| 130 | + decompileFile, |
| 131 | + extractToDirectory, |
| 132 | + getExtractionSummary, |
| 133 | +} from "@shepherdjerred/bun-decompile"; |
| 134 | + |
| 135 | +// Parse binary and extract module graph |
| 136 | +const result = await decompileFile("./my-app"); |
| 137 | + |
| 138 | +console.log(getExtractionSummary(result)); |
| 139 | +// Bun Version: 1.2.0 |
| 140 | +// Bundled Modules: 42 |
| 141 | +// Entry Point: /src/index.ts |
| 142 | +// Original Sources: 38 (recovered from sourcemap) |
| 143 | + |
| 144 | +// Write to disk |
| 145 | +await extractToDirectory(result, "./extracted"); |
| 146 | +``` |
| 147 | + |
| 148 | +### De-minification |
| 149 | + |
| 150 | +```typescript |
| 151 | +import { |
| 152 | + createConfig, |
| 153 | + Deminifier, |
| 154 | +} from "@shepherdjerred/bun-decompile"; |
| 155 | + |
| 156 | +const config = createConfig(process.env.OPENAI_API_KEY!, "./output", { |
| 157 | + provider: "openai", |
| 158 | + model: "gpt-5-nano", |
| 159 | + verbose: true, |
| 160 | +}); |
| 161 | + |
| 162 | +const deminifier = new Deminifier(config); |
| 163 | + |
| 164 | +// Estimate cost before processing |
| 165 | +const estimate = deminifier.estimateCost(minifiedSource); |
| 166 | +console.log(`Estimated cost: $${estimate.estimatedCost.toFixed(4)}`); |
| 167 | + |
| 168 | +// De-minify with progress tracking |
| 169 | +const result = await deminifier.deminifyFile(minifiedSource, { |
| 170 | + fileName: "bundle.js", |
| 171 | + onExtendedProgress: (progress) => { |
| 172 | + console.log(`${progress.phase}: ${progress.current}/${progress.total}`); |
| 173 | + }, |
| 174 | +}); |
| 175 | + |
| 176 | +console.log(deminifier.getStats()); |
| 177 | +``` |
| 178 | + |
| 179 | +### Key Exports |
| 180 | + |
| 181 | +- **Extraction**: `decompileFile`, `decompile`, `extractToDirectory`, `parseSourceMap` |
| 182 | +- **De-minification**: `Deminifier`, `createConfig`, `ClaudeClient`, `OpenAIClient` |
| 183 | +- **Types**: `DecompileResult`, `ModuleEntry`, `DeminifyConfig`, `CostEstimate` |
| 184 | + |
| 185 | +## Output Structure |
| 186 | + |
| 187 | +``` |
| 188 | +output/ |
| 189 | +├── metadata.json # Bun version, entry point, module counts |
| 190 | +├── bundled/ # Transpiled/bundled JS sources + sourcemaps |
| 191 | +├── original/ # Original TS/TSX sources (from sourcemaps) |
| 192 | +├── deminified/ # AI de-minified output |
| 193 | +├── bytecode/ # Pre-compiled bytecode (if present) |
| 194 | +└── cache/ # LLM response cache |
| 195 | +``` |
| 196 | + |
| 197 | +## Development |
| 198 | + |
| 199 | +```bash |
| 200 | +bun test # Run tests |
| 201 | +bun run typecheck # Type check |
| 202 | +``` |
| 203 | + |
| 204 | +### Project Structure |
| 205 | + |
| 206 | +- `src/index.ts` - CLI entry point |
| 207 | +- `src/lib/parser.ts` - Binary parsing |
| 208 | +- `src/lib/extractor.ts` - File extraction |
| 209 | +- `src/lib/deminify/` - AI de-minification (deminifier, babel-renamer, call-graph, LLM clients) |
| 210 | + |
| 211 | +## License |
| 212 | + |
| 213 | +GPL-3.0 |
0 commit comments