claude-depester 1.3.6 → 1.4.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.
- package/README.md +19 -59
- package/bin/claude-depester +2 -162
- package/lib/bun-binary.js +154 -80
- package/lib/detector.js +0 -3
- package/lib/hooks.js +2 -142
- package/lib/patcher.js +61 -294
- package/package.json +4 -7
package/lib/bun-binary.js
CHANGED
|
@@ -14,7 +14,11 @@ const BUN_TRAILER = Buffer.from('\n---- Bun! ----\n');
|
|
|
14
14
|
// Size constants for binary structures
|
|
15
15
|
const SIZEOF_OFFSETS = 32;
|
|
16
16
|
const SIZEOF_STRING_POINTER = 8;
|
|
17
|
-
|
|
17
|
+
// Module struct sizes vary by Bun version:
|
|
18
|
+
// - Old format (pre-ESM bytecode, before Bun ~1.3.7): 4 StringPointers + 4 u8s = 36 bytes
|
|
19
|
+
// - New format (ESM bytecode, Bun ~1.3.7+): 6 StringPointers + 4 u8s = 52 bytes
|
|
20
|
+
const SIZEOF_MODULE_OLD = 4 * SIZEOF_STRING_POINTER + 4;
|
|
21
|
+
const SIZEOF_MODULE_NEW = 6 * SIZEOF_STRING_POINTER + 4;
|
|
18
22
|
|
|
19
23
|
/**
|
|
20
24
|
* Parse a StringPointer from buffer at offset
|
|
@@ -48,14 +52,29 @@ function parseOffsets(buffer) {
|
|
|
48
52
|
const entryPointId = buffer.readUInt32LE(pos);
|
|
49
53
|
pos += 4;
|
|
50
54
|
const compileExecArgvPtr = parseStringPointer(buffer, pos);
|
|
55
|
+
pos += 8;
|
|
56
|
+
const flags = buffer.readUInt32LE(pos);
|
|
51
57
|
|
|
52
|
-
return { byteCount, modulesPtr, entryPointId, compileExecArgvPtr };
|
|
58
|
+
return { byteCount, modulesPtr, entryPointId, compileExecArgvPtr, flags };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Detect the module struct size from the modules list byte length
|
|
63
|
+
*/
|
|
64
|
+
function detectModuleStructSize(modulesListLength) {
|
|
65
|
+
const fitsNew = modulesListLength % SIZEOF_MODULE_NEW === 0;
|
|
66
|
+
const fitsOld = modulesListLength % SIZEOF_MODULE_OLD === 0;
|
|
67
|
+
|
|
68
|
+
if (fitsNew && !fitsOld) return SIZEOF_MODULE_NEW;
|
|
69
|
+
if (fitsOld && !fitsNew) return SIZEOF_MODULE_OLD;
|
|
70
|
+
// Ambiguous or neither - prefer new format for recent Bun versions
|
|
71
|
+
return SIZEOF_MODULE_NEW;
|
|
53
72
|
}
|
|
54
73
|
|
|
55
74
|
/**
|
|
56
75
|
* Parse a compiled module from buffer
|
|
57
76
|
*/
|
|
58
|
-
function parseCompiledModule(buffer, offset) {
|
|
77
|
+
function parseCompiledModule(buffer, offset, moduleStructSize) {
|
|
59
78
|
let pos = offset;
|
|
60
79
|
const name = parseStringPointer(buffer, pos);
|
|
61
80
|
pos += 8;
|
|
@@ -65,6 +84,18 @@ function parseCompiledModule(buffer, offset) {
|
|
|
65
84
|
pos += 8;
|
|
66
85
|
const bytecode = parseStringPointer(buffer, pos);
|
|
67
86
|
pos += 8;
|
|
87
|
+
|
|
88
|
+
let moduleInfo, bytecodeOriginPath;
|
|
89
|
+
if (moduleStructSize === SIZEOF_MODULE_NEW) {
|
|
90
|
+
moduleInfo = parseStringPointer(buffer, pos);
|
|
91
|
+
pos += 8;
|
|
92
|
+
bytecodeOriginPath = parseStringPointer(buffer, pos);
|
|
93
|
+
pos += 8;
|
|
94
|
+
} else {
|
|
95
|
+
moduleInfo = { offset: 0, length: 0 };
|
|
96
|
+
bytecodeOriginPath = { offset: 0, length: 0 };
|
|
97
|
+
}
|
|
98
|
+
|
|
68
99
|
const encoding = buffer.readUInt8(pos);
|
|
69
100
|
pos += 1;
|
|
70
101
|
const loader = buffer.readUInt8(pos);
|
|
@@ -73,31 +104,34 @@ function parseCompiledModule(buffer, offset) {
|
|
|
73
104
|
pos += 1;
|
|
74
105
|
const side = buffer.readUInt8(pos);
|
|
75
106
|
|
|
76
|
-
return { name, contents, sourcemap, bytecode, encoding, loader, moduleFormat, side };
|
|
107
|
+
return { name, contents, sourcemap, bytecode, moduleInfo, bytecodeOriginPath, encoding, loader, moduleFormat, side };
|
|
77
108
|
}
|
|
78
109
|
|
|
79
110
|
/**
|
|
80
111
|
* Check if module name is the claude entrypoint
|
|
112
|
+
* Claude Code 2.0.69+ changed from '/claude' to 'file:///src/entrypoints/cli.js.jsc'
|
|
81
113
|
*/
|
|
82
114
|
function isClaudeModule(moduleName) {
|
|
83
115
|
return (
|
|
84
116
|
moduleName.endsWith('/claude') ||
|
|
85
117
|
moduleName === 'claude' ||
|
|
86
118
|
moduleName.endsWith('/claude.exe') ||
|
|
87
|
-
moduleName === 'claude.exe'
|
|
119
|
+
moduleName === 'claude.exe' ||
|
|
120
|
+
moduleName.includes('/cli.js') ||
|
|
121
|
+
moduleName.endsWith('cli.js.jsc')
|
|
88
122
|
);
|
|
89
123
|
}
|
|
90
124
|
|
|
91
125
|
/**
|
|
92
126
|
* Iterate over modules in Bun data
|
|
93
127
|
*/
|
|
94
|
-
function mapModules(bunData, bunOffsets, visitor) {
|
|
128
|
+
function mapModules(bunData, bunOffsets, moduleStructSize, visitor) {
|
|
95
129
|
const modulesListBytes = getStringPointerContent(bunData, bunOffsets.modulesPtr);
|
|
96
|
-
const modulesListCount = Math.floor(modulesListBytes.length /
|
|
130
|
+
const modulesListCount = Math.floor(modulesListBytes.length / moduleStructSize);
|
|
97
131
|
|
|
98
132
|
for (let i = 0; i < modulesListCount; i++) {
|
|
99
|
-
const offset = i *
|
|
100
|
-
const module = parseCompiledModule(modulesListBytes, offset);
|
|
133
|
+
const offset = i * moduleStructSize;
|
|
134
|
+
const module = parseCompiledModule(modulesListBytes, offset, moduleStructSize);
|
|
101
135
|
const moduleName = getStringPointerContent(bunData, module.name).toString('utf-8');
|
|
102
136
|
|
|
103
137
|
const result = visitor(module, moduleName, i);
|
|
@@ -129,8 +163,9 @@ function parseBunDataBlob(bunDataContent) {
|
|
|
129
163
|
const offsetsStart = bunDataContent.length - SIZEOF_OFFSETS - BUN_TRAILER.length;
|
|
130
164
|
const offsetsBytes = bunDataContent.subarray(offsetsStart, offsetsStart + SIZEOF_OFFSETS);
|
|
131
165
|
const bunOffsets = parseOffsets(offsetsBytes);
|
|
166
|
+
const moduleStructSize = detectModuleStructSize(bunOffsets.modulesPtr.length);
|
|
132
167
|
|
|
133
|
-
return { bunOffsets, bunData: bunDataContent };
|
|
168
|
+
return { bunOffsets, bunData: bunDataContent, moduleStructSize };
|
|
134
169
|
}
|
|
135
170
|
|
|
136
171
|
/**
|
|
@@ -163,9 +198,9 @@ function extractBunDataFromSection(sectionData) {
|
|
|
163
198
|
}
|
|
164
199
|
|
|
165
200
|
const bunDataContent = sectionData.subarray(headerSize, headerSize + bunDataSize);
|
|
166
|
-
const { bunOffsets, bunData } = parseBunDataBlob(bunDataContent);
|
|
201
|
+
const { bunOffsets, bunData, moduleStructSize } = parseBunDataBlob(bunDataContent);
|
|
167
202
|
|
|
168
|
-
return { bunOffsets, bunData, sectionHeaderSize: headerSize };
|
|
203
|
+
return { bunOffsets, bunData, sectionHeaderSize: headerSize, moduleStructSize };
|
|
169
204
|
}
|
|
170
205
|
|
|
171
206
|
/**
|
|
@@ -209,8 +244,9 @@ function extractBunDataFromELFOverlay(elfBinary) {
|
|
|
209
244
|
|
|
210
245
|
// Reconstruct blob [data][offsets][trailer]
|
|
211
246
|
const bunDataBlob = Buffer.concat([dataRegion, offsetsBytes, trailerBytes]);
|
|
247
|
+
const moduleStructSize = detectModuleStructSize(bunOffsets.modulesPtr.length);
|
|
212
248
|
|
|
213
|
-
return { bunOffsets, bunData: bunDataBlob };
|
|
249
|
+
return { bunOffsets, bunData: bunDataBlob, moduleStructSize };
|
|
214
250
|
}
|
|
215
251
|
|
|
216
252
|
/**
|
|
@@ -259,34 +295,16 @@ function extractClaudeJs(binaryPath) {
|
|
|
259
295
|
try {
|
|
260
296
|
LIEF.logging.disable();
|
|
261
297
|
const binary = LIEF.parse(binaryPath);
|
|
262
|
-
const { bunOffsets, bunData } = getBunData(binary);
|
|
298
|
+
const { bunOffsets, bunData, moduleStructSize } = getBunData(binary);
|
|
263
299
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
mapModules(bunData, bunOffsets, (module, moduleName) => {
|
|
267
|
-
// Check contents
|
|
268
|
-
const moduleContents = getStringPointerContent(bunData, module.contents);
|
|
269
|
-
if (moduleContents.includes('Flibbertigibbeting')) {
|
|
270
|
-
target = { content: moduleContents, moduleName, type: 'contents' };
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Check bytecode (for new versions where source is embedded in bytecode)
|
|
275
|
-
const moduleBytecode = getStringPointerContent(bunData, module.bytecode);
|
|
276
|
-
if (moduleBytecode.includes('Flibbertigibbeting')) {
|
|
277
|
-
target = { content: moduleBytecode, moduleName, type: 'bytecode' };
|
|
278
|
-
return true;
|
|
279
|
-
}
|
|
300
|
+
const result = mapModules(bunData, bunOffsets, moduleStructSize, (module, moduleName) => {
|
|
301
|
+
if (!isClaudeModule(moduleName)) return undefined;
|
|
280
302
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
target = { content: moduleContents, moduleName, type: 'contents' };
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return undefined;
|
|
303
|
+
const moduleContents = getStringPointerContent(bunData, module.contents);
|
|
304
|
+
return moduleContents.length > 0 ? moduleContents : undefined;
|
|
287
305
|
});
|
|
288
306
|
|
|
289
|
-
return
|
|
307
|
+
return result || null;
|
|
290
308
|
} catch (error) {
|
|
291
309
|
return null;
|
|
292
310
|
}
|
|
@@ -295,39 +313,46 @@ function extractClaudeJs(binaryPath) {
|
|
|
295
313
|
/**
|
|
296
314
|
* Rebuild Bun data with modified claude.js
|
|
297
315
|
*/
|
|
298
|
-
function rebuildBunData(bunData, bunOffsets, modifiedClaudeJs,
|
|
316
|
+
function rebuildBunData(bunData, bunOffsets, modifiedClaudeJs, moduleStructSize) {
|
|
299
317
|
// Collect all string data and module metadata
|
|
300
318
|
const stringsData = [];
|
|
301
319
|
const modulesMetadata = [];
|
|
320
|
+
const stringsPerModule = moduleStructSize === SIZEOF_MODULE_NEW ? 6 : 4;
|
|
302
321
|
|
|
303
|
-
mapModules(bunData, bunOffsets, (module, moduleName) => {
|
|
322
|
+
mapModules(bunData, bunOffsets, moduleStructSize, (module, moduleName) => {
|
|
304
323
|
const nameBytes = getStringPointerContent(bunData, module.name);
|
|
305
324
|
|
|
306
|
-
|
|
307
|
-
let
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
} else {
|
|
313
|
-
contentsBytes = modifiedClaudeJs;
|
|
314
|
-
}
|
|
325
|
+
// Use modified contents for claude module
|
|
326
|
+
let contentsBytes;
|
|
327
|
+
if (modifiedClaudeJs && isClaudeModule(moduleName)) {
|
|
328
|
+
contentsBytes = modifiedClaudeJs;
|
|
329
|
+
} else {
|
|
330
|
+
contentsBytes = getStringPointerContent(bunData, module.contents);
|
|
315
331
|
}
|
|
316
332
|
|
|
317
333
|
const sourcemapBytes = getStringPointerContent(bunData, module.sourcemap);
|
|
334
|
+
const bytecodeBytes = getStringPointerContent(bunData, module.bytecode);
|
|
335
|
+
const moduleInfoBytes = getStringPointerContent(bunData, module.moduleInfo);
|
|
336
|
+
const bytecodeOriginPathBytes = getStringPointerContent(bunData, module.bytecodeOriginPath);
|
|
318
337
|
|
|
319
338
|
modulesMetadata.push({
|
|
320
339
|
name: nameBytes,
|
|
321
340
|
contents: contentsBytes,
|
|
322
341
|
sourcemap: sourcemapBytes,
|
|
323
342
|
bytecode: bytecodeBytes,
|
|
343
|
+
moduleInfo: moduleInfoBytes,
|
|
344
|
+
bytecodeOriginPath: bytecodeOriginPathBytes,
|
|
324
345
|
encoding: module.encoding,
|
|
325
346
|
loader: module.loader,
|
|
326
347
|
moduleFormat: module.moduleFormat,
|
|
327
348
|
side: module.side,
|
|
328
349
|
});
|
|
329
350
|
|
|
330
|
-
|
|
351
|
+
if (moduleStructSize === SIZEOF_MODULE_NEW) {
|
|
352
|
+
stringsData.push(nameBytes, contentsBytes, sourcemapBytes, bytecodeBytes, moduleInfoBytes, bytecodeOriginPathBytes);
|
|
353
|
+
} else {
|
|
354
|
+
stringsData.push(nameBytes, contentsBytes, sourcemapBytes, bytecodeBytes);
|
|
355
|
+
}
|
|
331
356
|
return undefined;
|
|
332
357
|
});
|
|
333
358
|
|
|
@@ -341,7 +366,7 @@ function rebuildBunData(bunData, bunOffsets, modifiedClaudeJs, targetModuleName,
|
|
|
341
366
|
}
|
|
342
367
|
|
|
343
368
|
const modulesListOffset = currentOffset;
|
|
344
|
-
const modulesListSize = modulesMetadata.length *
|
|
369
|
+
const modulesListSize = modulesMetadata.length * moduleStructSize;
|
|
345
370
|
currentOffset += modulesListSize;
|
|
346
371
|
|
|
347
372
|
const compileExecArgvBytes = getStringPointerContent(bunData, bunOffsets.compileExecArgvPtr);
|
|
@@ -377,8 +402,8 @@ function rebuildBunData(bunData, bunOffsets, modifiedClaudeJs, targetModuleName,
|
|
|
377
402
|
|
|
378
403
|
// Write module structures
|
|
379
404
|
for (let i = 0; i < modulesMetadata.length; i++) {
|
|
380
|
-
const baseStringIdx = i *
|
|
381
|
-
const moduleOffset = modulesListOffset + i *
|
|
405
|
+
const baseStringIdx = i * stringsPerModule;
|
|
406
|
+
const moduleOffset = modulesListOffset + i * moduleStructSize;
|
|
382
407
|
let pos = moduleOffset;
|
|
383
408
|
|
|
384
409
|
// Write StringPointers
|
|
@@ -395,6 +420,16 @@ function rebuildBunData(bunData, bunOffsets, modifiedClaudeJs, targetModuleName,
|
|
|
395
420
|
newBuffer.writeUInt32LE(stringOffsets[baseStringIdx + 3].length, pos + 4);
|
|
396
421
|
pos += 8;
|
|
397
422
|
|
|
423
|
+
// Write new-format-only StringPointers
|
|
424
|
+
if (moduleStructSize === SIZEOF_MODULE_NEW) {
|
|
425
|
+
newBuffer.writeUInt32LE(stringOffsets[baseStringIdx + 4].offset, pos);
|
|
426
|
+
newBuffer.writeUInt32LE(stringOffsets[baseStringIdx + 4].length, pos + 4);
|
|
427
|
+
pos += 8;
|
|
428
|
+
newBuffer.writeUInt32LE(stringOffsets[baseStringIdx + 5].offset, pos);
|
|
429
|
+
newBuffer.writeUInt32LE(stringOffsets[baseStringIdx + 5].length, pos + 4);
|
|
430
|
+
pos += 8;
|
|
431
|
+
}
|
|
432
|
+
|
|
398
433
|
// Write flags
|
|
399
434
|
newBuffer.writeUInt8(modulesMetadata[i].encoding, pos);
|
|
400
435
|
newBuffer.writeUInt8(modulesMetadata[i].loader, pos + 1);
|
|
@@ -413,6 +448,8 @@ function rebuildBunData(bunData, bunOffsets, modifiedClaudeJs, targetModuleName,
|
|
|
413
448
|
offsetsPos += 4;
|
|
414
449
|
newBuffer.writeUInt32LE(compileExecArgvOffset, offsetsPos);
|
|
415
450
|
newBuffer.writeUInt32LE(compileExecArgvLength, offsetsPos + 4);
|
|
451
|
+
offsetsPos += 8;
|
|
452
|
+
newBuffer.writeUInt32LE(bunOffsets.flags || 0, offsetsPos);
|
|
416
453
|
|
|
417
454
|
// Write trailer
|
|
418
455
|
BUN_TRAILER.copy(newBuffer, trailerOffset);
|
|
@@ -457,43 +494,55 @@ function repackELF(elfBinary, binaryPath, newBunBuffer, outputPath) {
|
|
|
457
494
|
}
|
|
458
495
|
|
|
459
496
|
/**
|
|
460
|
-
* Repack MachO binary with modified Bun data
|
|
497
|
+
* Repack MachO binary with modified Bun data using raw file write.
|
|
498
|
+
* Bypasses LIEF write() which has a bug that bloats MachO binaries.
|
|
499
|
+
* Instead, writes the patched section data directly at the file offset.
|
|
461
500
|
*/
|
|
462
501
|
function repackMachO(machoBinary, binaryPath, newBunBuffer, outputPath, sectionHeaderSize) {
|
|
463
502
|
const fs = require('fs');
|
|
464
503
|
const { execSync } = require('child_process');
|
|
465
504
|
|
|
466
|
-
// Remove code signature
|
|
467
|
-
if (machoBinary.hasCodeSignature) {
|
|
468
|
-
machoBinary.removeSignature();
|
|
469
|
-
}
|
|
470
|
-
|
|
471
505
|
const bunSegment = machoBinary.getSegment('__BUN');
|
|
472
506
|
const bunSection = bunSegment.getSection('__bun');
|
|
473
507
|
|
|
508
|
+
const originalSectionSize = Number(bunSection.size);
|
|
509
|
+
const sectionFileOffset = Number(bunSection.offset);
|
|
510
|
+
|
|
474
511
|
const newSectionData = buildSectionData(newBunBuffer, sectionHeaderSize);
|
|
475
|
-
const sizeDiff = newSectionData.length - Number(bunSection.size);
|
|
476
512
|
|
|
477
|
-
if (
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
machoBinary.extendSegment(bunSegment, alignedSizeDiff);
|
|
513
|
+
if (newSectionData.length > originalSectionSize) {
|
|
514
|
+
throw new Error(
|
|
515
|
+
`Patched section data (${newSectionData.length}) is larger than original (${originalSectionSize}). ` +
|
|
516
|
+
'This should not happen since we only replace arrays with shorter content.'
|
|
517
|
+
);
|
|
483
518
|
}
|
|
484
519
|
|
|
485
|
-
|
|
486
|
-
|
|
520
|
+
// Pad new section data to original size so binary layout stays identical
|
|
521
|
+
const paddedSectionData = Buffer.alloc(originalSectionSize, 0);
|
|
522
|
+
newSectionData.copy(paddedSectionData, 0);
|
|
523
|
+
// Update the size header to reflect actual data size (not padded)
|
|
524
|
+
if (sectionHeaderSize === 8) {
|
|
525
|
+
paddedSectionData.writeBigUInt64LE(BigInt(newBunBuffer.length), 0);
|
|
526
|
+
} else {
|
|
527
|
+
paddedSectionData.writeUInt32LE(newBunBuffer.length, 0);
|
|
528
|
+
}
|
|
487
529
|
|
|
488
|
-
//
|
|
530
|
+
// Raw file write: copy original binary, overwrite section bytes
|
|
489
531
|
const tempPath = outputPath + '.tmp';
|
|
490
|
-
|
|
532
|
+
fs.copyFileSync(binaryPath, tempPath);
|
|
533
|
+
|
|
534
|
+
const fd = fs.openSync(tempPath, 'r+');
|
|
535
|
+
try {
|
|
536
|
+
fs.writeSync(fd, paddedSectionData, 0, paddedSectionData.length, sectionFileOffset);
|
|
537
|
+
} finally {
|
|
538
|
+
fs.closeSync(fd);
|
|
539
|
+
}
|
|
491
540
|
|
|
492
541
|
const origStat = fs.statSync(binaryPath);
|
|
493
542
|
fs.chmodSync(tempPath, origStat.mode);
|
|
494
543
|
fs.renameSync(tempPath, outputPath);
|
|
495
544
|
|
|
496
|
-
// Re-sign with ad-hoc signature
|
|
545
|
+
// Re-sign with ad-hoc signature (needed on macOS after modifying binary)
|
|
497
546
|
try {
|
|
498
547
|
execSync(`codesign -s - -f "${outputPath}"`, { stdio: 'ignore' });
|
|
499
548
|
} catch (e) {
|
|
@@ -502,34 +551,59 @@ function repackMachO(machoBinary, binaryPath, newBunBuffer, outputPath, sectionH
|
|
|
502
551
|
}
|
|
503
552
|
|
|
504
553
|
/**
|
|
505
|
-
* Repack PE binary with modified Bun data
|
|
554
|
+
* Repack PE binary with modified Bun data using raw file write.
|
|
555
|
+
* Bypasses LIEF write() to avoid potential binary corruption.
|
|
506
556
|
*/
|
|
507
557
|
function repackPE(peBinary, binaryPath, newBunBuffer, outputPath, sectionHeaderSize) {
|
|
508
558
|
const fs = require('fs');
|
|
509
559
|
|
|
510
560
|
const bunSection = peBinary.sections().find(s => s.name === '.bun');
|
|
561
|
+
|
|
562
|
+
const originalSectionSize = Number(bunSection.size);
|
|
563
|
+
// PE section file offset property name varies by node-lief version
|
|
564
|
+
const sectionFileOffset = Number(bunSection.pointerToRawData || bunSection.pointersToRawData || bunSection.offset);
|
|
565
|
+
|
|
511
566
|
const newSectionData = buildSectionData(newBunBuffer, sectionHeaderSize);
|
|
512
567
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
568
|
+
if (newSectionData.length > originalSectionSize) {
|
|
569
|
+
throw new Error(
|
|
570
|
+
`Patched section data (${newSectionData.length}) is larger than original (${originalSectionSize}). ` +
|
|
571
|
+
'This should not happen since we only replace arrays with shorter content.'
|
|
572
|
+
);
|
|
573
|
+
}
|
|
516
574
|
|
|
517
|
-
//
|
|
575
|
+
// Pad new section data to original size so binary layout stays identical
|
|
576
|
+
const paddedSectionData = Buffer.alloc(originalSectionSize, 0);
|
|
577
|
+
newSectionData.copy(paddedSectionData, 0);
|
|
578
|
+
if (sectionHeaderSize === 8) {
|
|
579
|
+
paddedSectionData.writeBigUInt64LE(BigInt(newBunBuffer.length), 0);
|
|
580
|
+
} else {
|
|
581
|
+
paddedSectionData.writeUInt32LE(newBunBuffer.length, 0);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Raw file write: copy original binary, overwrite section bytes
|
|
518
585
|
const tempPath = outputPath + '.tmp';
|
|
519
|
-
|
|
586
|
+
fs.copyFileSync(binaryPath, tempPath);
|
|
587
|
+
|
|
588
|
+
const fd = fs.openSync(tempPath, 'r+');
|
|
589
|
+
try {
|
|
590
|
+
fs.writeSync(fd, paddedSectionData, 0, paddedSectionData.length, sectionFileOffset);
|
|
591
|
+
} finally {
|
|
592
|
+
fs.closeSync(fd);
|
|
593
|
+
}
|
|
594
|
+
|
|
520
595
|
fs.renameSync(tempPath, outputPath);
|
|
521
596
|
}
|
|
522
597
|
|
|
523
598
|
/**
|
|
524
599
|
* Repack native installation with modified claude.js
|
|
525
600
|
*/
|
|
526
|
-
function repackNativeInstallation(binaryPath, modifiedClaudeJs, outputPath
|
|
601
|
+
function repackNativeInstallation(binaryPath, modifiedClaudeJs, outputPath) {
|
|
527
602
|
LIEF.logging.disable();
|
|
528
603
|
const binary = LIEF.parse(binaryPath);
|
|
529
604
|
|
|
530
|
-
const { bunOffsets, bunData, sectionHeaderSize } = getBunData(binary);
|
|
531
|
-
|
|
532
|
-
const newBuffer = rebuildBunData(bunData, bunOffsets, modifiedClaudeJs, targetModuleName, targetType);
|
|
605
|
+
const { bunOffsets, bunData, sectionHeaderSize, moduleStructSize } = getBunData(binary);
|
|
606
|
+
const newBuffer = rebuildBunData(bunData, bunOffsets, modifiedClaudeJs, moduleStructSize);
|
|
533
607
|
|
|
534
608
|
switch (binary.format) {
|
|
535
609
|
case 'MachO':
|
package/lib/detector.js
CHANGED
|
@@ -153,9 +153,6 @@ function getPotentialPaths() {
|
|
|
153
153
|
path.join(HOME, '.vscode', 'extensions'), // VS Code (all platforms)
|
|
154
154
|
path.join(HOME, '.vscode-oss', 'extensions'), // VSCodium (Linux)
|
|
155
155
|
path.join(HOME, '.vscode-insiders', 'extensions'), // VS Code Insiders
|
|
156
|
-
path.join(HOME, '.vscode-server', 'extensions'), // VS Code Remote Server
|
|
157
|
-
path.join(HOME, '.vscode-server-insiders', 'extensions'), // VS Code Remote Server (Insiders)
|
|
158
|
-
path.join(HOME, '.cursor-server', 'extensions'), // Cursor Remote Server
|
|
159
156
|
];
|
|
160
157
|
|
|
161
158
|
// Add Windows-specific paths
|
package/lib/hooks.js
CHANGED
|
@@ -15,13 +15,8 @@ const HOME = os.homedir();
|
|
|
15
15
|
// (on Windows, ~ expands to %USERPROFILE%)
|
|
16
16
|
const SETTINGS_PATH = path.join(HOME, '.claude', 'settings.json');
|
|
17
17
|
|
|
18
|
-
// Log file for debugging hook execution
|
|
19
|
-
const LOG_PATH = path.join(HOME, '.claude', 'depester.log');
|
|
20
|
-
const MAX_LOG_ENTRIES = 50;
|
|
21
|
-
|
|
22
18
|
// Use --all to patch all installations (CLI + VS Code binary + webview)
|
|
23
|
-
|
|
24
|
-
const HOOK_COMMAND = 'npx claude-depester --all --silent --log';
|
|
19
|
+
const HOOK_COMMAND = 'npx claude-depester --all --silent';
|
|
25
20
|
|
|
26
21
|
/**
|
|
27
22
|
* Read Claude Code settings
|
|
@@ -184,146 +179,11 @@ function getHookStatus() {
|
|
|
184
179
|
};
|
|
185
180
|
}
|
|
186
181
|
|
|
187
|
-
/**
|
|
188
|
-
* Search Claude debug logs for hook-related entries
|
|
189
|
-
* @param {number} maxFiles - Maximum number of log files to search (default 5)
|
|
190
|
-
* @returns {Array<{file: string, timestamp: string, entries: Array<{time: string, message: string}>}>}
|
|
191
|
-
*/
|
|
192
|
-
function searchHookLogs(maxFiles = 5) {
|
|
193
|
-
const debugDir = path.join(HOME, '.claude', 'debug');
|
|
194
|
-
const results = [];
|
|
195
|
-
|
|
196
|
-
try {
|
|
197
|
-
if (!fs.existsSync(debugDir)) {
|
|
198
|
-
return results;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Get log files sorted by modification time (newest first)
|
|
202
|
-
const files = fs.readdirSync(debugDir)
|
|
203
|
-
.filter(f => f.endsWith('.txt') && f !== 'latest')
|
|
204
|
-
.map(f => ({
|
|
205
|
-
name: f,
|
|
206
|
-
path: path.join(debugDir, f),
|
|
207
|
-
mtime: fs.statSync(path.join(debugDir, f)).mtime
|
|
208
|
-
}))
|
|
209
|
-
.sort((a, b) => b.mtime - a.mtime)
|
|
210
|
-
.slice(0, maxFiles);
|
|
211
|
-
|
|
212
|
-
for (const file of files) {
|
|
213
|
-
const content = fs.readFileSync(file.path, 'utf-8');
|
|
214
|
-
const lines = content.split('\n');
|
|
215
|
-
const hookEntries = [];
|
|
216
|
-
|
|
217
|
-
for (const line of lines) {
|
|
218
|
-
// Look for SessionStart hook entries only (not repo URLs containing "depester")
|
|
219
|
-
if (line.includes('SessionStart') ||
|
|
220
|
-
line.includes('Hook output')) {
|
|
221
|
-
// Parse timestamp from log line: 2026-01-18T09:07:34.119Z [DEBUG] ...
|
|
222
|
-
const match = line.match(/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)\s+\[.*?\]\s+(.*)$/);
|
|
223
|
-
if (match) {
|
|
224
|
-
hookEntries.push({
|
|
225
|
-
time: match[1],
|
|
226
|
-
message: match[2]
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (hookEntries.length > 0) {
|
|
233
|
-
results.push({
|
|
234
|
-
file: file.name,
|
|
235
|
-
timestamp: file.mtime.toISOString(),
|
|
236
|
-
entries: hookEntries
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
} catch (e) {
|
|
241
|
-
// Ignore errors reading logs
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return results;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Append entry to depester log file, keeping only last MAX_LOG_ENTRIES
|
|
249
|
-
* @param {string} message - Log message
|
|
250
|
-
* @param {object} data - Additional data to log
|
|
251
|
-
*/
|
|
252
|
-
function appendLog(message, data = null) {
|
|
253
|
-
try {
|
|
254
|
-
const timestamp = new Date().toISOString();
|
|
255
|
-
const entry = {
|
|
256
|
-
timestamp,
|
|
257
|
-
message,
|
|
258
|
-
...(data && { data })
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
// Read existing entries
|
|
262
|
-
let entries = [];
|
|
263
|
-
if (fs.existsSync(LOG_PATH)) {
|
|
264
|
-
const content = fs.readFileSync(LOG_PATH, 'utf-8');
|
|
265
|
-
const lines = content.trim().split('\n').filter(l => l);
|
|
266
|
-
entries = lines.map(line => {
|
|
267
|
-
try {
|
|
268
|
-
return JSON.parse(line);
|
|
269
|
-
} catch {
|
|
270
|
-
return null;
|
|
271
|
-
}
|
|
272
|
-
}).filter(e => e !== null);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Add new entry
|
|
276
|
-
entries.push(entry);
|
|
277
|
-
|
|
278
|
-
// Keep only last MAX_LOG_ENTRIES
|
|
279
|
-
if (entries.length > MAX_LOG_ENTRIES) {
|
|
280
|
-
entries = entries.slice(-MAX_LOG_ENTRIES);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Write back
|
|
284
|
-
const dir = path.dirname(LOG_PATH);
|
|
285
|
-
if (!fs.existsSync(dir)) {
|
|
286
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
287
|
-
}
|
|
288
|
-
fs.writeFileSync(LOG_PATH, entries.map(e => JSON.stringify(e)).join('\n') + '\n', 'utf-8');
|
|
289
|
-
|
|
290
|
-
} catch (e) {
|
|
291
|
-
// Silently ignore logging errors
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Read depester log entries
|
|
297
|
-
* @returns {Array<{timestamp: string, message: string, data?: object}>}
|
|
298
|
-
*/
|
|
299
|
-
function readLog() {
|
|
300
|
-
try {
|
|
301
|
-
if (!fs.existsSync(LOG_PATH)) {
|
|
302
|
-
return [];
|
|
303
|
-
}
|
|
304
|
-
const content = fs.readFileSync(LOG_PATH, 'utf-8');
|
|
305
|
-
const lines = content.trim().split('\n').filter(l => l);
|
|
306
|
-
return lines.map(line => {
|
|
307
|
-
try {
|
|
308
|
-
return JSON.parse(line);
|
|
309
|
-
} catch {
|
|
310
|
-
return null;
|
|
311
|
-
}
|
|
312
|
-
}).filter(e => e !== null);
|
|
313
|
-
} catch (e) {
|
|
314
|
-
return [];
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
182
|
module.exports = {
|
|
319
183
|
installHook,
|
|
320
184
|
removeHook,
|
|
321
185
|
isHookInstalled,
|
|
322
186
|
getHookStatus,
|
|
323
|
-
searchHookLogs,
|
|
324
|
-
appendLog,
|
|
325
|
-
readLog,
|
|
326
187
|
SETTINGS_PATH,
|
|
327
|
-
HOOK_COMMAND
|
|
328
|
-
LOG_PATH
|
|
188
|
+
HOOK_COMMAND
|
|
329
189
|
};
|