@wonderwhy-er/desktop-commander 0.1.34 → 0.1.36

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 (63) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +186 -56
  3. package/dist/command-manager.d.ts +1 -7
  4. package/dist/command-manager.js +31 -50
  5. package/dist/config-manager.d.ts +28 -16
  6. package/dist/config-manager.js +124 -189
  7. package/dist/config.d.ts +2 -2
  8. package/dist/config.js +7 -4
  9. package/dist/error-handlers.js +4 -0
  10. package/dist/handlers/edit-search-handlers.d.ts +3 -1
  11. package/dist/handlers/edit-search-handlers.js +9 -19
  12. package/dist/handlers/filesystem-handlers.d.ts +0 -4
  13. package/dist/handlers/filesystem-handlers.js +11 -19
  14. package/dist/handlers/index.d.ts +0 -1
  15. package/dist/handlers/index.js +0 -1
  16. package/dist/index.js +19 -4
  17. package/dist/polyform-license-src/edit/edit.d.ts +15 -0
  18. package/dist/polyform-license-src/edit/edit.js +163 -0
  19. package/dist/polyform-license-src/edit/fuzzySearch.d.ts +30 -0
  20. package/dist/polyform-license-src/edit/fuzzySearch.js +121 -0
  21. package/dist/polyform-license-src/edit/handlers.d.ts +16 -0
  22. package/dist/polyform-license-src/edit/handlers.js +24 -0
  23. package/dist/polyform-license-src/edit/index.d.ts +12 -0
  24. package/dist/polyform-license-src/edit/index.js +13 -0
  25. package/dist/polyform-license-src/edit/schemas.d.ts +25 -0
  26. package/dist/polyform-license-src/edit/schemas.js +16 -0
  27. package/dist/polyform-license-src/index.d.ts +9 -0
  28. package/dist/polyform-license-src/index.js +10 -0
  29. package/dist/sandbox/index.d.ts +9 -0
  30. package/dist/sandbox/index.js +50 -0
  31. package/dist/sandbox/mac-sandbox.d.ts +19 -0
  32. package/dist/sandbox/mac-sandbox.js +174 -0
  33. package/dist/server.js +181 -176
  34. package/dist/setup-claude-server.js +554 -244
  35. package/dist/terminal-manager.d.ts +1 -1
  36. package/dist/terminal-manager.js +22 -3
  37. package/dist/tools/config.d.ts +0 -58
  38. package/dist/tools/config.js +44 -107
  39. package/dist/tools/debug-path.d.ts +1 -0
  40. package/dist/tools/debug-path.js +44 -0
  41. package/dist/tools/edit.d.ts +8 -6
  42. package/dist/tools/edit.js +165 -35
  43. package/dist/tools/execute.js +6 -6
  44. package/dist/tools/filesystem-fixed.d.ts +22 -0
  45. package/dist/tools/filesystem-fixed.js +176 -0
  46. package/dist/tools/filesystem.d.ts +4 -6
  47. package/dist/tools/filesystem.js +157 -87
  48. package/dist/tools/fuzzySearch.d.ts +22 -0
  49. package/dist/tools/fuzzySearch.js +113 -0
  50. package/dist/tools/pdf-reader.d.ts +13 -0
  51. package/dist/tools/pdf-reader.js +214 -0
  52. package/dist/tools/schemas.d.ts +29 -19
  53. package/dist/tools/schemas.js +15 -8
  54. package/dist/tools/search.js +5 -4
  55. package/dist/utils/capture.d.ts +15 -0
  56. package/dist/utils/capture.js +175 -0
  57. package/dist/utils/withTimeout.d.ts +11 -0
  58. package/dist/utils/withTimeout.js +52 -0
  59. package/dist/utils.d.ts +15 -1
  60. package/dist/utils.js +174 -41
  61. package/dist/version.d.ts +1 -1
  62. package/dist/version.js +1 -1
  63. package/package.json +2 -3
@@ -0,0 +1,176 @@
1
+ if (returnMetadata === true) {
2
+ return { content, mimeType, isImage };
3
+ }
4
+ else {
5
+ return content;
6
+ }
7
+ try { }
8
+ catch (error) {
9
+ // If UTF-8 reading fails, treat as binary and return base64 but still as text
10
+ const buffer = await fs.readFile(validPath);
11
+ const content = `Binary file content (base64 encoded):\n${buffer.toString('base64')}`;
12
+ if (returnMetadata === true) {
13
+ return { content, mimeType: 'text/plain', isImage: false };
14
+ }
15
+ else {
16
+ return content;
17
+ }
18
+ }
19
+ ;
20
+ // Execute with timeout
21
+ const result = await withTimeout(readOperation(), FILE_READ_TIMEOUT, `Read file operation for ${filePath}`, returnMetadata ?
22
+ { content: `Operation timed out after ${FILE_READ_TIMEOUT / 1000} seconds`, mimeType: 'text/plain', isImage: false } :
23
+ `Operation timed out after ${FILE_READ_TIMEOUT / 1000} seconds`);
24
+ return result;
25
+ /**
26
+ * Read a file from either the local filesystem or a URL
27
+ * @param filePath Path to the file or URL
28
+ * @param returnMetadata Whether to return metadata with the content
29
+ * @param isUrl Whether the path is a URL
30
+ * @returns File content or file result with metadata
31
+ */
32
+ export async function readFile(filePath, returnMetadata, isUrl) {
33
+ return isUrl
34
+ ? readFileFromUrl(filePath, returnMetadata)
35
+ : readFileFromDisk(filePath, returnMetadata);
36
+ }
37
+ export async function writeFile(filePath, content) {
38
+ // Expand home directory in filePath before validation
39
+ const expandedFilePath = expandHome(filePath);
40
+ const validPath = await validatePath(expandedFilePath);
41
+ // Check if the path validation returned an error
42
+ if (validPath.startsWith('__ERROR__:')) {
43
+ throw new Error(validPath.substring('__ERROR__:'.length).trim());
44
+ }
45
+ await fs.writeFile(validPath, content, "utf-8");
46
+ }
47
+ export async function readMultipleFiles(paths) {
48
+ return Promise.all(paths.map(async (filePath) => {
49
+ try {
50
+ // Expand home directory in filePath before validation
51
+ const expandedFilePath = expandHome(filePath);
52
+ const validPath = await validatePath(expandedFilePath);
53
+ // Check if the path validation returned an error
54
+ if (validPath.startsWith('__ERROR__:')) {
55
+ return {
56
+ path: filePath,
57
+ error: validPath.substring('__ERROR__:'.length).trim()
58
+ };
59
+ }
60
+ const fileResult = await readFile(validPath, true);
61
+ return {
62
+ path: filePath,
63
+ content: typeof fileResult === 'string' ? fileResult : fileResult.content,
64
+ mimeType: typeof fileResult === 'string' ? "text/plain" : fileResult.mimeType,
65
+ isImage: typeof fileResult === 'string' ? false : fileResult.isImage
66
+ };
67
+ }
68
+ catch (error) {
69
+ const errorMessage = error instanceof Error ? error.message : String(error);
70
+ return {
71
+ path: filePath,
72
+ error: errorMessage
73
+ };
74
+ }
75
+ }));
76
+ }
77
+ export async function createDirectory(dirPath) {
78
+ // Expand home directory in dirPath before validation
79
+ const expandedDirPath = expandHome(dirPath);
80
+ const validPath = await validatePath(expandedDirPath);
81
+ // Check if the path validation returned an error
82
+ if (validPath.startsWith('__ERROR__:')) {
83
+ throw new Error(validPath.substring('__ERROR__:'.length).trim());
84
+ }
85
+ await fs.mkdir(validPath, { recursive: true });
86
+ }
87
+ export async function listDirectory(dirPath) {
88
+ console.log(`List directory called with: ${dirPath}`);
89
+ // Handle tilde expansion directly here as well
90
+ const expandedDirPath = expandHome(dirPath);
91
+ console.log(`Expanded dir path: ${expandedDirPath}`);
92
+ const validPath = await validatePath(expandedDirPath);
93
+ console.log(`Valid path: ${validPath}`);
94
+ // Check if the path starts with an error indicator
95
+ if (validPath.startsWith('__ERROR__:')) {
96
+ console.error(`Path validation error: ${validPath}`);
97
+ return [`Error: ${validPath.substring('__ERROR__:'.length).trim()}`];
98
+ }
99
+ try {
100
+ const entries = await fs.readdir(validPath, { withFileTypes: true });
101
+ return entries.map((entry) => `${entry.isDirectory() ? "[DIR]" : "[FILE]"} ${entry.name}`);
102
+ }
103
+ catch (error) {
104
+ const errorMessage = error instanceof Error ? error.message : String(error);
105
+ console.error(`Error reading directory: ${errorMessage}`);
106
+ return [`Error reading directory: ${errorMessage}`];
107
+ }
108
+ }
109
+ export async function moveFile(sourcePath, destinationPath) {
110
+ // Expand home directory in both paths before validation
111
+ const expandedSourcePath = expandHome(sourcePath);
112
+ const expandedDestPath = expandHome(destinationPath);
113
+ const validSourcePath = await validatePath(expandedSourcePath);
114
+ const validDestPath = await validatePath(expandedDestPath);
115
+ // Check if either path validation returned an error
116
+ if (validSourcePath.startsWith('__ERROR__:')) {
117
+ throw new Error(validSourcePath.substring('__ERROR__:'.length).trim());
118
+ }
119
+ if (validDestPath.startsWith('__ERROR__:')) {
120
+ throw new Error(validDestPath.substring('__ERROR__:'.length).trim());
121
+ }
122
+ await fs.rename(validSourcePath, validDestPath);
123
+ }
124
+ export async function searchFiles(rootPath, pattern) {
125
+ const results = [];
126
+ async function search(currentPath) {
127
+ const entries = await fs.readdir(currentPath, { withFileTypes: true });
128
+ for (const entry of entries) {
129
+ const fullPath = path.join(currentPath, entry.name);
130
+ try {
131
+ // No need to expand home directory here as we're generating full paths
132
+ // from the already validated rootPath
133
+ await validatePath(fullPath);
134
+ if (entry.name.toLowerCase().includes(pattern.toLowerCase())) {
135
+ results.push(fullPath);
136
+ }
137
+ if (entry.isDirectory()) {
138
+ await search(fullPath);
139
+ }
140
+ }
141
+ catch (error) {
142
+ continue;
143
+ }
144
+ }
145
+ }
146
+ // Expand home directory in rootPath before validation
147
+ const expandedRootPath = expandHome(rootPath);
148
+ const validPath = await validatePath(expandedRootPath);
149
+ // Check if the path validation returned an error
150
+ if (validPath.startsWith('__ERROR__:')) {
151
+ throw new Error(validPath.substring('__ERROR__:'.length).trim());
152
+ }
153
+ await search(validPath);
154
+ return results;
155
+ }
156
+ export async function getFileInfo(filePath) {
157
+ // Expand home directory in filePath before validation
158
+ const expandedFilePath = expandHome(filePath);
159
+ const validPath = await validatePath(expandedFilePath);
160
+ // Check if the path validation returned an error
161
+ if (validPath.startsWith('__ERROR__:')) {
162
+ throw new Error(validPath.substring('__ERROR__:'.length).trim());
163
+ }
164
+ const stats = await fs.stat(validPath);
165
+ return {
166
+ size: stats.size,
167
+ created: stats.birthtime,
168
+ modified: stats.mtime,
169
+ accessed: stats.atime,
170
+ isDirectory: stats.isDirectory(),
171
+ isFile: stats.isFile(),
172
+ permissions: stats.mode.toString(8).slice(-3),
173
+ };
174
+ }
175
+ // This function has been replaced with configManager.getConfig()
176
+ // Use get_config tool to retrieve allowedDirectories
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * @param requestedPath The path to validate
7
7
  * @returns Promise<string> The validated path
8
- * @throws Error if the path or its parent directories don't exist
8
+ * @throws Error if the path or its parent directories don't exist or if the path is not allowed
9
9
  */
10
10
  export declare function validatePath(requestedPath: string): Promise<string>;
11
11
  export interface FileResult {
@@ -16,17 +16,16 @@ export interface FileResult {
16
16
  /**
17
17
  * Read file content from a URL
18
18
  * @param url URL to fetch content from
19
- * @param returnMetadata Whether to return metadata with the content
20
19
  * @returns File content or file result with metadata
21
20
  */
22
- export declare function readFileFromUrl(url: string, returnMetadata?: boolean): Promise<string | FileResult>;
21
+ export declare function readFileFromUrl(url: string): Promise<FileResult>;
23
22
  /**
24
23
  * Read file content from the local filesystem
25
24
  * @param filePath Path to the file
26
25
  * @param returnMetadata Whether to return metadata with the content
27
26
  * @returns File content or file result with metadata
28
27
  */
29
- export declare function readFileFromDisk(filePath: string, returnMetadata?: boolean): Promise<string | FileResult>;
28
+ export declare function readFileFromDisk(filePath: string): Promise<FileResult>;
30
29
  /**
31
30
  * Read a file from either the local filesystem or a URL
32
31
  * @param filePath Path to the file or URL
@@ -34,7 +33,7 @@ export declare function readFileFromDisk(filePath: string, returnMetadata?: bool
34
33
  * @param isUrl Whether the path is a URL
35
34
  * @returns File content or file result with metadata
36
35
  */
37
- export declare function readFile(filePath: string, returnMetadata?: boolean, isUrl?: boolean): Promise<string | FileResult>;
36
+ export declare function readFile(filePath: string, isUrl?: boolean): Promise<FileResult>;
38
37
  export declare function writeFile(filePath: string, content: string): Promise<void>;
39
38
  export interface MultiFileResult {
40
39
  path: string;
@@ -49,4 +48,3 @@ export declare function listDirectory(dirPath: string): Promise<string[]>;
49
48
  export declare function moveFile(sourcePath: string, destinationPath: string): Promise<void>;
50
49
  export declare function searchFiles(rootPath: string, pattern: string): Promise<string[]>;
51
50
  export declare function getFileInfo(filePath: string): Promise<Record<string, any>>;
52
- export declare function listAllowedDirectories(): string[];
@@ -2,22 +2,36 @@ import fs from "fs/promises";
2
2
  import path from "path";
3
3
  import os from 'os';
4
4
  import fetch from 'cross-fetch';
5
- import { withTimeout } from '../utils.js';
6
- // Store allowed directories - temporarily allowing all paths
7
- // TODO: Make this configurable through a configuration file
8
- const allowedDirectories = [
9
- "/" // Root directory - effectively allows all paths
10
- ];
11
- // Original implementation commented out for future reference
12
- /*
13
- const allowedDirectories: string[] = [
14
- process.cwd(), // Current working directory
15
- os.homedir() // User's home directory
16
- ];
17
- */
5
+ import { capture } from '../utils/capture.js';
6
+ import { withTimeout } from '../utils/withTimeout.js';
7
+ import { configManager } from '../config-manager.js';
8
+ // Initialize allowed directories from configuration
9
+ async function getAllowedDirs() {
10
+ try {
11
+ let allowedDirectories;
12
+ const config = await configManager.getConfig();
13
+ if (config.allowedDirectories && Array.isArray(config.allowedDirectories)) {
14
+ allowedDirectories = config.allowedDirectories;
15
+ }
16
+ else {
17
+ // Fall back to default directories if not configured
18
+ allowedDirectories = [
19
+ os.homedir() // User's home directory
20
+ ];
21
+ // Update config with default
22
+ await configManager.setValue('allowedDirectories', allowedDirectories);
23
+ }
24
+ return allowedDirectories;
25
+ }
26
+ catch (error) {
27
+ console.error('Failed to initialize allowed directories:', error);
28
+ // Keep the default permissive path
29
+ }
30
+ return [];
31
+ }
18
32
  // Normalize all paths consistently
19
33
  function normalizePath(p) {
20
- return path.normalize(p).toLowerCase();
34
+ return path.normalize(expandHome(p)).toLowerCase();
21
35
  }
22
36
  function expandHome(filepath) {
23
37
  if (filepath.startsWith('~/') || filepath === '~') {
@@ -49,6 +63,47 @@ async function validateParentDirectories(directoryPath) {
49
63
  return validateParentDirectories(parentDir);
50
64
  }
51
65
  }
66
+ /**
67
+ * Checks if a path is within any of the allowed directories
68
+ *
69
+ * @param pathToCheck Path to check
70
+ * @returns boolean True if path is allowed
71
+ */
72
+ async function isPathAllowed(pathToCheck) {
73
+ // If root directory is allowed, all paths are allowed
74
+ const allowedDirectories = await getAllowedDirs();
75
+ if (allowedDirectories.includes('/') || allowedDirectories.length === 0) {
76
+ return true;
77
+ }
78
+ let normalizedPathToCheck = normalizePath(pathToCheck);
79
+ if (normalizedPathToCheck.slice(-1) === path.sep) {
80
+ normalizedPathToCheck = normalizedPathToCheck.slice(0, -1);
81
+ }
82
+ // Check if the path is within any allowed directory
83
+ const isAllowed = allowedDirectories.some(allowedDir => {
84
+ let normalizedAllowedDir = normalizePath(allowedDir);
85
+ if (normalizedAllowedDir.slice(-1) === path.sep) {
86
+ normalizedAllowedDir = normalizedAllowedDir.slice(0, -1);
87
+ }
88
+ // Check if path is exactly the allowed directory
89
+ if (normalizedPathToCheck === normalizedAllowedDir) {
90
+ return true;
91
+ }
92
+ // Check if path is a subdirectory of the allowed directory
93
+ // Make sure to add a separator to prevent partial directory name matches
94
+ // e.g. /home/user vs /home/username
95
+ const subdirCheck = normalizedPathToCheck.startsWith(normalizedAllowedDir + path.sep);
96
+ if (subdirCheck) {
97
+ return true;
98
+ }
99
+ // If allowed directory is the root (C:\ on Windows), allow access to the entire drive
100
+ if (normalizedAllowedDir === 'c:' && process.platform === 'win32') {
101
+ return normalizedPathToCheck.startsWith('c:');
102
+ }
103
+ return false;
104
+ });
105
+ return isAllowed;
106
+ }
52
107
  /**
53
108
  * Validates a path to ensure it can be accessed or created.
54
109
  * For existing paths, returns the real path (resolving symlinks).
@@ -56,7 +111,7 @@ async function validateParentDirectories(directoryPath) {
56
111
  *
57
112
  * @param requestedPath The path to validate
58
113
  * @returns Promise<string> The validated path
59
- * @throws Error if the path or its parent directories don't exist
114
+ * @throws Error if the path or its parent directories don't exist or if the path is not allowed
60
115
  */
61
116
  export async function validatePath(requestedPath) {
62
117
  const PATH_VALIDATION_TIMEOUT = 10000; // 10 seconds timeout
@@ -67,6 +122,14 @@ export async function validatePath(requestedPath) {
67
122
  const absolute = path.isAbsolute(expandedPath)
68
123
  ? path.resolve(expandedPath)
69
124
  : path.resolve(process.cwd(), expandedPath);
125
+ // Check if path is allowed
126
+ if (!(await isPathAllowed(absolute))) {
127
+ capture('server_path_validation_error', {
128
+ error: 'Path not allowed',
129
+ allowedDirsCount: (await getAllowedDirs()).length
130
+ });
131
+ throw new Error(`Path not allowed: ${requestedPath}. Must be within one of these directories: ${(await getAllowedDirs()).join(', ')}`);
132
+ }
70
133
  // Check if path exists
71
134
  try {
72
135
  const stats = await fs.stat(absolute);
@@ -85,20 +148,23 @@ export async function validatePath(requestedPath) {
85
148
  }
86
149
  };
87
150
  // Execute with timeout
88
- const result = await withTimeout(validationOperation(), PATH_VALIDATION_TIMEOUT, `Path validation for ${requestedPath}`, null);
151
+ const result = await withTimeout(validationOperation(), PATH_VALIDATION_TIMEOUT, `Path validation operation`, // Generic name for telemetry
152
+ null);
89
153
  if (result === null) {
90
- // Return a path with an error indicator instead of throwing
91
- return `__ERROR__: Path validation timed out after ${PATH_VALIDATION_TIMEOUT / 1000} seconds for: ${requestedPath}`;
154
+ // Keep original path in error for AI but a generic message for telemetry
155
+ capture('server_path_validation_timeout', {
156
+ timeoutMs: PATH_VALIDATION_TIMEOUT
157
+ });
158
+ throw new Error(`Path validation failed for path: ${requestedPath}`);
92
159
  }
93
160
  return result;
94
161
  }
95
162
  /**
96
163
  * Read file content from a URL
97
164
  * @param url URL to fetch content from
98
- * @param returnMetadata Whether to return metadata with the content
99
165
  * @returns File content or file result with metadata
100
166
  */
101
- export async function readFileFromUrl(url, returnMetadata) {
167
+ export async function readFileFromUrl(url) {
102
168
  // Import the MIME type utilities
103
169
  const { isImageFile } = await import('./mime-types.js');
104
170
  // Set up fetch with timeout
@@ -121,22 +187,12 @@ export async function readFileFromUrl(url, returnMetadata) {
121
187
  // For images, convert to base64
122
188
  const buffer = await response.arrayBuffer();
123
189
  const content = Buffer.from(buffer).toString('base64');
124
- if (returnMetadata === true) {
125
- return { content, mimeType: contentType, isImage };
126
- }
127
- else {
128
- return content;
129
- }
190
+ return { content, mimeType: contentType, isImage };
130
191
  }
131
192
  else {
132
193
  // For text content
133
194
  const content = await response.text();
134
- if (returnMetadata === true) {
135
- return { content, mimeType: contentType, isImage };
136
- }
137
- else {
138
- return content;
139
- }
195
+ return { content, mimeType: contentType, isImage };
140
196
  }
141
197
  }
142
198
  catch (error) {
@@ -146,16 +202,7 @@ export async function readFileFromUrl(url, returnMetadata) {
146
202
  const errorMessage = error instanceof DOMException && error.name === 'AbortError'
147
203
  ? `URL fetch timed out after ${FETCH_TIMEOUT_MS}ms: ${url}`
148
204
  : `Failed to fetch URL: ${error instanceof Error ? error.message : String(error)}`;
149
- if (returnMetadata === true) {
150
- return {
151
- content: `Error: ${errorMessage}`,
152
- mimeType: 'text/plain',
153
- isImage: false
154
- };
155
- }
156
- else {
157
- return `Error: ${errorMessage}`;
158
- }
205
+ throw new Error(errorMessage);
159
206
  }
160
207
  }
161
208
  /**
@@ -164,31 +211,35 @@ export async function readFileFromUrl(url, returnMetadata) {
164
211
  * @param returnMetadata Whether to return metadata with the content
165
212
  * @returns File content or file result with metadata
166
213
  */
167
- export async function readFileFromDisk(filePath, returnMetadata) {
214
+ export async function readFileFromDisk(filePath) {
168
215
  // Import the MIME type utilities
169
216
  const { getMimeType, isImageFile } = await import('./mime-types.js');
170
217
  const validPath = await validatePath(filePath);
218
+ // Get file extension for telemetry using path module consistently
219
+ const fileExtension = path.extname(validPath).toLowerCase();
171
220
  // Check file size before attempting to read
172
221
  try {
173
222
  const stats = await fs.stat(validPath);
174
223
  const MAX_SIZE = 100 * 1024; // 100KB limit
175
224
  if (stats.size > MAX_SIZE) {
176
225
  const message = `File too large (${(stats.size / 1024).toFixed(2)}KB > ${MAX_SIZE / 1024}KB limit)`;
177
- if (returnMetadata) {
178
- return {
179
- content: message,
180
- mimeType: 'text/plain',
181
- isImage: false
182
- };
183
- }
184
- else {
185
- return message;
186
- }
226
+ // Capture file extension in telemetry without capturing the file path
227
+ capture('server_read_file_large', { fileExtension: fileExtension });
228
+ return {
229
+ content: message,
230
+ mimeType: 'text/plain',
231
+ isImage: false
232
+ };
187
233
  }
234
+ // Capture file extension in telemetry without capturing the file path
235
+ capture('server_read_file', { fileExtension: fileExtension });
188
236
  }
189
237
  catch (error) {
238
+ console.error('error catch ' + error);
239
+ const errorMessage = error instanceof Error ? error.message : String(error);
240
+ capture('server_read_file_error', { error: errorMessage, fileExtension: fileExtension });
190
241
  // If we can't stat the file, continue anyway and let the read operation handle errors
191
- console.error(`Failed to stat file ${validPath}:`, error);
242
+ //console.error(`Failed to stat file ${validPath}:`, error);
192
243
  }
193
244
  // Detect the MIME type based on file extension
194
245
  const mimeType = getMimeType(validPath);
@@ -200,41 +251,28 @@ export async function readFileFromDisk(filePath, returnMetadata) {
200
251
  // For image files, read as Buffer and convert to base64
201
252
  const buffer = await fs.readFile(validPath);
202
253
  const content = buffer.toString('base64');
203
- if (returnMetadata === true) {
204
- return { content, mimeType, isImage };
205
- }
206
- else {
207
- return content;
208
- }
254
+ return { content, mimeType, isImage };
209
255
  }
210
256
  else {
211
257
  // For all other files, try to read as UTF-8 text
212
258
  try {
213
259
  const content = await fs.readFile(validPath, "utf-8");
214
- if (returnMetadata === true) {
215
- return { content, mimeType, isImage };
216
- }
217
- else {
218
- return content;
219
- }
260
+ return { content, mimeType, isImage };
220
261
  }
221
262
  catch (error) {
222
263
  // If UTF-8 reading fails, treat as binary and return base64 but still as text
223
264
  const buffer = await fs.readFile(validPath);
224
265
  const content = `Binary file content (base64 encoded):\n${buffer.toString('base64')}`;
225
- if (returnMetadata === true) {
226
- return { content, mimeType: 'text/plain', isImage: false };
227
- }
228
- else {
229
- return content;
230
- }
266
+ return { content, mimeType: 'text/plain', isImage: false };
231
267
  }
232
268
  }
233
269
  };
234
270
  // Execute with timeout
235
- const result = await withTimeout(readOperation(), FILE_READ_TIMEOUT, `Read file operation for ${filePath}`, returnMetadata ?
236
- { content: `Operation timed out after ${FILE_READ_TIMEOUT / 1000} seconds`, mimeType: 'text/plain', isImage: false } :
237
- `Operation timed out after ${FILE_READ_TIMEOUT / 1000} seconds`);
271
+ const result = await withTimeout(readOperation(), FILE_READ_TIMEOUT, `Read file operation for ${filePath}`, null);
272
+ if (result == null) {
273
+ // Handles the impossible case where withTimeout resolves to null instead of throwing
274
+ throw new Error('Failed to read the file');
275
+ }
238
276
  return result;
239
277
  }
240
278
  /**
@@ -244,20 +282,24 @@ export async function readFileFromDisk(filePath, returnMetadata) {
244
282
  * @param isUrl Whether the path is a URL
245
283
  * @returns File content or file result with metadata
246
284
  */
247
- export async function readFile(filePath, returnMetadata, isUrl) {
285
+ export async function readFile(filePath, isUrl) {
248
286
  return isUrl
249
- ? readFileFromUrl(filePath, returnMetadata)
250
- : readFileFromDisk(filePath, returnMetadata);
287
+ ? readFileFromUrl(filePath)
288
+ : readFileFromDisk(filePath);
251
289
  }
252
290
  export async function writeFile(filePath, content) {
253
291
  const validPath = await validatePath(filePath);
292
+ // Get file extension for telemetry
293
+ const fileExtension = path.extname(validPath).toLowerCase();
294
+ // Capture file extension in telemetry without capturing the file path
295
+ capture('server_write_file', { fileExtension: fileExtension });
254
296
  await fs.writeFile(validPath, content, "utf-8");
255
297
  }
256
298
  export async function readMultipleFiles(paths) {
257
299
  return Promise.all(paths.map(async (filePath) => {
258
300
  try {
259
301
  const validPath = await validatePath(filePath);
260
- const fileResult = await readFile(validPath, true);
302
+ const fileResult = await readFile(validPath);
261
303
  return {
262
304
  path: filePath,
263
305
  content: typeof fileResult === 'string' ? fileResult : fileResult.content,
@@ -291,7 +333,19 @@ export async function moveFile(sourcePath, destinationPath) {
291
333
  export async function searchFiles(rootPath, pattern) {
292
334
  const results = [];
293
335
  async function search(currentPath) {
294
- const entries = await fs.readdir(currentPath, { withFileTypes: true });
336
+ let entries;
337
+ try {
338
+ entries = await fs.readdir(currentPath, { withFileTypes: true });
339
+ }
340
+ catch (error) {
341
+ // Only sanitize for telemetry, not for the returned error
342
+ capture('server_search_files_error', {
343
+ errorType: error instanceof Error ? error.name : 'Unknown',
344
+ errorMessage: 'Error reading directory',
345
+ isReadDirError: true
346
+ });
347
+ return; // Skip this directory on error
348
+ }
295
349
  for (const entry of entries) {
296
350
  const fullPath = path.join(currentPath, entry.name);
297
351
  try {
@@ -308,10 +362,27 @@ export async function searchFiles(rootPath, pattern) {
308
362
  }
309
363
  }
310
364
  }
311
- // if path not exist, it will throw an error
312
- const validPath = await validatePath(rootPath);
313
- await search(validPath);
314
- return results;
365
+ try {
366
+ // Validate root path before starting search
367
+ const validPath = await validatePath(rootPath);
368
+ await search(validPath);
369
+ // Log only the count of found files, not their paths
370
+ capture('server_search_files_complete', {
371
+ resultsCount: results.length,
372
+ patternLength: pattern.length
373
+ });
374
+ return results;
375
+ }
376
+ catch (error) {
377
+ // For telemetry only - sanitize error info
378
+ capture('server_search_files_error', {
379
+ errorType: error instanceof Error ? error.name : 'Unknown',
380
+ errorMessage: 'Error with root path',
381
+ isRootPathError: true
382
+ });
383
+ // Re-throw the original error for the caller
384
+ throw error;
385
+ }
315
386
  }
316
387
  export async function getFileInfo(filePath) {
317
388
  const validPath = await validatePath(filePath);
@@ -326,6 +397,5 @@ export async function getFileInfo(filePath) {
326
397
  permissions: stats.mode.toString(8).slice(-3),
327
398
  };
328
399
  }
329
- export function listAllowedDirectories() {
330
- return ["/ (All paths are currently allowed)"];
331
- }
400
+ // This function has been replaced with configManager.getConfig()
401
+ // Use get_config tool to retrieve allowedDirectories
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Recursively finds the closest match to a query string within text using fuzzy matching
3
+ * @param text The text to search within
4
+ * @param query The query string to find
5
+ * @param start Start index in the text (default: 0)
6
+ * @param end End index in the text (default: text.length)
7
+ * @param parentDistance Best distance found so far (default: Infinity)
8
+ * @returns Object with start and end indices, matched value, and Levenshtein distance
9
+ */
10
+ export declare function recursiveFuzzyIndexOf(text: string, query: string, start?: number, end?: number | null, parentDistance?: number, depth?: number): {
11
+ start: number;
12
+ end: number;
13
+ value: string;
14
+ distance: number;
15
+ };
16
+ /**
17
+ * Calculates the similarity ratio between two strings
18
+ * @param a First string
19
+ * @param b Second string
20
+ * @returns Similarity ratio (0-1)
21
+ */
22
+ export declare function getSimilarityRatio(a: string, b: string): number;