@wonderwhy-er/desktop-commander 0.2.3 → 0.2.4

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.
Files changed (43) hide show
  1. package/README.md +25 -3
  2. package/dist/config-manager.d.ts +10 -0
  3. package/dist/config-manager.js +7 -1
  4. package/dist/handlers/edit-search-handlers.js +25 -6
  5. package/dist/handlers/terminal-handlers.d.ts +8 -4
  6. package/dist/handlers/terminal-handlers.js +16 -10
  7. package/dist/index-dxt.d.ts +2 -0
  8. package/dist/index-dxt.js +76 -0
  9. package/dist/index-with-startup-detection.d.ts +5 -0
  10. package/dist/index-with-startup-detection.js +180 -0
  11. package/dist/server.d.ts +5 -0
  12. package/dist/server.js +343 -42
  13. package/dist/terminal-manager.d.ts +7 -0
  14. package/dist/terminal-manager.js +93 -18
  15. package/dist/tools/client.d.ts +10 -0
  16. package/dist/tools/client.js +13 -0
  17. package/dist/tools/config.d.ts +1 -1
  18. package/dist/tools/config.js +21 -3
  19. package/dist/tools/edit.js +4 -3
  20. package/dist/tools/environment.d.ts +55 -0
  21. package/dist/tools/environment.js +65 -0
  22. package/dist/tools/feedback.d.ts +8 -0
  23. package/dist/tools/feedback.js +132 -0
  24. package/dist/tools/filesystem.js +152 -57
  25. package/dist/tools/improved-process-tools.js +170 -29
  26. package/dist/tools/schemas.d.ts +20 -2
  27. package/dist/tools/schemas.js +20 -2
  28. package/dist/tools/usage.d.ts +5 -0
  29. package/dist/tools/usage.js +24 -0
  30. package/dist/utils/capture.js +23 -1
  31. package/dist/utils/early-logger.d.ts +4 -0
  32. package/dist/utils/early-logger.js +35 -0
  33. package/dist/utils/mcp-logger.d.ts +30 -0
  34. package/dist/utils/mcp-logger.js +59 -0
  35. package/dist/utils/smithery-detector.d.ts +94 -0
  36. package/dist/utils/smithery-detector.js +292 -0
  37. package/dist/utils/startup-detector.d.ts +65 -0
  38. package/dist/utils/startup-detector.js +390 -0
  39. package/dist/utils/usageTracker.d.ts +85 -0
  40. package/dist/utils/usageTracker.js +280 -0
  41. package/dist/version.d.ts +1 -1
  42. package/dist/version.js +1 -1
  43. package/package.json +3 -1
@@ -7,6 +7,77 @@ import { createInterface } from 'readline';
7
7
  import { capture } from '../utils/capture.js';
8
8
  import { withTimeout } from '../utils/withTimeout.js';
9
9
  import { configManager } from '../config-manager.js';
10
+ // CONSTANTS SECTION - Consolidate all timeouts and thresholds
11
+ const FILE_OPERATION_TIMEOUTS = {
12
+ PATH_VALIDATION: 10000, // 10 seconds
13
+ URL_FETCH: 30000, // 30 seconds
14
+ FILE_READ: 30000, // 30 seconds
15
+ };
16
+ const FILE_SIZE_LIMITS = {
17
+ LARGE_FILE_THRESHOLD: 10 * 1024 * 1024, // 10MB
18
+ LINE_COUNT_LIMIT: 10 * 1024 * 1024, // 10MB for line counting
19
+ };
20
+ const READ_PERFORMANCE_THRESHOLDS = {
21
+ SMALL_READ_THRESHOLD: 100, // For very small reads
22
+ DEEP_OFFSET_THRESHOLD: 1000, // For byte estimation
23
+ SAMPLE_SIZE: 10000, // Sample size for estimation
24
+ CHUNK_SIZE: 8192, // 8KB chunks for reverse reading
25
+ };
26
+ // UTILITY FUNCTIONS - Eliminate duplication
27
+ /**
28
+ * Count lines in text content efficiently
29
+ * @param content Text content to count lines in
30
+ * @returns Number of lines
31
+ */
32
+ function countLines(content) {
33
+ return content.split('\n').length;
34
+ }
35
+ /**
36
+ * Count lines in a file efficiently (for files under size limit)
37
+ * @param filePath Path to the file
38
+ * @returns Line count or undefined if file too large/can't read
39
+ */
40
+ async function getFileLineCount(filePath) {
41
+ try {
42
+ const stats = await fs.stat(filePath);
43
+ // Only count lines for reasonably sized files to avoid performance issues
44
+ if (stats.size < FILE_SIZE_LIMITS.LINE_COUNT_LIMIT) {
45
+ const content = await fs.readFile(filePath, 'utf8');
46
+ return countLines(content);
47
+ }
48
+ }
49
+ catch (error) {
50
+ // If we can't read the file, just return undefined
51
+ }
52
+ return undefined;
53
+ }
54
+ /**
55
+ * Get MIME type information for a file
56
+ * @param filePath Path to the file
57
+ * @returns Object with mimeType and isImage properties
58
+ */
59
+ async function getMimeTypeInfo(filePath) {
60
+ const { getMimeType, isImageFile } = await import('./mime-types.js');
61
+ const mimeType = getMimeType(filePath);
62
+ const isImage = isImageFile(mimeType);
63
+ return { mimeType, isImage };
64
+ }
65
+ /**
66
+ * Get file extension for telemetry purposes
67
+ * @param filePath Path to the file
68
+ * @returns Lowercase file extension
69
+ */
70
+ function getFileExtension(filePath) {
71
+ return path.extname(filePath).toLowerCase();
72
+ }
73
+ /**
74
+ * Get default read length from configuration
75
+ * @returns Default number of lines to read
76
+ */
77
+ async function getDefaultReadLength() {
78
+ const config = await configManager.getConfig();
79
+ return config.fileReadLineLimit ?? 1000; // Default to 1000 lines if not set
80
+ }
10
81
  // Initialize allowed directories from configuration
11
82
  async function getAllowedDirs() {
12
83
  try {
@@ -116,7 +187,6 @@ async function isPathAllowed(pathToCheck) {
116
187
  * @throws Error if the path or its parent directories don't exist or if the path is not allowed
117
188
  */
118
189
  export async function validatePath(requestedPath) {
119
- const PATH_VALIDATION_TIMEOUT = 10000; // 10 seconds timeout
120
190
  const validationOperation = async () => {
121
191
  // Expand home directory if present
122
192
  const expandedPath = expandHome(requestedPath);
@@ -150,12 +220,12 @@ export async function validatePath(requestedPath) {
150
220
  }
151
221
  };
152
222
  // Execute with timeout
153
- const result = await withTimeout(validationOperation(), PATH_VALIDATION_TIMEOUT, `Path validation operation`, // Generic name for telemetry
223
+ const result = await withTimeout(validationOperation(), FILE_OPERATION_TIMEOUTS.PATH_VALIDATION, `Path validation operation`, // Generic name for telemetry
154
224
  null);
155
225
  if (result === null) {
156
226
  // Keep original path in error for AI but a generic message for telemetry
157
227
  capture('server_path_validation_timeout', {
158
- timeoutMs: PATH_VALIDATION_TIMEOUT
228
+ timeoutMs: FILE_OPERATION_TIMEOUTS.PATH_VALIDATION
159
229
  });
160
230
  throw new Error(`Path validation failed for path: ${requestedPath}`);
161
231
  }
@@ -170,9 +240,8 @@ export async function readFileFromUrl(url) {
170
240
  // Import the MIME type utilities
171
241
  const { isImageFile } = await import('./mime-types.js');
172
242
  // Set up fetch with timeout
173
- const FETCH_TIMEOUT_MS = 30000;
174
243
  const controller = new AbortController();
175
- const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
244
+ const timeoutId = setTimeout(() => controller.abort(), FILE_OPERATION_TIMEOUTS.URL_FETCH);
176
245
  try {
177
246
  const response = await fetch(url, {
178
247
  signal: controller.signal
@@ -202,11 +271,52 @@ export async function readFileFromUrl(url) {
202
271
  clearTimeout(timeoutId);
203
272
  // Return error information instead of throwing
204
273
  const errorMessage = error instanceof DOMException && error.name === 'AbortError'
205
- ? `URL fetch timed out after ${FETCH_TIMEOUT_MS}ms: ${url}`
274
+ ? `URL fetch timed out after ${FILE_OPERATION_TIMEOUTS.URL_FETCH}ms: ${url}`
206
275
  : `Failed to fetch URL: ${error instanceof Error ? error.message : String(error)}`;
207
276
  throw new Error(errorMessage);
208
277
  }
209
278
  }
279
+ /**
280
+ * Generate enhanced status message with total and remaining line information
281
+ * @param readLines Number of lines actually read
282
+ * @param offset Starting offset (line number)
283
+ * @param totalLines Total lines in the file (if available)
284
+ * @param isNegativeOffset Whether this is a tail operation
285
+ * @returns Enhanced status message string
286
+ */
287
+ function generateEnhancedStatusMessage(readLines, offset, totalLines, isNegativeOffset = false) {
288
+ if (isNegativeOffset) {
289
+ // For tail operations (negative offset)
290
+ if (totalLines !== undefined) {
291
+ return `[Reading last ${readLines} lines (total: ${totalLines} lines)]`;
292
+ }
293
+ else {
294
+ return `[Reading last ${readLines} lines]`;
295
+ }
296
+ }
297
+ else {
298
+ // For normal reads (positive offset)
299
+ if (totalLines !== undefined) {
300
+ const endLine = offset + readLines;
301
+ const remainingLines = Math.max(0, totalLines - endLine);
302
+ if (offset === 0) {
303
+ return `[Reading ${readLines} lines from start (total: ${totalLines} lines, ${remainingLines} remaining)]`;
304
+ }
305
+ else {
306
+ return `[Reading ${readLines} lines from line ${offset} (total: ${totalLines} lines, ${remainingLines} remaining)]`;
307
+ }
308
+ }
309
+ else {
310
+ // Fallback when total lines unknown
311
+ if (offset === 0) {
312
+ return `[Reading ${readLines} lines from start]`;
313
+ }
314
+ else {
315
+ return `[Reading ${readLines} lines from line ${offset}]`;
316
+ }
317
+ }
318
+ }
319
+ }
210
320
  /**
211
321
  * Read file content using smart positioning for optimal performance
212
322
  * @param filePath Path to the file (already validated)
@@ -219,34 +329,34 @@ export async function readFileFromUrl(url) {
219
329
  async function readFileWithSmartPositioning(filePath, offset, length, mimeType, includeStatusMessage = true) {
220
330
  const stats = await fs.stat(filePath);
221
331
  const fileSize = stats.size;
222
- const LARGE_FILE_THRESHOLD = 10 * 1024 * 1024; // 10MB threshold
223
- const SMALL_READ_THRESHOLD = 100; // For very small reads, use efficient methods
332
+ // Get total line count for enhanced status messages (only for smaller files)
333
+ const totalLines = await getFileLineCount(filePath);
224
334
  // For negative offsets (tail behavior), use reverse reading
225
335
  if (offset < 0) {
226
336
  const requestedLines = Math.abs(offset);
227
- if (fileSize > LARGE_FILE_THRESHOLD && requestedLines <= SMALL_READ_THRESHOLD) {
337
+ if (fileSize > FILE_SIZE_LIMITS.LARGE_FILE_THRESHOLD && requestedLines <= READ_PERFORMANCE_THRESHOLDS.SMALL_READ_THRESHOLD) {
228
338
  // Use efficient reverse reading for large files with small tail requests
229
- return await readLastNLinesReverse(filePath, requestedLines, mimeType, includeStatusMessage);
339
+ return await readLastNLinesReverse(filePath, requestedLines, mimeType, includeStatusMessage, totalLines);
230
340
  }
231
341
  else {
232
342
  // Use readline circular buffer for other cases
233
- return await readFromEndWithReadline(filePath, requestedLines, mimeType, includeStatusMessage);
343
+ return await readFromEndWithReadline(filePath, requestedLines, mimeType, includeStatusMessage, totalLines);
234
344
  }
235
345
  }
236
346
  // For positive offsets
237
347
  else {
238
348
  // For small files or reading from start, use simple readline
239
- if (fileSize < LARGE_FILE_THRESHOLD || offset === 0) {
240
- return await readFromStartWithReadline(filePath, offset, length, mimeType, includeStatusMessage);
349
+ if (fileSize < FILE_SIZE_LIMITS.LARGE_FILE_THRESHOLD || offset === 0) {
350
+ return await readFromStartWithReadline(filePath, offset, length, mimeType, includeStatusMessage, totalLines);
241
351
  }
242
352
  // For large files with middle/end reads, try to estimate position
243
353
  else {
244
354
  // If seeking deep into file, try byte estimation
245
- if (offset > 1000) {
246
- return await readFromEstimatedPosition(filePath, offset, length, mimeType, includeStatusMessage);
355
+ if (offset > READ_PERFORMANCE_THRESHOLDS.DEEP_OFFSET_THRESHOLD) {
356
+ return await readFromEstimatedPosition(filePath, offset, length, mimeType, includeStatusMessage, totalLines);
247
357
  }
248
358
  else {
249
- return await readFromStartWithReadline(filePath, offset, length, mimeType, includeStatusMessage);
359
+ return await readFromStartWithReadline(filePath, offset, length, mimeType, includeStatusMessage, totalLines);
250
360
  }
251
361
  }
252
362
  }
@@ -254,17 +364,16 @@ async function readFileWithSmartPositioning(filePath, offset, length, mimeType,
254
364
  /**
255
365
  * Read last N lines efficiently by reading file backwards in chunks
256
366
  */
257
- async function readLastNLinesReverse(filePath, n, mimeType, includeStatusMessage = true) {
367
+ async function readLastNLinesReverse(filePath, n, mimeType, includeStatusMessage = true, fileTotalLines) {
258
368
  const fd = await fs.open(filePath, 'r');
259
369
  try {
260
370
  const stats = await fd.stat();
261
371
  const fileSize = stats.size;
262
- const chunkSize = 8192; // 8KB chunks
263
372
  let position = fileSize;
264
373
  let lines = [];
265
374
  let partialLine = '';
266
375
  while (position > 0 && lines.length < n) {
267
- const readSize = Math.min(chunkSize, position);
376
+ const readSize = Math.min(READ_PERFORMANCE_THRESHOLDS.CHUNK_SIZE, position);
268
377
  position -= readSize;
269
378
  const buffer = Buffer.alloc(readSize);
270
379
  await fd.read(buffer, 0, readSize, position);
@@ -280,7 +389,7 @@ async function readLastNLinesReverse(filePath, n, mimeType, includeStatusMessage
280
389
  }
281
390
  const result = lines.slice(-n); // Get exactly n lines
282
391
  const content = includeStatusMessage
283
- ? `[Reading last ${result.length} lines]\n\n${result.join('\n')}`
392
+ ? `${generateEnhancedStatusMessage(result.length, -n, fileTotalLines, true)}\n\n${result.join('\n')}`
284
393
  : result.join('\n');
285
394
  return { content, mimeType, isImage: false };
286
395
  }
@@ -291,7 +400,7 @@ async function readLastNLinesReverse(filePath, n, mimeType, includeStatusMessage
291
400
  /**
292
401
  * Read from end using readline with circular buffer
293
402
  */
294
- async function readFromEndWithReadline(filePath, requestedLines, mimeType, includeStatusMessage = true) {
403
+ async function readFromEndWithReadline(filePath, requestedLines, mimeType, includeStatusMessage = true, fileTotalLines) {
295
404
  const rl = createInterface({
296
405
  input: createReadStream(filePath),
297
406
  crlfDelay: Infinity
@@ -317,14 +426,14 @@ async function readFromEndWithReadline(filePath, requestedLines, mimeType, inclu
317
426
  result = buffer.slice(0, totalLines);
318
427
  }
319
428
  const content = includeStatusMessage
320
- ? `[Reading last ${result.length} lines]\n\n${result.join('\n')}`
429
+ ? `${generateEnhancedStatusMessage(result.length, -requestedLines, fileTotalLines, true)}\n\n${result.join('\n')}`
321
430
  : result.join('\n');
322
431
  return { content, mimeType, isImage: false };
323
432
  }
324
433
  /**
325
434
  * Read from start/middle using readline
326
435
  */
327
- async function readFromStartWithReadline(filePath, offset, length, mimeType, includeStatusMessage = true) {
436
+ async function readFromStartWithReadline(filePath, offset, length, mimeType, includeStatusMessage = true, fileTotalLines) {
328
437
  const rl = createInterface({
329
438
  input: createReadStream(filePath),
330
439
  crlfDelay: Infinity
@@ -341,9 +450,7 @@ async function readFromStartWithReadline(filePath, offset, length, mimeType, inc
341
450
  }
342
451
  rl.close();
343
452
  if (includeStatusMessage) {
344
- const statusMessage = offset === 0
345
- ? `[Reading ${result.length} lines from start]`
346
- : `[Reading ${result.length} lines from line ${offset}]`;
453
+ const statusMessage = generateEnhancedStatusMessage(result.length, offset, fileTotalLines, false);
347
454
  const content = `${statusMessage}\n\n${result.join('\n')}`;
348
455
  return { content, mimeType, isImage: false };
349
456
  }
@@ -355,7 +462,7 @@ async function readFromStartWithReadline(filePath, offset, length, mimeType, inc
355
462
  /**
356
463
  * Read from estimated byte position for very large files
357
464
  */
358
- async function readFromEstimatedPosition(filePath, offset, length, mimeType, includeStatusMessage = true) {
465
+ async function readFromEstimatedPosition(filePath, offset, length, mimeType, includeStatusMessage = true, fileTotalLines) {
359
466
  // First, do a quick scan to estimate lines per byte
360
467
  const rl = createInterface({
361
468
  input: createReadStream(filePath),
@@ -363,17 +470,16 @@ async function readFromEstimatedPosition(filePath, offset, length, mimeType, inc
363
470
  });
364
471
  let sampleLines = 0;
365
472
  let bytesRead = 0;
366
- const SAMPLE_SIZE = 10000; // Sample first 10KB
367
473
  for await (const line of rl) {
368
474
  bytesRead += Buffer.byteLength(line, 'utf-8') + 1; // +1 for newline
369
475
  sampleLines++;
370
- if (bytesRead >= SAMPLE_SIZE)
476
+ if (bytesRead >= READ_PERFORMANCE_THRESHOLDS.SAMPLE_SIZE)
371
477
  break;
372
478
  }
373
479
  rl.close();
374
480
  if (sampleLines === 0) {
375
481
  // Fallback to simple read
376
- return await readFromStartWithReadline(filePath, offset, length, mimeType, includeStatusMessage);
482
+ return await readFromStartWithReadline(filePath, offset, length, mimeType, includeStatusMessage, fileTotalLines);
377
483
  }
378
484
  // Estimate average line length and seek position
379
485
  const avgLineLength = bytesRead / sampleLines;
@@ -407,7 +513,7 @@ async function readFromEstimatedPosition(filePath, offset, length, mimeType, inc
407
513
  }
408
514
  rl2.close();
409
515
  const content = includeStatusMessage
410
- ? `[Reading ${result.length} lines from estimated position (target line ${offset})]\n\n${result.join('\n')}`
516
+ ? `${generateEnhancedStatusMessage(result.length, offset, fileTotalLines, false)}\n\n${result.join('\n')}`
411
517
  : result.join('\n');
412
518
  return { content, mimeType, isImage: false };
413
519
  }
@@ -427,16 +533,13 @@ export async function readFileFromDisk(filePath, offset = 0, length) {
427
533
  if (!filePath || typeof filePath !== 'string') {
428
534
  throw new Error('Invalid file path provided');
429
535
  }
430
- // Import the MIME type utilities
431
- const { getMimeType, isImageFile } = await import('./mime-types.js');
432
536
  // Get default length from config if not provided
433
537
  if (length === undefined) {
434
- const config = await configManager.getConfig();
435
- length = config.fileReadLineLimit ?? 1000; // Default to 1000 lines if not set
538
+ length = await getDefaultReadLength();
436
539
  }
437
540
  const validPath = await validatePath(filePath);
438
541
  // Get file extension for telemetry using path module consistently
439
- const fileExtension = path.extname(validPath).toLowerCase();
542
+ const fileExtension = getFileExtension(validPath);
440
543
  // Check file size before attempting to read
441
544
  try {
442
545
  const stats = await fs.stat(validPath);
@@ -455,9 +558,7 @@ export async function readFileFromDisk(filePath, offset = 0, length) {
455
558
  // If we can't stat the file, continue anyway and let the read operation handle errors
456
559
  }
457
560
  // Detect the MIME type based on file extension
458
- const mimeType = getMimeType(validPath);
459
- const isImage = isImageFile(mimeType);
460
- const FILE_READ_TIMEOUT = 30000; // 30 seconds timeout for file operations
561
+ const { mimeType, isImage } = await getMimeTypeInfo(validPath);
461
562
  // Use withTimeout to handle potential hangs
462
563
  const readOperation = async () => {
463
564
  if (isImage) {
@@ -481,7 +582,7 @@ export async function readFileFromDisk(filePath, offset = 0, length) {
481
582
  }
482
583
  };
483
584
  // Execute with timeout
484
- const result = await withTimeout(readOperation(), FILE_READ_TIMEOUT, `Read file operation for ${filePath}`, null);
585
+ const result = await withTimeout(readOperation(), FILE_OPERATION_TIMEOUTS.FILE_READ, `Read file operation for ${filePath}`, null);
485
586
  if (result == null) {
486
587
  // Handles the impossible case where withTimeout resolves to null instead of throwing
487
588
  throw new Error('Failed to read the file');
@@ -513,20 +614,17 @@ export async function readFile(filePath, isUrl, offset, length) {
513
614
  export async function readFileInternal(filePath, offset = 0, length) {
514
615
  // Get default length from config if not provided
515
616
  if (length === undefined) {
516
- const config = await configManager.getConfig();
517
- length = config.fileReadLineLimit ?? 1000;
617
+ length = await getDefaultReadLength();
518
618
  }
519
619
  const validPath = await validatePath(filePath);
520
620
  // Get file extension and MIME type
521
- const fileExtension = path.extname(validPath).toLowerCase();
522
- const { getMimeType, isImageFile } = await import('./mime-types.js');
523
- const mimeType = getMimeType(validPath);
524
- const isImage = isImageFile(mimeType);
621
+ const fileExtension = getFileExtension(validPath);
622
+ const { mimeType, isImage } = await getMimeTypeInfo(validPath);
525
623
  if (isImage) {
526
624
  throw new Error('Cannot read image files as text for internal operations');
527
625
  }
528
626
  // IMPORTANT: For internal operations (especially edit operations), we must
529
- // preserve exact file content including original line endings.
627
+ // preserve exact file content including original line endings.
530
628
  // We cannot use readline-based reading as it strips line endings.
531
629
  // Read entire file content preserving line endings
532
630
  const content = await fs.readFile(validPath, 'utf8');
@@ -582,10 +680,10 @@ function splitLinesPreservingEndings(content) {
582
680
  export async function writeFile(filePath, content, mode = 'rewrite') {
583
681
  const validPath = await validatePath(filePath);
584
682
  // Get file extension for telemetry
585
- const fileExtension = path.extname(validPath).toLowerCase();
683
+ const fileExtension = getFileExtension(validPath);
586
684
  // Calculate content metrics
587
685
  const contentBytes = Buffer.from(content).length;
588
- const lineCount = content.split('\n').length;
686
+ const lineCount = countLines(content);
589
687
  // Capture file extension and operation details in telemetry without capturing the file path
590
688
  capture('server_write_file', {
591
689
  fileExtension: fileExtension,
@@ -698,15 +796,14 @@ export async function getFileInfo(filePath) {
698
796
  permissions: stats.mode.toString(8).slice(-3),
699
797
  };
700
798
  // For text files that aren't too large, also count lines
701
- if (stats.isFile() && stats.size < 10 * 1024 * 1024) { // Limit to 10MB files
799
+ if (stats.isFile() && stats.size < FILE_SIZE_LIMITS.LINE_COUNT_LIMIT) {
702
800
  try {
703
- // Import the MIME type utilities
704
- const { getMimeType, isImageFile } = await import('./mime-types.js');
705
- const mimeType = getMimeType(validPath);
801
+ // Get MIME type information
802
+ const { mimeType, isImage } = await getMimeTypeInfo(validPath);
706
803
  // Only count lines for non-image, likely text files
707
- if (!isImageFile(mimeType)) {
804
+ if (!isImage) {
708
805
  const content = await fs.readFile(validPath, 'utf8');
709
- const lineCount = content.split('\n').length;
806
+ const lineCount = countLines(content);
710
807
  info.lineCount = lineCount;
711
808
  info.lastLine = lineCount - 1; // Zero-indexed last line
712
809
  info.appendPosition = lineCount; // Position to append at end
@@ -719,5 +816,3 @@ export async function getFileInfo(filePath) {
719
816
  }
720
817
  return info;
721
818
  }
722
- // This function has been replaced with configManager.getConfig()
723
- // Use get_config tool to retrieve allowedDirectories