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/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
- const SIZEOF_MODULE = 4 * SIZEOF_STRING_POINTER + 4;
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 / SIZEOF_MODULE);
130
+ const modulesListCount = Math.floor(modulesListBytes.length / moduleStructSize);
97
131
 
98
132
  for (let i = 0; i < modulesListCount; i++) {
99
- const offset = i * SIZEOF_MODULE;
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
- let target = null;
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
- // Fallback
282
- if (isClaudeModule(moduleName) && !target) {
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 target;
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, targetModuleName, targetType) {
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
- let contentsBytes = getStringPointerContent(bunData, module.contents);
307
- let bytecodeBytes = getStringPointerContent(bunData, module.bytecode);
308
-
309
- if (modifiedClaudeJs && moduleName === targetModuleName) {
310
- if (targetType === 'bytecode') {
311
- bytecodeBytes = modifiedClaudeJs;
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
- stringsData.push(nameBytes, contentsBytes, sourcemapBytes, bytecodeBytes);
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 * SIZEOF_MODULE;
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 * 4;
381
- const moduleOffset = modulesListOffset + i * SIZEOF_MODULE;
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 (sizeDiff > 0) {
478
- const isARM64 = machoBinary.header.cpuType === LIEF.MachO.Header.CPU_TYPE.ARM64;
479
- const PAGE_SIZE = isARM64 ? 16384 : 4096;
480
- const alignedSizeDiff = Math.ceil(sizeDiff / PAGE_SIZE) * PAGE_SIZE;
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
- bunSection.content = newSectionData;
486
- bunSection.size = BigInt(newSectionData.length);
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
- // Write atomically
530
+ // Raw file write: copy original binary, overwrite section bytes
489
531
  const tempPath = outputPath + '.tmp';
490
- machoBinary.write(tempPath);
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
- bunSection.content = newSectionData;
514
- bunSection.virtualSize = BigInt(newSectionData.length);
515
- bunSection.size = BigInt(newSectionData.length);
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
- // Write atomically
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
- peBinary.write(tempPath);
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, targetModuleName, targetType) {
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
- // Pass targetModuleName and targetType
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
- // Use --log to write results to ~/.claude/depester.log for debugging
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
  };