@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 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
- [![Great! I just used Windsurf, bought license a week ago, for upgrading old fullstack socket project and it works many times good or ok but also many times runs away in cascade and have to revert all changes loosing hundereds of cascade tokens. In just a week down to less than 100 tokens and do not want to buy only 300 tokens for 10$. This Claude MCP ,bought claude Pro finally needed but wanted very good reason to also have next to ChatGPT, and now can code as much as I want not worrying about token cost.
261
+ [![Great! I just used Windsurf, bought license a week ago, for upgrading old fullstack socket project and it works many times good or ok but also many times runs away in cascade and have to revert all changes losing hundereds of cascade tokens. In just a week down to less than 100 tokens and do not want to buy only 300 tokens for 10$. This Claude MCP ,bought claude Pro finally needed but wanted very good reason to also have next to ChatGPT, and now can code as much as I want not worrying about token cost.
256
262
  Also this is much more than code editing it is much more thank you for great video!](https://raw.githubusercontent.com/wonderwhy-er/ClaudeComputerCommander/main/testemonials/img_2.png)
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
- "Reads UTF-8 text and provides detailed error messages " +
101
- "if the file cannot be read. Only works within allowed directories.",
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
- const content = await readFile(parsed.path);
257
- return {
258
- content: [{ type: "text", text: content }],
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 results = await readMultipleFiles(parsed.paths);
273
- return {
274
- content: [{ type: "text", text: results.join("\n---\n") }],
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
  };
@@ -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 = content.indexOf(block.search);
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 = content.substring(0, searchIndex) +
13
+ const newContent = contentStr.substring(0, searchIndex) +
11
14
  block.replace +
12
- content.substring(searchIndex + block.search.length);
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 declare function readFile(filePath: string): Promise<string>;
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 declare function readMultipleFiles(paths: string[]): Promise<string[]>;
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>;
@@ -23,10 +23,40 @@ function expandHome(filepath) {
23
23
  }
24
24
  return filepath;
25
25
  }
26
- // Security utilities
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
- // return path if it's not exist. This will be used for folder creation and many other file operations
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
- /* Original implementation commented out for future reference
47
- const expandedPath = expandHome(requestedPath);
48
- const absolute = path.isAbsolute(expandedPath)
49
- ? path.resolve(expandedPath)
50
- : path.resolve(process.cwd(), expandedPath);
51
-
52
- const normalizedRequested = normalizePath(absolute);
53
-
54
- // Check if path is within allowed directories
55
- const isAllowed = allowedDirectories.some(dir => normalizedRequested.startsWith(normalizePath(dir)));
56
- if (!isAllowed) {
57
- throw new Error(`Access denied - path outside allowed directories: ${absolute}`);
58
- }
59
-
60
- // Handle symlinks by checking their real path
61
- try {
62
- const realPath = await fs.realpath(absolute);
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
- return realPath;
69
- } catch (error) {
70
- // For new files that don't exist yet, verify parent directory
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 realParentPath = await fs.realpath(parentDir);
74
- const normalizedParent = normalizePath(realParentPath);
75
- const isParentAllowed = allowedDirectories.some(dir => normalizedParent.startsWith(normalizePath(dir)));
76
- if (!isParentAllowed) {
77
- throw new Error("Access denied - parent directory outside allowed directories");
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 content = await fs.readFile(validPath, "utf-8");
100
- return `${filePath}:\n${content}\n`;
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 `${filePath}: Error - ${errorMessage}`;
145
+ return {
146
+ path: filePath,
147
+ error: errorMessage
148
+ };
105
149
  }
106
150
  }));
107
151
  }
@@ -0,0 +1,2 @@
1
+ export declare function getMimeType(filePath: string): string;
2
+ export declare function isImageFile(mimeType: string): boolean;
@@ -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 (error) {
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 (error) {
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.29";
1
+ export declare const VERSION = "0.1.31";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.1.29';
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.29",
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/test.js",
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"