@wonderwhy-er/desktop-commander 0.1.33 → 0.1.35

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 (50) hide show
  1. package/README.md +105 -36
  2. package/dist/command-manager.d.ts +1 -7
  3. package/dist/command-manager.js +31 -50
  4. package/dist/config-manager.d.ts +27 -16
  5. package/dist/config-manager.js +109 -191
  6. package/dist/config.js +8 -4
  7. package/dist/error-handlers.d.ts +7 -0
  8. package/dist/error-handlers.js +15 -0
  9. package/dist/handlers/command-handlers.d.ts +4 -18
  10. package/dist/handlers/command-handlers.js +15 -3
  11. package/dist/handlers/edit-search-handlers.d.ts +3 -12
  12. package/dist/handlers/edit-search-handlers.js +10 -7
  13. package/dist/handlers/filesystem-handlers.d.ts +9 -66
  14. package/dist/handlers/filesystem-handlers.js +106 -65
  15. package/dist/handlers/index.d.ts +0 -1
  16. package/dist/handlers/index.js +0 -1
  17. package/dist/handlers/process-handlers.d.ts +3 -12
  18. package/dist/handlers/terminal-handlers.d.ts +5 -24
  19. package/dist/index.js +18 -3
  20. package/dist/sandbox/index.d.ts +9 -0
  21. package/dist/sandbox/index.js +50 -0
  22. package/dist/sandbox/mac-sandbox.d.ts +19 -0
  23. package/dist/sandbox/mac-sandbox.js +174 -0
  24. package/dist/server.js +156 -176
  25. package/dist/setup-claude-server.js +98 -49
  26. package/dist/terminal-manager.d.ts +1 -1
  27. package/dist/terminal-manager.js +26 -4
  28. package/dist/tools/config.d.ts +0 -58
  29. package/dist/tools/config.js +44 -107
  30. package/dist/tools/debug-path.d.ts +1 -0
  31. package/dist/tools/debug-path.js +44 -0
  32. package/dist/tools/edit.d.ts +3 -1
  33. package/dist/tools/edit.js +19 -7
  34. package/dist/tools/execute.d.ts +4 -18
  35. package/dist/tools/execute.js +27 -8
  36. package/dist/tools/filesystem-fixed.d.ts +22 -0
  37. package/dist/tools/filesystem-fixed.js +176 -0
  38. package/dist/tools/filesystem.d.ts +4 -6
  39. package/dist/tools/filesystem.js +106 -75
  40. package/dist/tools/process.d.ts +3 -12
  41. package/dist/tools/process.js +12 -3
  42. package/dist/tools/schemas.d.ts +15 -14
  43. package/dist/tools/schemas.js +10 -6
  44. package/dist/tools/search.js +3 -3
  45. package/dist/types.d.ts +12 -0
  46. package/dist/utils.d.ts +5 -0
  47. package/dist/utils.js +92 -32
  48. package/dist/version.d.ts +1 -1
  49. package/dist/version.js +1 -1
  50. package/package.json +1 -2
@@ -1,29 +1,3 @@
1
- import { z } from 'zod';
2
- export declare const GetConfigArgsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
3
- export declare const GetConfigValueArgsSchema: z.ZodObject<{
4
- key: z.ZodString;
5
- }, "strip", z.ZodTypeAny, {
6
- key: string;
7
- }, {
8
- key: string;
9
- }>;
10
- export declare const SetConfigValueArgsSchema: z.ZodObject<{
11
- key: z.ZodString;
12
- value: z.ZodAny;
13
- }, "strip", z.ZodTypeAny, {
14
- key: string;
15
- value?: any;
16
- }, {
17
- key: string;
18
- value?: any;
19
- }>;
20
- export declare const UpdateConfigArgsSchema: z.ZodObject<{
21
- config: z.ZodRecord<z.ZodString, z.ZodAny>;
22
- }, "strip", z.ZodTypeAny, {
23
- config: Record<string, any>;
24
- }, {
25
- config: Record<string, any>;
26
- }>;
27
1
  /**
28
2
  * Get the entire config
29
3
  */
@@ -33,22 +7,6 @@ export declare function getConfig(): Promise<{
33
7
  text: string;
34
8
  }[];
35
9
  }>;
36
- /**
37
- * Get a specific config value
38
- */
39
- export declare function getConfigValue(args: unknown): Promise<{
40
- content: {
41
- type: string;
42
- text: string;
43
- }[];
44
- isError: boolean;
45
- } | {
46
- content: {
47
- type: string;
48
- text: string;
49
- }[];
50
- isError?: undefined;
51
- }>;
52
10
  /**
53
11
  * Set a specific config value
54
12
  */
@@ -65,19 +23,3 @@ export declare function setConfigValue(args: unknown): Promise<{
65
23
  }[];
66
24
  isError?: undefined;
67
25
  }>;
68
- /**
69
- * Update multiple config values at once
70
- */
71
- export declare function updateConfig(args: unknown): Promise<{
72
- content: {
73
- type: string;
74
- text: string;
75
- }[];
76
- isError: boolean;
77
- } | {
78
- content: {
79
- type: string;
80
- text: string;
81
- }[];
82
- isError?: undefined;
83
- }>;
@@ -1,17 +1,5 @@
1
- import { z } from 'zod';
2
1
  import { configManager } from '../config-manager.js';
3
- // Schemas for config operations
4
- export const GetConfigArgsSchema = z.object({});
5
- export const GetConfigValueArgsSchema = z.object({
6
- key: z.string(),
7
- });
8
- export const SetConfigValueArgsSchema = z.object({
9
- key: z.string(),
10
- value: z.any(),
11
- });
12
- export const UpdateConfigArgsSchema = z.object({
13
- config: z.record(z.any()),
14
- });
2
+ import { SetConfigValueArgsSchema } from './schemas.js';
15
3
  /**
16
4
  * Get the entire config
17
5
  */
@@ -39,46 +27,6 @@ export async function getConfig() {
39
27
  };
40
28
  }
41
29
  }
42
- /**
43
- * Get a specific config value
44
- */
45
- export async function getConfigValue(args) {
46
- console.error(`getConfigValue called with args: ${JSON.stringify(args)}`);
47
- try {
48
- const parsed = GetConfigValueArgsSchema.safeParse(args);
49
- if (!parsed.success) {
50
- console.error(`Invalid arguments for get_config_value: ${parsed.error}`);
51
- return {
52
- content: [{
53
- type: "text",
54
- text: `Invalid arguments: ${parsed.error}`
55
- }],
56
- isError: true
57
- };
58
- }
59
- const value = await configManager.getValue(parsed.data.key);
60
- console.error(`getConfigValue result for key ${parsed.data.key}: ${JSON.stringify(value)}`);
61
- return {
62
- content: [{
63
- type: "text",
64
- text: value !== undefined
65
- ? `Value for ${parsed.data.key}: ${JSON.stringify(value, null, 2)}`
66
- : `No value found for key: ${parsed.data.key}`
67
- }],
68
- };
69
- }
70
- catch (error) {
71
- console.error(`Error in getConfigValue: ${error instanceof Error ? error.message : String(error)}`);
72
- console.error(error instanceof Error && error.stack ? error.stack : 'No stack trace available');
73
- return {
74
- content: [{
75
- type: "text",
76
- text: `Error retrieving value: ${error instanceof Error ? error.message : String(error)}`
77
- }],
78
- isError: true
79
- };
80
- }
81
- }
82
30
  /**
83
31
  * Set a specific config value
84
32
  */
@@ -97,12 +45,52 @@ export async function setConfigValue(args) {
97
45
  };
98
46
  }
99
47
  try {
100
- await configManager.setValue(parsed.data.key, parsed.data.value);
101
- console.error(`setConfigValue: Successfully set ${parsed.data.key} to ${JSON.stringify(parsed.data.value)}`);
48
+ // Parse string values that should be arrays or objects
49
+ let valueToStore = parsed.data.value;
50
+ // If the value is a string that looks like an array or object, try to parse it
51
+ if (typeof valueToStore === 'string' &&
52
+ (valueToStore.startsWith('[') || valueToStore.startsWith('{'))) {
53
+ try {
54
+ valueToStore = JSON.parse(valueToStore);
55
+ console.error(`Parsed string value to object/array: ${JSON.stringify(valueToStore)}`);
56
+ }
57
+ catch (parseError) {
58
+ console.error(`Failed to parse string as JSON, using as-is: ${parseError}`);
59
+ }
60
+ }
61
+ // Special handling for known array configuration keys
62
+ if ((parsed.data.key === 'allowedDirectories' || parsed.data.key === 'blockedCommands') &&
63
+ !Array.isArray(valueToStore)) {
64
+ if (typeof valueToStore === 'string') {
65
+ try {
66
+ valueToStore = JSON.parse(valueToStore);
67
+ }
68
+ catch (parseError) {
69
+ console.error(`Failed to parse string as array for ${parsed.data.key}: ${parseError}`);
70
+ // If parsing failed and it's a single value, convert to an array with one item
71
+ if (!valueToStore.includes('[')) {
72
+ valueToStore = [valueToStore];
73
+ }
74
+ }
75
+ }
76
+ else {
77
+ // If not a string or array, convert to an array with one item
78
+ valueToStore = [valueToStore];
79
+ }
80
+ // Ensure the value is an array after all our conversions
81
+ if (!Array.isArray(valueToStore)) {
82
+ console.error(`Value for ${parsed.data.key} is still not an array, converting to array`);
83
+ valueToStore = [String(valueToStore)];
84
+ }
85
+ }
86
+ await configManager.setValue(parsed.data.key, valueToStore);
87
+ // Get the updated configuration to show the user
88
+ const updatedConfig = await configManager.getConfig();
89
+ console.error(`setConfigValue: Successfully set ${parsed.data.key} to ${JSON.stringify(valueToStore)}`);
102
90
  return {
103
91
  content: [{
104
92
  type: "text",
105
- text: `Successfully set ${parsed.data.key} to ${JSON.stringify(parsed.data.value, null, 2)}`
93
+ text: `Successfully set ${parsed.data.key} to ${JSON.stringify(valueToStore, null, 2)}\n\nUpdated configuration:\n${JSON.stringify(updatedConfig, null, 2)}`
106
94
  }],
107
95
  };
108
96
  }
@@ -130,54 +118,3 @@ export async function setConfigValue(args) {
130
118
  };
131
119
  }
132
120
  }
133
- /**
134
- * Update multiple config values at once
135
- */
136
- export async function updateConfig(args) {
137
- console.error(`updateConfig called with args: ${JSON.stringify(args)}`);
138
- try {
139
- const parsed = UpdateConfigArgsSchema.safeParse(args);
140
- if (!parsed.success) {
141
- console.error(`Invalid arguments for update_config: ${parsed.error}`);
142
- return {
143
- content: [{
144
- type: "text",
145
- text: `Invalid arguments: ${parsed.error}`
146
- }],
147
- isError: true
148
- };
149
- }
150
- try {
151
- const updatedConfig = await configManager.updateConfig(parsed.data.config);
152
- console.error(`updateConfig result: ${JSON.stringify(updatedConfig, null, 2)}`);
153
- return {
154
- content: [{
155
- type: "text",
156
- text: `Configuration updated successfully.\nNew configuration:\n${JSON.stringify(updatedConfig, null, 2)}`
157
- }],
158
- };
159
- }
160
- catch (saveError) {
161
- console.error(`Error saving updated config: ${saveError.message}`);
162
- // Return useful response instead of crashing
163
- return {
164
- content: [{
165
- type: "text",
166
- text: `Configuration updated in memory but couldn't be saved to disk: ${saveError.message}`
167
- }],
168
- isError: true
169
- };
170
- }
171
- }
172
- catch (error) {
173
- console.error(`Error in updateConfig: ${error instanceof Error ? error.message : String(error)}`);
174
- console.error(error instanceof Error && error.stack ? error.stack : 'No stack trace available');
175
- return {
176
- content: [{
177
- type: "text",
178
- text: `Error updating configuration: ${error instanceof Error ? error.message : String(error)}`
179
- }],
180
- isError: true
181
- };
182
- }
183
- }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,44 @@
1
+ // This is a debug script to help understand how ~ expansion works
2
+ import os from 'os';
3
+ import path from 'path';
4
+ function expandHome(filepath) {
5
+ console.log(`Expanding ${filepath}`);
6
+ // Handle both '~' alone or '~/' path prefix
7
+ if (filepath === '~') {
8
+ const home = os.homedir();
9
+ console.log(`~ expanded to ${home}`);
10
+ return home;
11
+ }
12
+ else if (filepath.startsWith('~/')) {
13
+ const joinedPath = path.join(os.homedir(), filepath.slice(2));
14
+ console.log(`~/ expanded to ${joinedPath}`);
15
+ return joinedPath;
16
+ }
17
+ return filepath;
18
+ }
19
+ console.log(`Home directory: ${os.homedir()}`);
20
+ console.log(`Current working directory: ${process.cwd()}`);
21
+ console.log(`Expanding ~: ${expandHome('~')}`);
22
+ console.log(`Expanding ~/Documents: ${expandHome('~/Documents')}`);
23
+ console.log(`Expanding /tmp: ${expandHome('/tmp')}`);
24
+ // Create lowercase normalized paths for comparison
25
+ function normalizePath(p) {
26
+ return path.normalize(p).toLowerCase();
27
+ }
28
+ const allowedDir = '~';
29
+ const expandedAllowedDir = expandHome(allowedDir);
30
+ const normalizedAllowedDir = normalizePath(expandedAllowedDir);
31
+ console.log(`Allowed dir: ${allowedDir}`);
32
+ console.log(`Expanded allowed dir: ${expandedAllowedDir}`);
33
+ console.log(`Normalized allowed dir: ${normalizedAllowedDir}`);
34
+ // Example path to check
35
+ const pathToCheck = '~';
36
+ const expandedPathToCheck = expandHome(pathToCheck);
37
+ const normalizedPathToCheck = normalizePath(expandedPathToCheck);
38
+ console.log(`Path to check: ${pathToCheck}`);
39
+ console.log(`Expanded path to check: ${expandedPathToCheck}`);
40
+ console.log(`Normalized path to check: ${normalizedPathToCheck}`);
41
+ // Check if path is allowed
42
+ const isAllowed = normalizedPathToCheck === normalizedAllowedDir ||
43
+ normalizedPathToCheck.startsWith(normalizedAllowedDir + path.sep);
44
+ console.log(`Is path allowed: ${isAllowed}`);
@@ -1,10 +1,12 @@
1
+ import { ServerResult } from '../types.js';
1
2
  interface SearchReplace {
2
3
  search: string;
3
4
  replace: string;
4
5
  }
5
- export declare function performSearchReplace(filePath: string, block: SearchReplace): Promise<void>;
6
+ export declare function performSearchReplace(filePath: string, block: SearchReplace): Promise<ServerResult>;
6
7
  export declare function parseEditBlock(blockContent: string): Promise<{
7
8
  filePath: string;
8
9
  searchReplace: SearchReplace;
10
+ error?: string;
9
11
  }>;
10
12
  export {};
@@ -1,19 +1,27 @@
1
1
  import { readFile, writeFile } from './filesystem.js';
2
2
  export async function performSearchReplace(filePath, block) {
3
3
  // Read file as plain string (don't pass true to get just the string)
4
- const content = await readFile(filePath);
4
+ const { content } = await readFile(filePath);
5
5
  // Make sure content is a string
6
- const contentStr = typeof content === 'string' ? content : content.content;
6
+ if (typeof content !== 'string') {
7
+ throw new Error('Wrong content for file ' + filePath);
8
+ }
9
+ ;
7
10
  // Find first occurrence
8
- const searchIndex = contentStr.indexOf(block.search);
11
+ const searchIndex = content.indexOf(block.search);
9
12
  if (searchIndex === -1) {
10
- throw new Error(`Search content not found in ${filePath}`);
13
+ return {
14
+ content: [{ type: "text", text: `Search content not found in ${filePath}.` }],
15
+ };
11
16
  }
12
17
  // Replace content
13
- const newContent = contentStr.substring(0, searchIndex) +
18
+ const newContent = content.substring(0, searchIndex) +
14
19
  block.replace +
15
- contentStr.substring(searchIndex + block.search.length);
20
+ content.substring(searchIndex + block.search.length);
16
21
  await writeFile(filePath, newContent);
22
+ return {
23
+ content: [{ type: "text", text: `Successfully applied edit to ${filePath}` }],
24
+ };
17
25
  }
18
26
  export async function parseEditBlock(blockContent) {
19
27
  const lines = blockContent.split('\n');
@@ -24,7 +32,11 @@ export async function parseEditBlock(blockContent) {
24
32
  const divider = lines.indexOf('=======');
25
33
  const replaceEnd = lines.indexOf('>>>>>>> REPLACE');
26
34
  if (searchStart === -1 || divider === -1 || replaceEnd === -1) {
27
- throw new Error('Invalid edit block format - missing markers');
35
+ return {
36
+ filePath: '',
37
+ searchReplace: { search: '', replace: '' },
38
+ error: 'Invalid edit block format - missing markers'
39
+ };
28
40
  }
29
41
  // Extract search and replace content
30
42
  const search = lines.slice(searchStart + 1, divider).join('\n');
@@ -1,21 +1,7 @@
1
- export declare function executeCommand(args: unknown): Promise<{
2
- content: {
3
- type: string;
4
- text: string;
5
- }[];
6
- }>;
7
- export declare function readOutput(args: unknown): Promise<{
8
- content: {
9
- type: string;
10
- text: string;
11
- }[];
12
- }>;
13
- export declare function forceTerminate(args: unknown): Promise<{
14
- content: {
15
- type: string;
16
- text: string;
17
- }[];
18
- }>;
1
+ import { ServerResult } from '../types.js';
2
+ export declare function executeCommand(args: unknown): Promise<ServerResult>;
3
+ export declare function readOutput(args: unknown): Promise<ServerResult>;
4
+ export declare function forceTerminate(args: unknown): Promise<ServerResult>;
19
5
  export declare function listSessions(): Promise<{
20
6
  content: {
21
7
  type: string;
@@ -6,7 +6,10 @@ export async function executeCommand(args) {
6
6
  const parsed = ExecuteCommandArgsSchema.safeParse(args);
7
7
  if (!parsed.success) {
8
8
  capture('server_execute_command_failed');
9
- throw new Error(`Invalid arguments for execute_command: ${parsed.error}`);
9
+ return {
10
+ content: [{ type: "text", text: `Error: Invalid arguments for execute_command: ${parsed.error}` }],
11
+ isError: true,
12
+ };
10
13
  }
11
14
  try {
12
15
  // Extract all commands for analytics while ensuring execution continues even if parsing fails
@@ -21,13 +24,23 @@ export async function executeCommand(args) {
21
24
  capture('server_execute_command', {
22
25
  command: commandManager.getBaseCommand(parsed.data.command)
23
26
  });
24
- // Log the error but continue execution
25
- console.error('Error during command extraction:', error);
26
27
  }
27
- if (!commandManager.validateCommand(parsed.data.command)) {
28
- throw new Error(`Command not allowed: ${parsed.data.command}`);
28
+ // Command validation is now async
29
+ const isAllowed = await commandManager.validateCommand(parsed.data.command);
30
+ if (!isAllowed) {
31
+ return {
32
+ content: [{ type: "text", text: `Error: Command not allowed: ${parsed.data.command}` }],
33
+ isError: true,
34
+ };
35
+ }
36
+ const result = await terminalManager.executeCommand(parsed.data.command, parsed.data.timeout_ms, parsed.data.shell);
37
+ // Check for error condition (pid = -1)
38
+ if (result.pid === -1) {
39
+ return {
40
+ content: [{ type: "text", text: result.output }],
41
+ isError: true,
42
+ };
29
43
  }
30
- const result = await terminalManager.executeCommand(parsed.data.command, parsed.data.timeout_ms);
31
44
  return {
32
45
  content: [{
33
46
  type: "text",
@@ -38,7 +51,10 @@ export async function executeCommand(args) {
38
51
  export async function readOutput(args) {
39
52
  const parsed = ReadOutputArgsSchema.safeParse(args);
40
53
  if (!parsed.success) {
41
- throw new Error(`Invalid arguments for read_output: ${parsed.error}`);
54
+ return {
55
+ content: [{ type: "text", text: `Error: Invalid arguments for read_output: ${parsed.error}` }],
56
+ isError: true,
57
+ };
42
58
  }
43
59
  const output = terminalManager.getNewOutput(parsed.data.pid);
44
60
  return {
@@ -53,7 +69,10 @@ export async function readOutput(args) {
53
69
  export async function forceTerminate(args) {
54
70
  const parsed = ForceTerminateArgsSchema.safeParse(args);
55
71
  if (!parsed.success) {
56
- throw new Error(`Invalid arguments for force_terminate: ${parsed.error}`);
72
+ return {
73
+ content: [{ type: "text", text: `Error: Invalid arguments for force_terminate: ${parsed.error}` }],
74
+ isError: true,
75
+ };
57
76
  }
58
77
  const success = terminalManager.forceTerminate(parsed.data.pid);
59
78
  return {
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Read a file from either the local filesystem or a URL
3
+ * @param filePath Path to the file or URL
4
+ * @param returnMetadata Whether to return metadata with the content
5
+ * @param isUrl Whether the path is a URL
6
+ * @returns File content or file result with metadata
7
+ */
8
+ export declare function readFile(filePath: string, returnMetadata?: boolean, isUrl?: boolean): Promise<string | FileResult>;
9
+ export declare function writeFile(filePath: string, content: string): Promise<void>;
10
+ export interface MultiFileResult {
11
+ path: string;
12
+ content?: string;
13
+ mimeType?: string;
14
+ isImage?: boolean;
15
+ error?: string;
16
+ }
17
+ export declare function readMultipleFiles(paths: string[]): Promise<MultiFileResult[]>;
18
+ export declare function createDirectory(dirPath: string): Promise<void>;
19
+ export declare function listDirectory(dirPath: string): Promise<string[]>;
20
+ export declare function moveFile(sourcePath: string, destinationPath: string): Promise<void>;
21
+ export declare function searchFiles(rootPath: string, pattern: string): Promise<string[]>;
22
+ export declare function getFileInfo(filePath: string): Promise<Record<string, any>>;
@@ -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