claude-depester 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,530 @@
1
+ /**
2
+ * Bun binary extraction and repacking utilities
3
+ * Based on tweakcc's approach - proper Bun binary structure handling
4
+ */
5
+
6
+ const LIEF = require('node-lief');
7
+
8
+ // Bun trailer that marks the end of embedded data
9
+ const BUN_TRAILER = Buffer.from('\n---- Bun! ----\n');
10
+
11
+ // Size constants for binary structures
12
+ const SIZEOF_OFFSETS = 32;
13
+ const SIZEOF_STRING_POINTER = 8;
14
+ const SIZEOF_MODULE = 4 * SIZEOF_STRING_POINTER + 4;
15
+
16
+ /**
17
+ * Parse a StringPointer from buffer at offset
18
+ */
19
+ function parseStringPointer(buffer, offset) {
20
+ return {
21
+ offset: buffer.readUInt32LE(offset),
22
+ length: buffer.readUInt32LE(offset + 4),
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Get content from buffer using StringPointer
28
+ */
29
+ function getStringPointerContent(buffer, stringPointer) {
30
+ return buffer.subarray(
31
+ stringPointer.offset,
32
+ stringPointer.offset + stringPointer.length
33
+ );
34
+ }
35
+
36
+ /**
37
+ * Parse BunOffsets structure
38
+ */
39
+ function parseOffsets(buffer) {
40
+ let pos = 0;
41
+ const byteCount = buffer.readBigUInt64LE(pos);
42
+ pos += 8;
43
+ const modulesPtr = parseStringPointer(buffer, pos);
44
+ pos += 8;
45
+ const entryPointId = buffer.readUInt32LE(pos);
46
+ pos += 4;
47
+ const compileExecArgvPtr = parseStringPointer(buffer, pos);
48
+
49
+ return { byteCount, modulesPtr, entryPointId, compileExecArgvPtr };
50
+ }
51
+
52
+ /**
53
+ * Parse a compiled module from buffer
54
+ */
55
+ function parseCompiledModule(buffer, offset) {
56
+ let pos = offset;
57
+ const name = parseStringPointer(buffer, pos);
58
+ pos += 8;
59
+ const contents = parseStringPointer(buffer, pos);
60
+ pos += 8;
61
+ const sourcemap = parseStringPointer(buffer, pos);
62
+ pos += 8;
63
+ const bytecode = parseStringPointer(buffer, pos);
64
+ pos += 8;
65
+ const encoding = buffer.readUInt8(pos);
66
+ pos += 1;
67
+ const loader = buffer.readUInt8(pos);
68
+ pos += 1;
69
+ const moduleFormat = buffer.readUInt8(pos);
70
+ pos += 1;
71
+ const side = buffer.readUInt8(pos);
72
+
73
+ return { name, contents, sourcemap, bytecode, encoding, loader, moduleFormat, side };
74
+ }
75
+
76
+ /**
77
+ * Check if module name is the claude entrypoint
78
+ */
79
+ function isClaudeModule(moduleName) {
80
+ return (
81
+ moduleName.endsWith('/claude') ||
82
+ moduleName === 'claude' ||
83
+ moduleName.endsWith('/claude.exe') ||
84
+ moduleName === 'claude.exe'
85
+ );
86
+ }
87
+
88
+ /**
89
+ * Iterate over modules in Bun data
90
+ */
91
+ function mapModules(bunData, bunOffsets, visitor) {
92
+ const modulesListBytes = getStringPointerContent(bunData, bunOffsets.modulesPtr);
93
+ const modulesListCount = Math.floor(modulesListBytes.length / SIZEOF_MODULE);
94
+
95
+ for (let i = 0; i < modulesListCount; i++) {
96
+ const offset = i * SIZEOF_MODULE;
97
+ const module = parseCompiledModule(modulesListBytes, offset);
98
+ const moduleName = getStringPointerContent(bunData, module.name).toString('utf-8');
99
+
100
+ const result = visitor(module, moduleName, i);
101
+ if (result !== undefined) {
102
+ return result;
103
+ }
104
+ }
105
+
106
+ return undefined;
107
+ }
108
+
109
+ /**
110
+ * Parse Bun data blob that contains: [data][offsets][trailer]
111
+ */
112
+ function parseBunDataBlob(bunDataContent) {
113
+ if (bunDataContent.length < SIZEOF_OFFSETS + BUN_TRAILER.length) {
114
+ throw new Error('BUN data is too small');
115
+ }
116
+
117
+ // Verify trailer
118
+ const trailerStart = bunDataContent.length - BUN_TRAILER.length;
119
+ const trailerBytes = bunDataContent.subarray(trailerStart);
120
+
121
+ if (!trailerBytes.equals(BUN_TRAILER)) {
122
+ throw new Error('BUN trailer not found');
123
+ }
124
+
125
+ // Parse Offsets
126
+ const offsetsStart = bunDataContent.length - SIZEOF_OFFSETS - BUN_TRAILER.length;
127
+ const offsetsBytes = bunDataContent.subarray(offsetsStart, offsetsStart + SIZEOF_OFFSETS);
128
+ const bunOffsets = parseOffsets(offsetsBytes);
129
+
130
+ return { bunOffsets, bunData: bunDataContent };
131
+ }
132
+
133
+ /**
134
+ * Extract Bun data from section (MachO/PE format)
135
+ */
136
+ function extractBunDataFromSection(sectionData) {
137
+ if (sectionData.length < 4) {
138
+ throw new Error('Section data too small');
139
+ }
140
+
141
+ // Try u32 header (old format)
142
+ const bunDataSizeU32 = sectionData.readUInt32LE(0);
143
+ const expectedLengthU32 = 4 + bunDataSizeU32;
144
+
145
+ // Try u64 header (new format)
146
+ const bunDataSizeU64 = sectionData.length >= 8 ? Number(sectionData.readBigUInt64LE(0)) : 0;
147
+ const expectedLengthU64 = 8 + bunDataSizeU64;
148
+
149
+ let headerSize, bunDataSize;
150
+
151
+ // Check which format matches
152
+ if (sectionData.length >= 8 && expectedLengthU64 <= sectionData.length && expectedLengthU64 >= sectionData.length - 4096) {
153
+ headerSize = 8;
154
+ bunDataSize = bunDataSizeU64;
155
+ } else if (expectedLengthU32 <= sectionData.length && expectedLengthU32 >= sectionData.length - 4096) {
156
+ headerSize = 4;
157
+ bunDataSize = bunDataSizeU32;
158
+ } else {
159
+ throw new Error('Cannot determine section header format');
160
+ }
161
+
162
+ const bunDataContent = sectionData.subarray(headerSize, headerSize + bunDataSize);
163
+ const { bunOffsets, bunData } = parseBunDataBlob(bunDataContent);
164
+
165
+ return { bunOffsets, bunData, sectionHeaderSize: headerSize };
166
+ }
167
+
168
+ /**
169
+ * Extract Bun data from ELF overlay
170
+ */
171
+ function extractBunDataFromELFOverlay(elfBinary) {
172
+ if (!elfBinary.hasOverlay) {
173
+ throw new Error('ELF binary has no overlay data');
174
+ }
175
+
176
+ const overlayData = elfBinary.overlay;
177
+
178
+ if (overlayData.length < BUN_TRAILER.length + 8 + SIZEOF_OFFSETS) {
179
+ throw new Error('ELF overlay data is too small');
180
+ }
181
+
182
+ // Read totalByteCount from last 8 bytes
183
+ const totalByteCount = overlayData.readBigUInt64LE(overlayData.length - 8);
184
+
185
+ // Verify trailer
186
+ const trailerStart = overlayData.length - 8 - BUN_TRAILER.length;
187
+ const trailerBytes = overlayData.subarray(trailerStart, overlayData.length - 8);
188
+
189
+ if (!trailerBytes.equals(BUN_TRAILER)) {
190
+ throw new Error('BUN trailer not found in ELF overlay');
191
+ }
192
+
193
+ // Parse Offsets
194
+ const offsetsStart = overlayData.length - 8 - BUN_TRAILER.length - SIZEOF_OFFSETS;
195
+ const offsetsBytes = overlayData.subarray(offsetsStart, overlayData.length - 8 - BUN_TRAILER.length);
196
+ const bunOffsets = parseOffsets(offsetsBytes);
197
+
198
+ const byteCount = typeof bunOffsets.byteCount === 'bigint'
199
+ ? bunOffsets.byteCount
200
+ : BigInt(bunOffsets.byteCount);
201
+
202
+ // Extract data region
203
+ const tailDataLen = 8 + BUN_TRAILER.length + SIZEOF_OFFSETS;
204
+ const dataStart = overlayData.length - tailDataLen - Number(byteCount);
205
+ const dataRegion = overlayData.subarray(dataStart, overlayData.length - tailDataLen);
206
+
207
+ // Reconstruct blob [data][offsets][trailer]
208
+ const bunDataBlob = Buffer.concat([dataRegion, offsetsBytes, trailerBytes]);
209
+
210
+ return { bunOffsets, bunData: bunDataBlob };
211
+ }
212
+
213
+ /**
214
+ * Extract Bun data from MachO binary
215
+ */
216
+ function extractBunDataFromMachO(machoBinary) {
217
+ const bunSegment = machoBinary.getSegment('__BUN');
218
+ if (!bunSegment) throw new Error('__BUN segment not found');
219
+
220
+ const bunSection = bunSegment.getSection('__bun');
221
+ if (!bunSection) throw new Error('__bun section not found');
222
+
223
+ return extractBunDataFromSection(bunSection.content);
224
+ }
225
+
226
+ /**
227
+ * Extract Bun data from PE binary
228
+ */
229
+ function extractBunDataFromPE(peBinary) {
230
+ const bunSection = peBinary.sections().find(s => s.name === '.bun');
231
+ if (!bunSection) throw new Error('.bun section not found');
232
+
233
+ return extractBunDataFromSection(bunSection.content);
234
+ }
235
+
236
+ /**
237
+ * Get Bun data from binary (auto-detect format)
238
+ */
239
+ function getBunData(binary) {
240
+ switch (binary.format) {
241
+ case 'MachO':
242
+ return extractBunDataFromMachO(binary);
243
+ case 'PE':
244
+ return extractBunDataFromPE(binary);
245
+ case 'ELF':
246
+ return extractBunDataFromELFOverlay(binary);
247
+ default:
248
+ throw new Error(`Unsupported binary format: ${binary.format}`);
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Extract claude.js from native installation binary
254
+ */
255
+ function extractClaudeJs(binaryPath) {
256
+ try {
257
+ LIEF.logging.disable();
258
+ const binary = LIEF.parse(binaryPath);
259
+ const { bunOffsets, bunData } = getBunData(binary);
260
+
261
+ const result = mapModules(bunData, bunOffsets, (module, moduleName) => {
262
+ if (!isClaudeModule(moduleName)) return undefined;
263
+
264
+ const moduleContents = getStringPointerContent(bunData, module.contents);
265
+ return moduleContents.length > 0 ? moduleContents : undefined;
266
+ });
267
+
268
+ return result || null;
269
+ } catch (error) {
270
+ return null;
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Rebuild Bun data with modified claude.js
276
+ */
277
+ function rebuildBunData(bunData, bunOffsets, modifiedClaudeJs) {
278
+ // Collect all string data and module metadata
279
+ const stringsData = [];
280
+ const modulesMetadata = [];
281
+
282
+ mapModules(bunData, bunOffsets, (module, moduleName) => {
283
+ const nameBytes = getStringPointerContent(bunData, module.name);
284
+
285
+ // Use modified contents for claude module
286
+ let contentsBytes;
287
+ if (modifiedClaudeJs && isClaudeModule(moduleName)) {
288
+ contentsBytes = modifiedClaudeJs;
289
+ } else {
290
+ contentsBytes = getStringPointerContent(bunData, module.contents);
291
+ }
292
+
293
+ const sourcemapBytes = getStringPointerContent(bunData, module.sourcemap);
294
+ const bytecodeBytes = getStringPointerContent(bunData, module.bytecode);
295
+
296
+ modulesMetadata.push({
297
+ name: nameBytes,
298
+ contents: contentsBytes,
299
+ sourcemap: sourcemapBytes,
300
+ bytecode: bytecodeBytes,
301
+ encoding: module.encoding,
302
+ loader: module.loader,
303
+ moduleFormat: module.moduleFormat,
304
+ side: module.side,
305
+ });
306
+
307
+ stringsData.push(nameBytes, contentsBytes, sourcemapBytes, bytecodeBytes);
308
+ return undefined;
309
+ });
310
+
311
+ // Calculate buffer layout
312
+ let currentOffset = 0;
313
+ const stringOffsets = [];
314
+
315
+ for (const stringData of stringsData) {
316
+ stringOffsets.push({ offset: currentOffset, length: stringData.length });
317
+ currentOffset += stringData.length + 1; // +1 for null terminator
318
+ }
319
+
320
+ const modulesListOffset = currentOffset;
321
+ const modulesListSize = modulesMetadata.length * SIZEOF_MODULE;
322
+ currentOffset += modulesListSize;
323
+
324
+ const compileExecArgvBytes = getStringPointerContent(bunData, bunOffsets.compileExecArgvPtr);
325
+ const compileExecArgvOffset = currentOffset;
326
+ const compileExecArgvLength = compileExecArgvBytes.length;
327
+ currentOffset += compileExecArgvLength + 1;
328
+
329
+ const offsetsOffset = currentOffset;
330
+ currentOffset += SIZEOF_OFFSETS;
331
+
332
+ const trailerOffset = currentOffset;
333
+ currentOffset += BUN_TRAILER.length;
334
+
335
+ // Build new buffer
336
+ const newBuffer = Buffer.allocUnsafe(currentOffset);
337
+ newBuffer.fill(0);
338
+
339
+ // Write strings
340
+ let stringIdx = 0;
341
+ for (const { offset, length } of stringOffsets) {
342
+ if (length > 0) {
343
+ stringsData[stringIdx].copy(newBuffer, offset, 0, length);
344
+ }
345
+ newBuffer[offset + length] = 0;
346
+ stringIdx++;
347
+ }
348
+
349
+ // Write compileExecArgv
350
+ if (compileExecArgvLength > 0) {
351
+ compileExecArgvBytes.copy(newBuffer, compileExecArgvOffset, 0, compileExecArgvLength);
352
+ newBuffer[compileExecArgvOffset + compileExecArgvLength] = 0;
353
+ }
354
+
355
+ // Write module structures
356
+ for (let i = 0; i < modulesMetadata.length; i++) {
357
+ const baseStringIdx = i * 4;
358
+ const moduleOffset = modulesListOffset + i * SIZEOF_MODULE;
359
+ let pos = moduleOffset;
360
+
361
+ // Write StringPointers
362
+ newBuffer.writeUInt32LE(stringOffsets[baseStringIdx].offset, pos);
363
+ newBuffer.writeUInt32LE(stringOffsets[baseStringIdx].length, pos + 4);
364
+ pos += 8;
365
+ newBuffer.writeUInt32LE(stringOffsets[baseStringIdx + 1].offset, pos);
366
+ newBuffer.writeUInt32LE(stringOffsets[baseStringIdx + 1].length, pos + 4);
367
+ pos += 8;
368
+ newBuffer.writeUInt32LE(stringOffsets[baseStringIdx + 2].offset, pos);
369
+ newBuffer.writeUInt32LE(stringOffsets[baseStringIdx + 2].length, pos + 4);
370
+ pos += 8;
371
+ newBuffer.writeUInt32LE(stringOffsets[baseStringIdx + 3].offset, pos);
372
+ newBuffer.writeUInt32LE(stringOffsets[baseStringIdx + 3].length, pos + 4);
373
+ pos += 8;
374
+
375
+ // Write flags
376
+ newBuffer.writeUInt8(modulesMetadata[i].encoding, pos);
377
+ newBuffer.writeUInt8(modulesMetadata[i].loader, pos + 1);
378
+ newBuffer.writeUInt8(modulesMetadata[i].moduleFormat, pos + 2);
379
+ newBuffer.writeUInt8(modulesMetadata[i].side, pos + 3);
380
+ }
381
+
382
+ // Write Offsets structure
383
+ let offsetsPos = offsetsOffset;
384
+ newBuffer.writeBigUInt64LE(BigInt(offsetsOffset), offsetsPos);
385
+ offsetsPos += 8;
386
+ newBuffer.writeUInt32LE(modulesListOffset, offsetsPos);
387
+ newBuffer.writeUInt32LE(modulesListSize, offsetsPos + 4);
388
+ offsetsPos += 8;
389
+ newBuffer.writeUInt32LE(bunOffsets.entryPointId, offsetsPos);
390
+ offsetsPos += 4;
391
+ newBuffer.writeUInt32LE(compileExecArgvOffset, offsetsPos);
392
+ newBuffer.writeUInt32LE(compileExecArgvLength, offsetsPos + 4);
393
+
394
+ // Write trailer
395
+ BUN_TRAILER.copy(newBuffer, trailerOffset);
396
+
397
+ return newBuffer;
398
+ }
399
+
400
+ /**
401
+ * Build section data with size header
402
+ */
403
+ function buildSectionData(bunBuffer, headerSize = 8) {
404
+ const sectionData = Buffer.allocUnsafe(headerSize + bunBuffer.length);
405
+ if (headerSize === 8) {
406
+ sectionData.writeBigUInt64LE(BigInt(bunBuffer.length), 0);
407
+ } else {
408
+ sectionData.writeUInt32LE(bunBuffer.length, 0);
409
+ }
410
+ bunBuffer.copy(sectionData, headerSize);
411
+ return sectionData;
412
+ }
413
+
414
+ /**
415
+ * Repack ELF binary with modified Bun data
416
+ */
417
+ function repackELF(elfBinary, binaryPath, newBunBuffer, outputPath) {
418
+ const fs = require('fs');
419
+
420
+ // Build new overlay
421
+ const newOverlay = Buffer.allocUnsafe(newBunBuffer.length + 8);
422
+ newBunBuffer.copy(newOverlay, 0);
423
+ newOverlay.writeBigUInt64LE(BigInt(newBunBuffer.length), newBunBuffer.length);
424
+
425
+ elfBinary.overlay = newOverlay;
426
+
427
+ // Write atomically
428
+ const tempPath = outputPath + '.tmp';
429
+ elfBinary.write(tempPath);
430
+
431
+ const origStat = fs.statSync(binaryPath);
432
+ fs.chmodSync(tempPath, origStat.mode);
433
+ fs.renameSync(tempPath, outputPath);
434
+ }
435
+
436
+ /**
437
+ * Repack MachO binary with modified Bun data
438
+ */
439
+ function repackMachO(machoBinary, binaryPath, newBunBuffer, outputPath, sectionHeaderSize) {
440
+ const fs = require('fs');
441
+ const { execSync } = require('child_process');
442
+
443
+ // Remove code signature
444
+ if (machoBinary.hasCodeSignature) {
445
+ machoBinary.removeSignature();
446
+ }
447
+
448
+ const bunSegment = machoBinary.getSegment('__BUN');
449
+ const bunSection = bunSegment.getSection('__bun');
450
+
451
+ const newSectionData = buildSectionData(newBunBuffer, sectionHeaderSize);
452
+ const sizeDiff = newSectionData.length - Number(bunSection.size);
453
+
454
+ if (sizeDiff > 0) {
455
+ const isARM64 = machoBinary.header.cpuType === LIEF.MachO.Header.CPU_TYPE.ARM64;
456
+ const PAGE_SIZE = isARM64 ? 16384 : 4096;
457
+ const alignedSizeDiff = Math.ceil(sizeDiff / PAGE_SIZE) * PAGE_SIZE;
458
+
459
+ machoBinary.extendSegment(bunSegment, alignedSizeDiff);
460
+ }
461
+
462
+ bunSection.content = newSectionData;
463
+ bunSection.size = BigInt(newSectionData.length);
464
+
465
+ // Write atomically
466
+ const tempPath = outputPath + '.tmp';
467
+ machoBinary.write(tempPath);
468
+
469
+ const origStat = fs.statSync(binaryPath);
470
+ fs.chmodSync(tempPath, origStat.mode);
471
+ fs.renameSync(tempPath, outputPath);
472
+
473
+ // Re-sign with ad-hoc signature
474
+ try {
475
+ execSync(`codesign -s - -f "${outputPath}"`, { stdio: 'ignore' });
476
+ } catch (e) {
477
+ // Ignore codesign errors on non-macOS
478
+ }
479
+ }
480
+
481
+ /**
482
+ * Repack PE binary with modified Bun data
483
+ */
484
+ function repackPE(peBinary, binaryPath, newBunBuffer, outputPath, sectionHeaderSize) {
485
+ const fs = require('fs');
486
+
487
+ const bunSection = peBinary.sections().find(s => s.name === '.bun');
488
+ const newSectionData = buildSectionData(newBunBuffer, sectionHeaderSize);
489
+
490
+ bunSection.content = newSectionData;
491
+ bunSection.virtualSize = BigInt(newSectionData.length);
492
+ bunSection.size = BigInt(newSectionData.length);
493
+
494
+ // Write atomically
495
+ const tempPath = outputPath + '.tmp';
496
+ peBinary.write(tempPath);
497
+ fs.renameSync(tempPath, outputPath);
498
+ }
499
+
500
+ /**
501
+ * Repack native installation with modified claude.js
502
+ */
503
+ function repackNativeInstallation(binaryPath, modifiedClaudeJs, outputPath) {
504
+ LIEF.logging.disable();
505
+ const binary = LIEF.parse(binaryPath);
506
+
507
+ const { bunOffsets, bunData, sectionHeaderSize } = getBunData(binary);
508
+ const newBuffer = rebuildBunData(bunData, bunOffsets, modifiedClaudeJs);
509
+
510
+ switch (binary.format) {
511
+ case 'MachO':
512
+ repackMachO(binary, binaryPath, newBuffer, outputPath, sectionHeaderSize);
513
+ break;
514
+ case 'PE':
515
+ repackPE(binary, binaryPath, newBuffer, outputPath, sectionHeaderSize);
516
+ break;
517
+ case 'ELF':
518
+ repackELF(binary, binaryPath, newBuffer, outputPath);
519
+ break;
520
+ default:
521
+ throw new Error(`Unsupported binary format: ${binary.format}`);
522
+ }
523
+ }
524
+
525
+ module.exports = {
526
+ extractClaudeJs,
527
+ repackNativeInstallation,
528
+ isClaudeModule,
529
+ BUN_TRAILER,
530
+ };
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Detect Claude Code installation paths
3
+ * Supports: native binary, local npm, global npm, homebrew
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { execSync } = require('child_process');
9
+ const os = require('os');
10
+
11
+ const HOME = os.homedir();
12
+
13
+ /**
14
+ * Get all potential paths to check (both cli.js and native binary)
15
+ */
16
+ function getPotentialPaths() {
17
+ const paths = [];
18
+
19
+ // Priority 1: Native binary (recommended installation method)
20
+ // The binary is at ~/.local/bin/claude which symlinks to ~/.local/share/claude/versions/X.Y.Z
21
+ if (process.platform !== 'win32') {
22
+ try {
23
+ const claudePath = execSync('which claude', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
24
+ if (claudePath) {
25
+ // Resolve symlinks to get the actual binary
26
+ const realPath = fs.realpathSync(claudePath);
27
+ paths.push({
28
+ method: 'native binary (which claude)',
29
+ path: realPath,
30
+ type: 'binary'
31
+ });
32
+ }
33
+ } catch (e) {
34
+ // which failed or claude not found
35
+ }
36
+ }
37
+
38
+ // Also check versions directory directly
39
+ const versionsDir = path.join(HOME, '.local', 'share', 'claude', 'versions');
40
+ try {
41
+ if (fs.existsSync(versionsDir)) {
42
+ const versions = fs.readdirSync(versionsDir)
43
+ .filter(f => /^\d+\.\d+\.\d+$/.test(f))
44
+ .sort((a, b) => {
45
+ const [aMajor, aMinor, aPatch] = a.split('.').map(Number);
46
+ const [bMajor, bMinor, bPatch] = b.split('.').map(Number);
47
+ return bMajor - aMajor || bMinor - aMinor || bPatch - aPatch;
48
+ });
49
+
50
+ if (versions.length > 0) {
51
+ paths.push({
52
+ method: 'native binary (~/.local/share/claude/versions)',
53
+ path: path.join(versionsDir, versions[0]),
54
+ type: 'binary'
55
+ });
56
+ }
57
+ }
58
+ } catch (e) {
59
+ // Failed to read versions dir
60
+ }
61
+
62
+ // Priority 2: Local npm installations
63
+ paths.push({
64
+ method: 'local npm (~/.claude)',
65
+ path: path.join(HOME, '.claude', 'local', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
66
+ type: 'js'
67
+ });
68
+ paths.push({
69
+ method: 'local npm (~/.config/claude)',
70
+ path: path.join(HOME, '.config', 'claude', 'local', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
71
+ type: 'js'
72
+ });
73
+
74
+ // Priority 3: Global npm (dynamic)
75
+ try {
76
+ const npmRoot = execSync('npm root -g', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
77
+ if (npmRoot) {
78
+ paths.push({
79
+ method: 'global npm (npm root -g)',
80
+ path: path.join(npmRoot, '@anthropic-ai', 'claude-code', 'cli.js'),
81
+ type: 'js'
82
+ });
83
+ }
84
+ } catch (e) {
85
+ // npm not available or failed
86
+ }
87
+
88
+ // Priority 4: Derive from process.execPath
89
+ try {
90
+ const nodeDir = path.dirname(process.execPath);
91
+ paths.push({
92
+ method: 'derived from node binary',
93
+ path: path.join(nodeDir, '..', 'lib', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
94
+ type: 'js'
95
+ });
96
+ } catch (e) {
97
+ // Failed to derive
98
+ }
99
+
100
+ // Priority 5: Common homebrew locations (macOS)
101
+ if (process.platform === 'darwin') {
102
+ paths.push({
103
+ method: 'homebrew (arm64)',
104
+ path: '/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js',
105
+ type: 'js'
106
+ });
107
+ paths.push({
108
+ method: 'homebrew (x86)',
109
+ path: '/usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js',
110
+ type: 'js'
111
+ });
112
+ }
113
+
114
+ return paths;
115
+ }
116
+
117
+ /**
118
+ * Check if a file contains the silly words (works for both JS and binary)
119
+ */
120
+ function containsSillyWords(filePath) {
121
+ try {
122
+ // Read as buffer to handle both text and binary
123
+ const content = fs.readFileSync(filePath);
124
+ const contentStr = content.toString('utf-8');
125
+
126
+ // Check for marker words
127
+ const markers = ['Flibbertigibbeting', 'Discombobulating', 'Clauding'];
128
+ let found = 0;
129
+ for (const marker of markers) {
130
+ if (contentStr.includes(marker)) {
131
+ found++;
132
+ }
133
+ }
134
+ return found >= 2;
135
+ } catch (e) {
136
+ return false;
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Find Claude Code installation (cli.js or native binary)
142
+ * @returns {{ path: string, method: string, type: 'js' | 'binary' } | null}
143
+ */
144
+ function findClaudeCode() {
145
+ const potentialPaths = getPotentialPaths();
146
+
147
+ for (const { method, path: codePath, type } of potentialPaths) {
148
+ try {
149
+ const resolvedPath = path.resolve(codePath);
150
+ if (fs.existsSync(resolvedPath)) {
151
+ const stats = fs.statSync(resolvedPath);
152
+ if (stats.isFile() && containsSillyWords(resolvedPath)) {
153
+ return { path: resolvedPath, method, type };
154
+ }
155
+ }
156
+ } catch (e) {
157
+ // File doesn't exist or can't be read
158
+ }
159
+ }
160
+
161
+ return null;
162
+ }
163
+
164
+ // Legacy alias
165
+ function findCliJs() {
166
+ const result = findClaudeCode();
167
+ if (result) {
168
+ return { path: result.path, method: result.method, type: result.type };
169
+ }
170
+ return null;
171
+ }
172
+
173
+ /**
174
+ * Get all searched paths for error reporting
175
+ */
176
+ function getSearchedPaths() {
177
+ return getPotentialPaths();
178
+ }
179
+
180
+ module.exports = {
181
+ findClaudeCode,
182
+ findCliJs,
183
+ getSearchedPaths
184
+ };