@wonderwhy-er/desktop-commander 0.1.29 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/dist/server.js +78 -10
- package/dist/setup-claude-server.js +11 -1
- package/dist/tools/edit.js +6 -3
- package/dist/tools/filesystem.d.ts +23 -2
- package/dist/tools/filesystem.js +90 -46
- package/dist/tools/mime-types.d.ts +2 -0
- package/dist/tools/mime-types.js +21 -0
- package/dist/utils.js +10 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -108,6 +108,12 @@ The setup command will:
|
|
|
108
108
|
- Configure Claude's desktop app
|
|
109
109
|
- Add MCP servers to Claude's config if needed
|
|
110
110
|
|
|
111
|
+
### Updating Desktop Commander
|
|
112
|
+
|
|
113
|
+
When installed through npx (Option 1) or Smithery (Option 2), Desktop Commander will automatically update to the latest version whenever you restart Claude. No manual update process is needed.
|
|
114
|
+
|
|
115
|
+
For manual installations, you can update by running the setup command again.
|
|
116
|
+
|
|
111
117
|
## Usage
|
|
112
118
|
|
|
113
119
|
The server provides these tool categories:
|
|
@@ -122,7 +128,7 @@ The server provides these tool categories:
|
|
|
122
128
|
- `block_command`/`unblock_command`: Manage command blacklist
|
|
123
129
|
|
|
124
130
|
### Filesystem Tools
|
|
125
|
-
- `read_file`/`write_file`: File operations
|
|
131
|
+
- `read_file`/`write_file`: File operations (supports viewing PNG, JPEG, GIF, and WebP images directly in Claude)
|
|
126
132
|
- `create_directory`/`list_directory`: Directory management
|
|
127
133
|
- `move_file`: Move/rename files
|
|
128
134
|
- `search_files`: Pattern-based file search
|
|
@@ -252,7 +258,7 @@ Join our [Discord server](https://discord.gg/kQ27sNnZr7) to get help, share feed
|
|
|
252
258
|
https://www.youtube.com/watch?v=ly3bed99Dy8&lc=UgztdHvDMqTb9jiqnf54AaABAg](https://www.youtube.com/watch?v=ly3bed99Dy8&lc=UgztdHvDMqTb9jiqnf54AaABAg
|
|
253
259
|
)
|
|
254
260
|
|
|
255
|
-
[
|
|
257
263
|
https://www.youtube.com/watch?v=ly3bed99Dy8&lc=UgyQFTmYLJ4VBwIlmql4AaABAg](https://www.youtube.com/watch?v=ly3bed99Dy8&lc=UgyQFTmYLJ4VBwIlmql4AaABAg)
|
|
258
264
|
|
|
@@ -293,6 +299,9 @@ Unlike IDE-focused tools, Claude Desktop Commander provides a solution-centric a
|
|
|
293
299
|
### Do I need to pay for API credits?
|
|
294
300
|
No. This tool works with Claude Desktop's standard Pro subscription ($20/month), not with API calls, so you won't incur additional costs beyond the subscription fee.
|
|
295
301
|
|
|
302
|
+
### Does Desktop Commander automatically update?
|
|
303
|
+
Yes, when installed through npx or Smithery, Desktop Commander automatically updates to the latest version when you restart Claude. No manual update process is needed.
|
|
304
|
+
|
|
296
305
|
### What are the most common use cases?
|
|
297
306
|
- Exploring and understanding complex codebases
|
|
298
307
|
- Generating diagrams and documentation
|
package/dist/server.js
CHANGED
|
@@ -97,14 +97,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
97
97
|
{
|
|
98
98
|
name: "read_file",
|
|
99
99
|
description: "Read the complete contents of a file from the file system. " +
|
|
100
|
-
"
|
|
101
|
-
"
|
|
100
|
+
"Handles text files normally and image files are returned as viewable images. " +
|
|
101
|
+
"Recognized image types: PNG, JPEG, GIF, WebP. " +
|
|
102
|
+
"Only works within allowed directories.",
|
|
102
103
|
inputSchema: zodToJsonSchema(ReadFileArgsSchema),
|
|
103
104
|
},
|
|
104
105
|
{
|
|
105
106
|
name: "read_multiple_files",
|
|
106
107
|
description: "Read the contents of multiple files simultaneously. " +
|
|
107
108
|
"Each file's content is returned with its path as a reference. " +
|
|
109
|
+
"Handles text files normally and renders images as viewable content. " +
|
|
110
|
+
"Recognized image types: PNG, JPEG, GIF, WebP. " +
|
|
108
111
|
"Failed reads for individual files won't stop the entire operation. " +
|
|
109
112
|
"Only works within allowed directories.",
|
|
110
113
|
inputSchema: zodToJsonSchema(ReadMultipleFilesArgsSchema),
|
|
@@ -244,6 +247,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
244
247
|
}
|
|
245
248
|
catch (error) {
|
|
246
249
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
250
|
+
capture('server_' + name + "_error");
|
|
247
251
|
return {
|
|
248
252
|
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
249
253
|
};
|
|
@@ -253,12 +257,34 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
253
257
|
capture('server_read_file');
|
|
254
258
|
try {
|
|
255
259
|
const parsed = ReadFileArgsSchema.parse(args);
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
+
// Explicitly cast the result to FileResult since we're passing true
|
|
261
|
+
const fileResult = await readFile(parsed.path, true);
|
|
262
|
+
if (fileResult.isImage) {
|
|
263
|
+
// For image files, return as an image content type
|
|
264
|
+
return {
|
|
265
|
+
content: [
|
|
266
|
+
{
|
|
267
|
+
type: "text",
|
|
268
|
+
text: `Image file: ${parsed.path} (${fileResult.mimeType})\n`
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
type: "image",
|
|
272
|
+
data: fileResult.content,
|
|
273
|
+
mimeType: fileResult.mimeType
|
|
274
|
+
}
|
|
275
|
+
],
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
// For all other files, return as text
|
|
280
|
+
capture('server_' + "read_file_error");
|
|
281
|
+
return {
|
|
282
|
+
content: [{ type: "text", text: fileResult.content }],
|
|
283
|
+
};
|
|
284
|
+
}
|
|
260
285
|
}
|
|
261
286
|
catch (error) {
|
|
287
|
+
capture('server_' + name + "_error");
|
|
262
288
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
263
289
|
return {
|
|
264
290
|
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
@@ -269,12 +295,47 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
269
295
|
capture('server_read_multiple_files');
|
|
270
296
|
try {
|
|
271
297
|
const parsed = ReadMultipleFilesArgsSchema.parse(args);
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
298
|
+
const fileResults = await readMultipleFiles(parsed.paths);
|
|
299
|
+
// Create a text summary of all files
|
|
300
|
+
const textSummary = fileResults.map(result => {
|
|
301
|
+
if (result.error) {
|
|
302
|
+
return `${result.path}: Error - ${result.error}`;
|
|
303
|
+
}
|
|
304
|
+
else if (result.mimeType) {
|
|
305
|
+
return `${result.path}: ${result.mimeType} ${result.isImage ? '(image)' : '(text)'}`;
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
return `${result.path}: Unknown type`;
|
|
309
|
+
}
|
|
310
|
+
}).join("\n");
|
|
311
|
+
// Create content items for each file
|
|
312
|
+
const contentItems = [];
|
|
313
|
+
// Add the text summary
|
|
314
|
+
contentItems.push({ type: "text", text: textSummary });
|
|
315
|
+
// Add each file content
|
|
316
|
+
for (const result of fileResults) {
|
|
317
|
+
if (!result.error && result.content !== undefined) {
|
|
318
|
+
if (result.isImage && result.mimeType) {
|
|
319
|
+
// For image files, add an image content item
|
|
320
|
+
contentItems.push({
|
|
321
|
+
type: "image",
|
|
322
|
+
data: result.content,
|
|
323
|
+
mimeType: result.mimeType
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
// For text files, add a text summary
|
|
328
|
+
contentItems.push({
|
|
329
|
+
type: "text",
|
|
330
|
+
text: `\n--- ${result.path} contents: ---\n${result.content}`
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return { content: contentItems };
|
|
276
336
|
}
|
|
277
337
|
catch (error) {
|
|
338
|
+
capture('server_' + name + "_error");
|
|
278
339
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
279
340
|
return {
|
|
280
341
|
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
@@ -291,6 +352,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
291
352
|
};
|
|
292
353
|
}
|
|
293
354
|
catch (error) {
|
|
355
|
+
capture('server_' + name + "_error");
|
|
294
356
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
295
357
|
return {
|
|
296
358
|
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
@@ -307,6 +369,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
307
369
|
};
|
|
308
370
|
}
|
|
309
371
|
catch (error) {
|
|
372
|
+
capture('server_' + name + "_error");
|
|
310
373
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
311
374
|
return {
|
|
312
375
|
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
@@ -323,6 +386,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
323
386
|
};
|
|
324
387
|
}
|
|
325
388
|
catch (error) {
|
|
389
|
+
capture('server_' + name + "_error");
|
|
326
390
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
327
391
|
return {
|
|
328
392
|
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
@@ -339,6 +403,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
339
403
|
};
|
|
340
404
|
}
|
|
341
405
|
catch (error) {
|
|
406
|
+
capture('server_' + name + "_error");
|
|
342
407
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
343
408
|
return {
|
|
344
409
|
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
@@ -355,6 +420,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
355
420
|
};
|
|
356
421
|
}
|
|
357
422
|
catch (error) {
|
|
423
|
+
capture('server_' + name + "_error");
|
|
358
424
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
359
425
|
return {
|
|
360
426
|
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
@@ -382,6 +448,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
382
448
|
}
|
|
383
449
|
}
|
|
384
450
|
catch (error) {
|
|
451
|
+
capture('server_' + name + "_error");
|
|
385
452
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
386
453
|
return {
|
|
387
454
|
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
@@ -416,6 +483,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
416
483
|
};
|
|
417
484
|
}
|
|
418
485
|
catch (error) {
|
|
486
|
+
capture('server_' + name + "_error");
|
|
419
487
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
420
488
|
return {
|
|
421
489
|
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
@@ -46,6 +46,15 @@ async function getNpmVersion() {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
const getVersion = async () => {
|
|
50
|
+
try {
|
|
51
|
+
const packageJson = await import('./package.json', { assert: { type: 'json' } });
|
|
52
|
+
return packageJson.default.version;
|
|
53
|
+
} catch {
|
|
54
|
+
return 'unknown'
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
49
58
|
// Function to detect shell environment
|
|
50
59
|
function detectShell() {
|
|
51
60
|
// Check for Windows shells
|
|
@@ -115,7 +124,7 @@ async function getTrackingProperties(additionalProps = {}) {
|
|
|
115
124
|
}
|
|
116
125
|
|
|
117
126
|
const context = getExecutionContext();
|
|
118
|
-
|
|
127
|
+
const version = await getVersion();
|
|
119
128
|
return {
|
|
120
129
|
platform: platform(),
|
|
121
130
|
nodeVersion: nodeVersion,
|
|
@@ -123,6 +132,7 @@ async function getTrackingProperties(additionalProps = {}) {
|
|
|
123
132
|
executionContext: context.runMethod,
|
|
124
133
|
isCI: context.isCI,
|
|
125
134
|
shell: context.shell,
|
|
135
|
+
DCVersion: version,
|
|
126
136
|
timestamp: new Date().toISOString(),
|
|
127
137
|
...additionalProps
|
|
128
138
|
};
|
package/dist/tools/edit.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { readFile, writeFile } from './filesystem.js';
|
|
2
2
|
export async function performSearchReplace(filePath, block) {
|
|
3
|
+
// Read file as plain string (don't pass true to get just the string)
|
|
3
4
|
const content = await readFile(filePath);
|
|
5
|
+
// Make sure content is a string
|
|
6
|
+
const contentStr = typeof content === 'string' ? content : content.content;
|
|
4
7
|
// Find first occurrence
|
|
5
|
-
const searchIndex =
|
|
8
|
+
const searchIndex = contentStr.indexOf(block.search);
|
|
6
9
|
if (searchIndex === -1) {
|
|
7
10
|
throw new Error(`Search content not found in ${filePath}`);
|
|
8
11
|
}
|
|
9
12
|
// Replace content
|
|
10
|
-
const newContent =
|
|
13
|
+
const newContent = contentStr.substring(0, searchIndex) +
|
|
11
14
|
block.replace +
|
|
12
|
-
|
|
15
|
+
contentStr.substring(searchIndex + block.search.length);
|
|
13
16
|
await writeFile(filePath, newContent);
|
|
14
17
|
}
|
|
15
18
|
export async function parseEditBlock(blockContent) {
|
|
@@ -1,7 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates a path to ensure it can be accessed or created.
|
|
3
|
+
* For existing paths, returns the real path (resolving symlinks).
|
|
4
|
+
* For non-existent paths, validates parent directories to ensure they exist.
|
|
5
|
+
*
|
|
6
|
+
* @param requestedPath The path to validate
|
|
7
|
+
* @returns Promise<string> The validated path
|
|
8
|
+
* @throws Error if the path or its parent directories don't exist
|
|
9
|
+
*/
|
|
1
10
|
export declare function validatePath(requestedPath: string): Promise<string>;
|
|
2
|
-
export
|
|
11
|
+
export interface FileResult {
|
|
12
|
+
content: string;
|
|
13
|
+
mimeType: string;
|
|
14
|
+
isImage: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function readFile(filePath: string, returnMetadata?: boolean): Promise<string | FileResult>;
|
|
3
17
|
export declare function writeFile(filePath: string, content: string): Promise<void>;
|
|
4
|
-
export
|
|
18
|
+
export interface MultiFileResult {
|
|
19
|
+
path: string;
|
|
20
|
+
content?: string;
|
|
21
|
+
mimeType?: string;
|
|
22
|
+
isImage?: boolean;
|
|
23
|
+
error?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function readMultipleFiles(paths: string[]): Promise<MultiFileResult[]>;
|
|
5
26
|
export declare function createDirectory(dirPath: string): Promise<void>;
|
|
6
27
|
export declare function listDirectory(dirPath: string): Promise<string[]>;
|
|
7
28
|
export declare function moveFile(sourcePath: string, destinationPath: string): Promise<void>;
|
package/dist/tools/filesystem.js
CHANGED
|
@@ -23,10 +23,40 @@ function expandHome(filepath) {
|
|
|
23
23
|
}
|
|
24
24
|
return filepath;
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Recursively validates parent directories until it finds a valid one
|
|
28
|
+
* This function handles the case where we need to create nested directories
|
|
29
|
+
* and we need to check if any of the parent directories exist
|
|
30
|
+
*
|
|
31
|
+
* @param directoryPath The path to validate
|
|
32
|
+
* @returns Promise<boolean> True if a valid parent directory was found
|
|
33
|
+
*/
|
|
34
|
+
async function validateParentDirectories(directoryPath) {
|
|
35
|
+
const parentDir = path.dirname(directoryPath);
|
|
36
|
+
// Base case: we've reached the root or the same directory (shouldn't happen normally)
|
|
37
|
+
if (parentDir === directoryPath || parentDir === path.dirname(parentDir)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
// Check if the parent directory exists
|
|
42
|
+
await fs.realpath(parentDir);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Parent doesn't exist, recursively check its parent
|
|
47
|
+
return validateParentDirectories(parentDir);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Validates a path to ensure it can be accessed or created.
|
|
52
|
+
* For existing paths, returns the real path (resolving symlinks).
|
|
53
|
+
* For non-existent paths, validates parent directories to ensure they exist.
|
|
54
|
+
*
|
|
55
|
+
* @param requestedPath The path to validate
|
|
56
|
+
* @returns Promise<string> The validated path
|
|
57
|
+
* @throws Error if the path or its parent directories don't exist
|
|
58
|
+
*/
|
|
27
59
|
export async function validatePath(requestedPath) {
|
|
28
|
-
// Temporarily allow all paths by just returning the resolved path
|
|
29
|
-
// TODO: Implement configurable path validation
|
|
30
60
|
// Expand home directory if present
|
|
31
61
|
const expandedPath = expandHome(requestedPath);
|
|
32
62
|
// Convert to absolute path
|
|
@@ -40,53 +70,59 @@ export async function validatePath(requestedPath) {
|
|
|
40
70
|
return await fs.realpath(absolute);
|
|
41
71
|
}
|
|
42
72
|
catch (error) {
|
|
43
|
-
//
|
|
73
|
+
// Path doesn't exist - validate parent directories
|
|
74
|
+
if (await validateParentDirectories(absolute)) {
|
|
75
|
+
// Return the path if a valid parent exists
|
|
76
|
+
// This will be used for folder creation and many other file operations
|
|
77
|
+
return absolute;
|
|
78
|
+
}
|
|
79
|
+
// If no valid parent directory was found, still return the absolute path
|
|
80
|
+
// to maintain compatibility with upstream behavior, but log a warning
|
|
81
|
+
console.warn(`Warning: Parent directory does not exist: ${path.dirname(absolute)}`);
|
|
44
82
|
return absolute;
|
|
45
83
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const normalizedReal = normalizePath(realPath);
|
|
64
|
-
const isRealPathAllowed = allowedDirectories.some(dir => normalizedReal.startsWith(normalizePath(dir)));
|
|
65
|
-
if (!isRealPathAllowed) {
|
|
66
|
-
throw new Error("Access denied - symlink target outside allowed directories");
|
|
84
|
+
}
|
|
85
|
+
export async function readFile(filePath, returnMetadata) {
|
|
86
|
+
const validPath = await validatePath(filePath);
|
|
87
|
+
// Import the MIME type utilities
|
|
88
|
+
const { getMimeType, isImageFile } = await import('./mime-types.js');
|
|
89
|
+
// Detect the MIME type based on file extension
|
|
90
|
+
const mimeType = getMimeType(validPath);
|
|
91
|
+
const isImage = isImageFile(mimeType);
|
|
92
|
+
if (isImage) {
|
|
93
|
+
// For image files, read as Buffer and convert to base64
|
|
94
|
+
const buffer = await fs.readFile(validPath);
|
|
95
|
+
const content = buffer.toString('base64');
|
|
96
|
+
if (returnMetadata === true) {
|
|
97
|
+
return { content, mimeType, isImage };
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
return content;
|
|
67
101
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// For
|
|
71
|
-
const parentDir = path.dirname(absolute);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// For all other files, try to read as UTF-8 text
|
|
72
105
|
try {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
106
|
+
const content = await fs.readFile(validPath, "utf-8");
|
|
107
|
+
if (returnMetadata === true) {
|
|
108
|
+
return { content, mimeType, isImage };
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
return content;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
// If UTF-8 reading fails, treat as binary and return base64 but still as text
|
|
116
|
+
const buffer = await fs.readFile(validPath);
|
|
117
|
+
const content = `Binary file content (base64 encoded):\n${buffer.toString('base64')}`;
|
|
118
|
+
if (returnMetadata === true) {
|
|
119
|
+
return { content, mimeType: 'text/plain', isImage: false };
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
return content;
|
|
78
123
|
}
|
|
79
|
-
return absolute;
|
|
80
|
-
} catch {
|
|
81
|
-
throw new Error(`Parent directory does not exist: ${parentDir}`);
|
|
82
124
|
}
|
|
83
125
|
}
|
|
84
|
-
*/
|
|
85
|
-
}
|
|
86
|
-
// File operation tools
|
|
87
|
-
export async function readFile(filePath) {
|
|
88
|
-
const validPath = await validatePath(filePath);
|
|
89
|
-
return fs.readFile(validPath, "utf-8");
|
|
90
126
|
}
|
|
91
127
|
export async function writeFile(filePath, content) {
|
|
92
128
|
const validPath = await validatePath(filePath);
|
|
@@ -96,12 +132,20 @@ export async function readMultipleFiles(paths) {
|
|
|
96
132
|
return Promise.all(paths.map(async (filePath) => {
|
|
97
133
|
try {
|
|
98
134
|
const validPath = await validatePath(filePath);
|
|
99
|
-
const
|
|
100
|
-
return
|
|
135
|
+
const fileResult = await readFile(validPath, true);
|
|
136
|
+
return {
|
|
137
|
+
path: filePath,
|
|
138
|
+
content: typeof fileResult === 'string' ? fileResult : fileResult.content,
|
|
139
|
+
mimeType: typeof fileResult === 'string' ? "text/plain" : fileResult.mimeType,
|
|
140
|
+
isImage: typeof fileResult === 'string' ? false : fileResult.isImage
|
|
141
|
+
};
|
|
101
142
|
}
|
|
102
143
|
catch (error) {
|
|
103
144
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
104
|
-
return
|
|
145
|
+
return {
|
|
146
|
+
path: filePath,
|
|
147
|
+
error: errorMessage
|
|
148
|
+
};
|
|
105
149
|
}
|
|
106
150
|
}));
|
|
107
151
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Simple MIME type detection based on file extension
|
|
2
|
+
export function getMimeType(filePath) {
|
|
3
|
+
const extension = filePath.toLowerCase().split('.').pop() || '';
|
|
4
|
+
// Image types - only the formats we can display
|
|
5
|
+
const imageTypes = {
|
|
6
|
+
'png': 'image/png',
|
|
7
|
+
'jpg': 'image/jpeg',
|
|
8
|
+
'jpeg': 'image/jpeg',
|
|
9
|
+
'gif': 'image/gif',
|
|
10
|
+
'webp': 'image/webp'
|
|
11
|
+
};
|
|
12
|
+
// Check if the file is an image
|
|
13
|
+
if (extension in imageTypes) {
|
|
14
|
+
return imageTypes[extension];
|
|
15
|
+
}
|
|
16
|
+
// Default to text/plain for all other files
|
|
17
|
+
return 'text/plain';
|
|
18
|
+
}
|
|
19
|
+
export function isImageFile(mimeType) {
|
|
20
|
+
return mimeType.startsWith('image/');
|
|
21
|
+
}
|
package/dist/utils.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { platform } from 'os';
|
|
2
|
+
let VERSION = 'unknown';
|
|
3
|
+
try {
|
|
4
|
+
const versionModule = await import('./version.js');
|
|
5
|
+
VERSION = versionModule.VERSION;
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
}
|
|
2
9
|
// Set default tracking state
|
|
3
10
|
const isTrackingEnabled = true;
|
|
4
11
|
let uniqueUserId = 'unknown';
|
|
@@ -25,7 +32,7 @@ try {
|
|
|
25
32
|
// Silently fail - we don't want analytics issues to break functionality
|
|
26
33
|
});
|
|
27
34
|
}
|
|
28
|
-
catch
|
|
35
|
+
catch {
|
|
29
36
|
//console.log('Analytics module not available - continuing without tracking');
|
|
30
37
|
}
|
|
31
38
|
export const capture = (event, properties) => {
|
|
@@ -36,14 +43,14 @@ export const capture = (event, properties) => {
|
|
|
36
43
|
properties = properties || {};
|
|
37
44
|
properties.timestamp = new Date().toISOString();
|
|
38
45
|
properties.platform = platform();
|
|
46
|
+
properties.DCVersion = VERSION;
|
|
39
47
|
posthog.capture({
|
|
40
48
|
distinctId: uniqueUserId,
|
|
41
49
|
event,
|
|
42
50
|
properties
|
|
43
51
|
});
|
|
44
52
|
}
|
|
45
|
-
catch
|
|
53
|
+
catch {
|
|
46
54
|
// Silently fail - we don't want analytics issues to break functionality
|
|
47
|
-
console.error('Analytics tracking failed:', error);
|
|
48
55
|
}
|
|
49
56
|
};
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.1.
|
|
1
|
+
export declare const VERSION = "0.1.31";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.1.
|
|
1
|
+
export const VERSION = '0.1.31';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wonderwhy-er/desktop-commander",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"description": "MCP server for terminal operations and file editing",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Eduards Ruzga",
|
|
@@ -31,8 +31,7 @@
|
|
|
31
31
|
"setup": "npm install && npm run build && node setup-claude-server.js",
|
|
32
32
|
"setup:debug": "npm install && npm run build && node setup-claude-server.js --debug",
|
|
33
33
|
"prepare": "npm run build",
|
|
34
|
-
"test": "node test/
|
|
35
|
-
"test:watch": "nodemon test/test.js",
|
|
34
|
+
"test": "node test/run-all-tests.js",
|
|
36
35
|
"link:local": "npm run build && npm link",
|
|
37
36
|
"unlink:local": "npm unlink",
|
|
38
37
|
"inspector": "npx @modelcontextprotocol/inspector dist/index.js"
|