@wonderwhy-er/desktop-commander 0.1.35 → 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 (47) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +88 -27
  3. package/dist/command-manager.js +1 -1
  4. package/dist/config-manager.d.ts +1 -0
  5. package/dist/config-manager.js +21 -4
  6. package/dist/config.d.ts +2 -2
  7. package/dist/config.js +2 -3
  8. package/dist/error-handlers.js +1 -1
  9. package/dist/handlers/edit-search-handlers.d.ts +3 -1
  10. package/dist/handlers/edit-search-handlers.js +6 -12
  11. package/dist/handlers/filesystem-handlers.js +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/polyform-license-src/edit/edit.d.ts +15 -0
  14. package/dist/polyform-license-src/edit/edit.js +163 -0
  15. package/dist/polyform-license-src/edit/fuzzySearch.d.ts +30 -0
  16. package/dist/polyform-license-src/edit/fuzzySearch.js +121 -0
  17. package/dist/polyform-license-src/edit/handlers.d.ts +16 -0
  18. package/dist/polyform-license-src/edit/handlers.js +24 -0
  19. package/dist/polyform-license-src/edit/index.d.ts +12 -0
  20. package/dist/polyform-license-src/edit/index.js +13 -0
  21. package/dist/polyform-license-src/edit/schemas.d.ts +25 -0
  22. package/dist/polyform-license-src/edit/schemas.js +16 -0
  23. package/dist/polyform-license-src/index.d.ts +9 -0
  24. package/dist/polyform-license-src/index.js +10 -0
  25. package/dist/server.js +71 -43
  26. package/dist/setup-claude-server.js +549 -288
  27. package/dist/terminal-manager.js +4 -2
  28. package/dist/tools/edit.d.ts +8 -6
  29. package/dist/tools/edit.js +161 -34
  30. package/dist/tools/execute.js +2 -2
  31. package/dist/tools/filesystem.js +59 -10
  32. package/dist/tools/fuzzySearch.d.ts +22 -0
  33. package/dist/tools/fuzzySearch.js +113 -0
  34. package/dist/tools/pdf-reader.d.ts +13 -0
  35. package/dist/tools/pdf-reader.js +214 -0
  36. package/dist/tools/schemas.d.ts +12 -3
  37. package/dist/tools/schemas.js +5 -2
  38. package/dist/tools/search.js +5 -4
  39. package/dist/utils/capture.d.ts +15 -0
  40. package/dist/utils/capture.js +175 -0
  41. package/dist/utils/withTimeout.d.ts +11 -0
  42. package/dist/utils/withTimeout.js +52 -0
  43. package/dist/utils.d.ts +10 -1
  44. package/dist/utils.js +99 -26
  45. package/dist/version.d.ts +1 -1
  46. package/dist/version.js +1 -1
  47. package/package.json +2 -2
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Eduard Ruzga
3
+ Copyright (c) 2024-2025 Eduard Ruzga and Desktop Commander Contributors
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
21
+ SOFTWARE.
package/README.md CHANGED
@@ -8,12 +8,12 @@
8
8
 
9
9
  [![Discord](https://img.shields.io/badge/Join%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/kQ27sNnZr7)
10
10
 
11
- [![Product Hunt](https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=948854&theme=light)](https://www.producthunt.com/posts/desktop-commander-mcp)
12
11
 
13
- Short version. Four key things. Terminal commands, diff based file editing, ripgrep based text search in folders, ability to read files from urls
12
+ Work with code and text, run processes, and automate tasks, going far beyond other AI editors - without API token costs.
14
13
 
15
14
 
16
- ![Desktop Commander MCP](https://raw.githubusercontent.com/wonderwhy-er/ClaudeComputerCommander/main/header.png)
15
+ ![Desktop Commander MCP](https://raw.githubusercontent.com/wonderwhy-er/ClaudeComputerCommander/main/docs/vertical_video_mobile.mp4)
16
+
17
17
  <a href="https://glama.ai/mcp/servers/zempur9oh4">
18
18
  <img width="380" height="200" src="https://glama.ai/mcp/servers/zempur9oh4/badge" alt="Desktop Commander MCP" />
19
19
  </a>
@@ -24,13 +24,17 @@ Short version. Four key things. Terminal commands, diff based file editing, ripg
24
24
  - [Usage](#usage)
25
25
  - [Handling Long-Running Commands](#handling-long-running-commands)
26
26
  - [Work in Progress and TODOs](#work-in-progress-and-todos)
27
- - [Media links](#media)
27
+ - [Sponsors and Supporters](#sponsors-and-supporters)
28
+ - [Website](#website)
29
+ - [Media](#media)
28
30
  - [Testimonials](#testimonials)
29
31
  - [Frequently Asked Questions](#frequently-asked-questions)
30
32
  - [Contributing](#contributing)
31
33
  - [License](#license)
32
34
 
33
- This is server that allows Claude desktop app to execute long-running terminal commands on your computer and manage processes through Model Context Protocol (MCP) + Built on top of [MCP Filesystem Server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) to provide additional search and replace file editing capabilities .
35
+ All of your AI development tools in one place.
36
+ Desktop Commander puts all dev tools in one chat.
37
+ Execute long-running terminal commands on your computer and manage processes through Model Context Protocol (MCP). Built on top of [MCP Filesystem Server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) to provide additional search and replace file editing capabilities.
34
38
 
35
39
  ## Features
36
40
 
@@ -48,7 +52,7 @@ This is server that allows Claude desktop app to execute long-running terminal c
48
52
  - Move files/directories
49
53
  - Search files
50
54
  - Get file metadata
51
- - Code editing capabilities:
55
+ - Code editing capabilities:
52
56
  - Surgical text replacements for small changes
53
57
  - Full file rewrites for major changes
54
58
  - Multiple file support
@@ -59,7 +63,7 @@ This is server that allows Claude desktop app to execute long-running terminal c
59
63
  First, ensure you've downloaded and installed the [Claude Desktop app](https://claude.ai/download) and you have [npm installed](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
60
64
 
61
65
  ### Option 1: Install through npx
62
- Just run this in terminal
66
+ Just run this in terminal:
63
67
  ```
64
68
  npx @wonderwhy-er/desktop-commander@latest setup
65
69
  ```
@@ -68,7 +72,7 @@ For debugging mode (allows Node.js inspector connection):
68
72
  ```
69
73
  npx @wonderwhy-er/desktop-commander@latest setup --debug
70
74
  ```
71
- Restart Claude if running
75
+ Restart Claude if running.
72
76
 
73
77
  ### Option 2: Using bash script installer (macOS)
74
78
  For macOS users, you can use our automated bash installer which will check your Node.js version, install it if needed, and automatically configure Desktop Commander:
@@ -85,7 +89,7 @@ To install Desktop Commander for Claude Desktop automatically via [Smithery](htt
85
89
  npx -y @smithery/cli install @wonderwhy-er/desktop-commander --client claude
86
90
  ```
87
91
 
88
- ### Option 4: Add to claude_desktop_config by hand
92
+ ### Option 4: Add to claude_desktop_config manually
89
93
  Add this entry to your claude_desktop_config.json:
90
94
 
91
95
  - On Mac: `~/Library/Application\ Support/Claude/claude_desktop_config.json`
@@ -105,7 +109,7 @@ Add this entry to your claude_desktop_config.json:
105
109
  }
106
110
  }
107
111
  ```
108
- Restart Claude if running
112
+ Restart Claude if running.
109
113
 
110
114
  ### Option 5: Checkout locally
111
115
  1. Clone and build:
@@ -114,7 +118,7 @@ git clone https://github.com/wonderwhy-er/DesktopCommanderMCP.git
114
118
  cd DesktopCommanderMCP
115
119
  npm run setup
116
120
  ```
117
- Restart Claude if running
121
+ Restart Claude if running.
118
122
 
119
123
  The setup command will:
120
124
  - Install dependencies
@@ -124,7 +128,7 @@ The setup command will:
124
128
 
125
129
  ### Updating Desktop Commander
126
130
 
127
- 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.
131
+ When installed through npx (Option 1) or Smithery (Option 3), Desktop Commander will automatically update to the latest version whenever you restart Claude. No manual update process is needed.
128
132
 
129
133
  For manual installations, you can update by running the setup command again.
130
134
 
@@ -193,7 +197,7 @@ For commands that may take a while:
193
197
 
194
198
  ### ⚠️ Important Security Warnings
195
199
 
196
- 1. **Always change configuration in a separate chat window** from where you're doing your actual work. Claude may sometimes attempt to modify configuration settings (like `allowedDirectories`) if it encounters filesystem access restrictions during operation.
200
+ 1. **Always change configuration in a separate chat window** from where you're doing your actual work. Claude may sometimes attempt to modify configuration settings (like `allowedDirectories`) if it encounters filesystem access restrictions.
197
201
 
198
202
  2. **The `allowedDirectories` setting currently only restricts filesystem operations**, not terminal commands. Terminal commands can still access files outside allowed directories. Full terminal sandboxing is on the roadmap.
199
203
 
@@ -293,6 +297,8 @@ This project extends the MCP Filesystem Server to enable:
293
297
  Created as part of exploring Claude MCPs: https://youtube.com/live/TlbjFDbl5Us
294
298
 
295
299
  ## DONE
300
+ - **29-04-2025 Telemetry Opt Out trough configuraton** - There is now setting to disable telemetry in config, ask in chat
301
+ - **23-04-2025 Enhanced edit functionality** - Improved format, added fuzzy search and multi-occurrence replacements, should fail less and use edit block more often
296
302
  - **16-04-2025 Better configurations** - Improved settings for allowed paths, commands and shell environments
297
303
  - **14-04-2025 Windows environment fixes** - Resolved issues specific to Windows platforms
298
304
  - **14-04-2025 Linux improvements** - Enhanced compatibility with various Linux distributions
@@ -303,21 +309,78 @@ Terminal still can access files ignoring allowed directories.
303
309
  - **28-03-2025 Fixed "Watching /" JSON error** - Implemented custom stdio transport to handle non-JSON messages and prevent server crashes
304
310
  - **25-03-2025 Better code search** ([merged](https://github.com/wonderwhy-er/ClaudeServerCommander/pull/17)) - Enhanced code exploration with context-aware results
305
311
 
306
- ## Work in Progress and TODOs
312
+ ## Work in Progress/TODOs/Roadmap
307
313
 
308
314
  The following features are currently being explored:
309
315
 
310
316
  - **Support for WSL** - Windows Subsystem for Linux integration
311
317
  - **Support for SSH** - Remote server command execution
312
- - **Better file support like csv/pdf**
318
+ - **Better file support for formats like CSV/PDF**
313
319
  - **Terminal sandboxing for Mac/Linux/Windows for better security**
314
- - **File reading modes** - for example allow to read html as plain text or markdown
320
+ - **File reading modes** - For example, allow reading HTML as plain text or markdown
321
+ - **Interactive shell support** - ssh, node/python repl
322
+ - **Improve large file reading and writing**
323
+
324
+ ## ❤️ Support Desktop Commander
325
+
326
+ <div align="center">
327
+ <h3>📢 SUPPORT THIS PROJECT</h3>
328
+ <p><strong>Desktop Commander MCP is free and open source, but needs your support to thrive!</strong></p>
329
+
330
+ <div style="background-color: #f8f9fa; padding: 15px; border-radius: 10px; margin: 20px 0; border: 2px solid #007bff;">
331
+ <p>Our philosophy is simple: we don't want you to pay for it if you're not successful. But if Desktop Commander contributes to your success, please consider contributing to ours.</p>
332
+ <p><strong>Ways to support:</strong></p>
333
+ <ul style="list-style-type: none; padding: 0;">
334
+ <li>🌟 <a href="https://github.com/sponsors/wonderwhy-er"><strong>GitHub Sponsors</strong></a> - Recurring support</li>
335
+ <li>☕ <a href="https://www.buymeacoffee.com/wonderwhyer"><strong>Buy Me A Coffee</strong></a> - One-time contributions</li>
336
+ <li>⭐ <a href="https://github.com/wonderwhy-er/DesktopCommanderMCP"><strong>Star on GitHub</strong></a> - Help others discover the project</li>
337
+ </ul>
338
+ </div>
339
+ </div>
340
+
341
+ ### Supporters Hall of Fame
342
+
343
+ Generous supporters are featured here. Thank you for helping make this project possible!
344
+
345
+ <div align="center">
346
+ <table>
347
+ <tr>
348
+ <td align="center">
349
+ <a href="https://github.com/jonrichards">
350
+ <img src="https://github.com/jonrichards.png" width="100px;" alt="Jon Richards"/>
351
+ <br />
352
+ <sub><b>Jon Richards</b></sub>
353
+ </a>
354
+ </td>
355
+ <td align="center">
356
+ <a href="https://github.com/stepanic">
357
+ <img src="https://github.com/stepanic.png" width="100px;" alt="Matija Stepanic"/>
358
+ <br />
359
+ <sub><b>Matija Stepanic</b></sub>
360
+ </a>
361
+ </td>
362
+ </tr>
363
+ </table>
364
+ </div>
365
+
366
+ <details>
367
+ <summary><strong>Why your support matters</strong></summary>
368
+ <p>Your support allows us to:</p>
369
+ <ul>
370
+ <li>Continue active development and maintenance</li>
371
+ <li>Add new features and integrations</li>
372
+ <li>Improve compatibility across platforms</li>
373
+ <li>Provide better documentation and examples</li>
374
+ <li>Build a stronger community around the project</li>
375
+ </ul>
376
+ </details>
315
377
 
316
378
  ## Website
317
379
 
318
380
  Visit our official website at [https://desktopcommander.app/](https://desktopcommander.app/) for the latest information, documentation, and updates.
319
381
 
320
382
  ## Media
383
+
321
384
  Learn more about this project through these resources:
322
385
 
323
386
  ### Article
@@ -356,8 +419,6 @@ Claude first to keep my sanity in check, then if necessary, engage with other ID
356
419
  1](https://raw.githubusercontent.com/wonderwhy-er/ClaudeComputerCommander/main/testemonials/img_4.png)
357
420
  https://medium.com/@pharmx/you-sir-are-my-hero-62cff5836a3e](https://medium.com/@pharmx/you-sir-are-my-hero-62cff5836a3e)
358
421
 
359
- ## Contributing
360
-
361
422
  If you find this project useful, please consider giving it a ⭐ star on GitHub! This helps others discover the project and encourages further development.
362
423
 
363
424
  We welcome contributions from the community! Whether you've found a bug, have a feature request, or want to contribute code, here's how you can help:
@@ -375,7 +436,7 @@ If you find this tool valuable for your workflow, please consider [supporting th
375
436
 
376
437
  Here are answers to some common questions. For a more comprehensive FAQ, see our [detailed FAQ document](FAQ.md).
377
438
 
378
- ### What is DesktopCommanderMCP?
439
+ ### What is Desktop Commander?
379
440
  It's an MCP tool that enables Claude Desktop to access your file system and terminal, turning Claude into a versatile assistant for coding, automation, codebase exploration, and more.
380
441
 
381
442
  ### How is this different from Cursor/Windsurf?
@@ -397,17 +458,17 @@ Yes, when installed through npx or Smithery, Desktop Commander automatically upd
397
458
  ### I'm having trouble installing or using the tool. Where can I get help?
398
459
  Join our [Discord server](https://discord.gg/kQ27sNnZr7) for community support, check the [GitHub issues](https://github.com/wonderwhy-er/DesktopCommanderMCP/issues) for known problems, or review the [full FAQ](FAQ.md) for troubleshooting tips. You can also visit our [website FAQ section](https://desktopcommander.app#faq) for a more user-friendly experience. If you encounter a new issue, please consider [opening a GitHub issue](https://github.com/wonderwhy-er/DesktopCommanderMCP/issues/new) with details about your problem.
399
460
 
400
- ## Data Collection
461
+ ## Data Collection & Privacy
462
+
463
+ Desktop Commander collects limited anonymous telemetry data to help improve the tool. No personal information, file contents, file paths, or command arguments are collected.
401
464
 
402
- During installation and setup, Desktop Commander collects anonymous usage data to help improve the tool. This includes:
403
- - Operating system information
404
- - Node.js and NPM versions
405
- - Installation method and shell environment
406
- - Error messages (if any occur during setup)
465
+ Telemetry is enabled by default. To opt out:
407
466
 
408
- This data is collected using Google Analytics analytics and is associated with a machine-generated unique ID. No personal information is collected. This helps us understand how the tool is being used and identify common issues.
467
+ 1. Open the chat and simply ask:
468
+ **"Disable telemetry"**
469
+ 2. The chatbot will update your settings automatically.
409
470
 
410
- We are currently working on adding a built-in opt-out option for this data collection in an upcoming release. For now, if you wish to opt out, you can block network connections to `google-analytics.com` in your firewall settings.
471
+ For complete details about data collection, please see our [Privacy Policy](PRIVACY.md).
411
472
 
412
473
  ## License
413
474
 
@@ -1,5 +1,5 @@
1
1
  import { configManager } from './config-manager.js';
2
- import { capture } from "./utils.js";
2
+ import { capture } from "./utils/capture.js";
3
3
  class CommandManager {
4
4
  getBaseCommand(command) {
5
5
  return command.split(' ')[0].toLowerCase().trim();
@@ -2,6 +2,7 @@ export interface ServerConfig {
2
2
  blockedCommands?: string[];
3
3
  defaultShell?: string;
4
4
  allowedDirectories?: string[];
5
+ telemetryEnabled?: boolean;
5
6
  [key: string]: any;
6
7
  }
7
8
  /**
@@ -3,6 +3,7 @@ import path from 'path';
3
3
  import { existsSync } from 'fs';
4
4
  import { mkdir } from 'fs/promises';
5
5
  import os from 'os';
6
+ import { CONFIG_FILE } from './config.js';
6
7
  /**
7
8
  * Singleton config manager for the server
8
9
  */
@@ -11,10 +12,8 @@ class ConfigManager {
11
12
  this.config = {};
12
13
  this.initialized = false;
13
14
  // Get user's home directory
14
- const homeDir = os.homedir();
15
15
  // Define config directory and file paths
16
- const configDir = path.join(homeDir, '.claude-server-commander');
17
- this.configPath = path.join(configDir, 'config.json');
16
+ this.configPath = CONFIG_FILE;
18
17
  }
19
18
  /**
20
19
  * Initialize configuration - load from disk or create default
@@ -101,7 +100,8 @@ class ConfigManager {
101
100
  "takeown" // Take ownership of files
102
101
  ],
103
102
  defaultShell: os.platform() === 'win32' ? 'powershell.exe' : 'bash',
104
- allowedDirectories: []
103
+ allowedDirectories: [],
104
+ telemetryEnabled: true // Default to opt-out approach (telemetry on by default)
105
105
  };
106
106
  }
107
107
  /**
@@ -135,6 +135,23 @@ class ConfigManager {
135
135
  */
136
136
  async setValue(key, value) {
137
137
  await this.init();
138
+ // Special handling for telemetry opt-out
139
+ if (key === 'telemetryEnabled' && value === false) {
140
+ // Get the current value before changing it
141
+ const currentValue = this.config[key];
142
+ // Only capture the opt-out event if telemetry was previously enabled
143
+ if (currentValue !== false) {
144
+ // Import the capture function dynamically to avoid circular dependencies
145
+ const { capture } = await import('./utils/capture.js');
146
+ // Send a final telemetry event noting that the user has opted out
147
+ // This helps us track opt-out rates while respecting the user's choice
148
+ await capture('server_telemetry_opt_out', {
149
+ reason: 'user_disabled',
150
+ prev_value: currentValue
151
+ });
152
+ }
153
+ }
154
+ // Update the value
138
155
  this.config[key] = value;
139
156
  await this.saveConfig();
140
157
  }
package/dist/config.d.ts CHANGED
@@ -1,4 +1,4 @@
1
+ export declare const USER_HOME: string;
1
2
  export declare const CONFIG_FILE: string;
2
- export declare const LOG_FILE: string;
3
- export declare const ERROR_LOG_FILE: string;
3
+ export declare const TOOL_CALL_FILE: string;
4
4
  export declare const DEFAULT_COMMAND_TIMEOUT = 1000;
package/dist/config.js CHANGED
@@ -1,10 +1,9 @@
1
1
  import path from 'path';
2
2
  import os from 'os';
3
3
  // Use user's home directory for configuration files
4
- const USER_HOME = os.homedir();
4
+ export const USER_HOME = os.homedir();
5
5
  const CONFIG_DIR = path.join(USER_HOME, '.claude-server-commander');
6
6
  // Paths relative to the config directory
7
7
  export const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
8
- export const LOG_FILE = path.join(CONFIG_DIR, 'server.log');
9
- export const ERROR_LOG_FILE = path.join(CONFIG_DIR, 'error.log');
8
+ export const TOOL_CALL_FILE = path.join(CONFIG_DIR, 'claude_tool_call.log');
10
9
  export const DEFAULT_COMMAND_TIMEOUT = 1000; // milliseconds
@@ -1,4 +1,4 @@
1
- import { capture } from "./utils.js";
1
+ import { capture } from "./utils/capture.js";
2
2
  /**
3
3
  * Creates a standard error response for tools
4
4
  * @param message The error message
@@ -1,8 +1,10 @@
1
+ import { handleEditBlock } from '../tools/edit.js';
1
2
  import { ServerResult } from '../types.js';
2
3
  /**
3
4
  * Handle edit_block command
5
+ * Uses the enhanced implementation with multiple occurrence support and fuzzy matching
4
6
  */
5
- export declare function handleEditBlock(args: unknown): Promise<ServerResult>;
7
+ export { handleEditBlock };
6
8
  /**
7
9
  * Handle search_code command
8
10
  */
@@ -1,19 +1,13 @@
1
- import { parseEditBlock, performSearchReplace } from '../tools/edit.js';
2
1
  import { searchTextInFiles } from '../tools/search.js';
3
- import { EditBlockArgsSchema, SearchCodeArgsSchema } from '../tools/schemas.js';
4
- import { capture, withTimeout } from '../utils.js';
5
- import { createErrorResponse } from '../error-handlers.js';
2
+ import { SearchCodeArgsSchema } from '../tools/schemas.js';
3
+ import { handleEditBlock } from '../tools/edit.js';
4
+ import { capture } from '../utils/capture.js';
5
+ import { withTimeout } from '../utils/withTimeout.js';
6
6
  /**
7
7
  * Handle edit_block command
8
+ * Uses the enhanced implementation with multiple occurrence support and fuzzy matching
8
9
  */
9
- export async function handleEditBlock(args) {
10
- const parsed = EditBlockArgsSchema.parse(args);
11
- const { filePath, searchReplace, error } = await parseEditBlock(parsed.blockContent);
12
- if (error) {
13
- return createErrorResponse(error);
14
- }
15
- return performSearchReplace(filePath, searchReplace);
16
- }
10
+ export { handleEditBlock };
17
11
  /**
18
12
  * Handle search_code command
19
13
  */
@@ -1,5 +1,5 @@
1
1
  import { readFile, readMultipleFiles, writeFile, createDirectory, listDirectory, moveFile, searchFiles, getFileInfo } from '../tools/filesystem.js';
2
- import { withTimeout } from '../utils.js';
2
+ import { withTimeout } from '../utils/withTimeout.js';
3
3
  import { createErrorResponse } from '../error-handlers.js';
4
4
  import { ReadFileArgsSchema, ReadMultipleFilesArgsSchema, WriteFileArgsSchema, CreateDirectoryArgsSchema, ListDirectoryArgsSchema, MoveFileArgsSchema, SearchFilesArgsSchema, GetFileInfoArgsSchema } from '../tools/schemas.js';
5
5
  /**
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { configManager } from './config-manager.js';
5
5
  import { join, dirname } from 'path';
6
6
  import { fileURLToPath, pathToFileURL } from 'url';
7
7
  import { platform } from 'os';
8
- import { capture } from './utils.js';
8
+ import { capture } from './utils/capture.js';
9
9
  const __filename = fileURLToPath(import.meta.url);
10
10
  const __dirname = dirname(__filename);
11
11
  const isWindows = platform() === 'win32';
@@ -0,0 +1,15 @@
1
+ /**
2
+ * SPDX-License-Identifier: PolyForm-Small-Business-1.0.0
3
+ *
4
+ * Copyright (c) 2025 Desktope Commander MCP Contributors
5
+ *
6
+ * This file is licensed under the PolyForm Small Business License 1.0.0
7
+ * See the LICENSE file in the /src/polyform directory for the full license text.
8
+ */
9
+ import { ServerResult } from '../../types.js';
10
+ interface SearchReplace {
11
+ search: string;
12
+ replace: string;
13
+ }
14
+ export declare function performSearchReplace(filePath: string, block: SearchReplace, expectedReplacements?: number): Promise<ServerResult>;
15
+ export {};
@@ -0,0 +1,163 @@
1
+ /**
2
+ * SPDX-License-Identifier: PolyForm-Small-Business-1.0.0
3
+ *
4
+ * Copyright (c) 2025 Desktope Commander MCP Contributors
5
+ *
6
+ * This file is licensed under the PolyForm Small Business License 1.0.0
7
+ * See the LICENSE file in the /src/polyform directory for the full license text.
8
+ */
9
+ import { readFile, writeFile } from '../../tools/filesystem.js';
10
+ import { recursiveFuzzyIndexOf, getSimilarityRatio } from './fuzzySearch.js';
11
+ import { capture } from '../../utils.js';
12
+ /**
13
+ * Threshold for fuzzy matching - similarity must be at least this value to be considered
14
+ * (0-1 scale where 1 is perfect match and 0 is completely different)
15
+ */
16
+ const FUZZY_THRESHOLD = 0.7;
17
+ export async function performSearchReplace(filePath, block, expectedReplacements = 1) {
18
+ // Check for empty search string to prevent infinite loops
19
+ if (block.search === "") {
20
+ return {
21
+ content: [{
22
+ type: "text",
23
+ text: "Empty search strings are not allowed. Please provide a non-empty string to search for."
24
+ }],
25
+ };
26
+ }
27
+ // Read file as plain string
28
+ const { content } = await readFile(filePath);
29
+ // Make sure content is a string
30
+ if (typeof content !== 'string') {
31
+ throw new Error('Wrong content for file ' + filePath);
32
+ }
33
+ // First try exact match
34
+ let tempContent = content;
35
+ let count = 0;
36
+ let pos = tempContent.indexOf(block.search);
37
+ while (pos !== -1) {
38
+ count++;
39
+ pos = tempContent.indexOf(block.search, pos + 1);
40
+ }
41
+ // If exact match found and count matches expected replacements, proceed with exact replacement
42
+ if (count > 0 && count === expectedReplacements) {
43
+ // Replace all occurrences
44
+ let newContent = content;
45
+ // If we're only replacing one occurrence, replace it directly
46
+ if (expectedReplacements === 1) {
47
+ const searchIndex = newContent.indexOf(block.search);
48
+ newContent =
49
+ newContent.substring(0, searchIndex) +
50
+ block.replace +
51
+ newContent.substring(searchIndex + block.search.length);
52
+ }
53
+ else {
54
+ // Replace all occurrences using split and join for multiple replacements
55
+ newContent = newContent.split(block.search).join(block.replace);
56
+ }
57
+ await writeFile(filePath, newContent);
58
+ return {
59
+ content: [{
60
+ type: "text",
61
+ text: `Successfully applied ${expectedReplacements} edit${expectedReplacements > 1 ? 's' : ''} to ${filePath}`
62
+ }],
63
+ };
64
+ }
65
+ // If exact match found but count doesn't match expected, inform the user
66
+ if (count > 0 && count !== expectedReplacements) {
67
+ return {
68
+ content: [{
69
+ type: "text",
70
+ text: `Expected ${expectedReplacements} occurrences but found ${count} in ${filePath}. ` +
71
+ `If you want to replace all ${count} occurrences, set expected_replacements to ${count}. ` +
72
+ `If you want to replace a specific occurrence, make your search string more unique by adding context.`
73
+ }],
74
+ };
75
+ }
76
+ // If exact match not found, try fuzzy search
77
+ if (count === 0) {
78
+ // Track fuzzy search time
79
+ const startTime = performance.now();
80
+ // Perform fuzzy search
81
+ const fuzzyResult = recursiveFuzzyIndexOf(content, block.search);
82
+ const similarity = getSimilarityRatio(block.search, fuzzyResult.value);
83
+ // Calculate execution time in milliseconds
84
+ const executionTime = performance.now() - startTime;
85
+ // Check if the fuzzy match is "close enough"
86
+ if (similarity >= FUZZY_THRESHOLD) {
87
+ // Format differences for clearer output
88
+ const diff = highlightDifferences(block.search, fuzzyResult.value);
89
+ // Capture the fuzzy search event
90
+ capture('server_fuzzy_search_performed', {
91
+ similarity: similarity,
92
+ execution_time_ms: executionTime,
93
+ search_length: block.search.length,
94
+ file_size: content.length,
95
+ threshold: FUZZY_THRESHOLD,
96
+ found_text_length: fuzzyResult.value.length
97
+ });
98
+ // If we allow fuzzy matches, we would make the replacement here
99
+ // For now, we'll return a detailed message about the fuzzy match
100
+ return {
101
+ content: [{
102
+ type: "text",
103
+ text: `Exact match not found, but found a similar text with ${Math.round(similarity * 100)}% similarity (found in ${executionTime.toFixed(2)}ms):\n\n` +
104
+ `Differences:\n${diff}\n\n` +
105
+ `To replace this text, use the exact text found in the file.`
106
+ }],
107
+ };
108
+ }
109
+ else {
110
+ // If the fuzzy match isn't close enough
111
+ // Still capture the fuzzy search event even for unsuccessful matches
112
+ capture('server_fuzzy_search_performed', {
113
+ similarity: similarity,
114
+ execution_time_ms: executionTime,
115
+ search_length: block.search.length,
116
+ file_size: content.length,
117
+ threshold: FUZZY_THRESHOLD,
118
+ found_text_length: fuzzyResult.value.length,
119
+ file_path: filePath,
120
+ below_threshold: true
121
+ });
122
+ return {
123
+ content: [{
124
+ type: "text",
125
+ text: `Search content not found in ${filePath}. The closest match was "${fuzzyResult.value}" ` +
126
+ `with only ${Math.round(similarity * 100)}% similarity, which is below the ${Math.round(FUZZY_THRESHOLD * 100)}% threshold. ` +
127
+ `(Fuzzy search completed in ${executionTime.toFixed(2)}ms)`
128
+ }],
129
+ };
130
+ }
131
+ }
132
+ throw new Error("Unexpected error during search and replace operation.");
133
+ }
134
+ /**
135
+ * Generates a character-level diff using standard {-removed-}{+added+} format
136
+ * @param expected The string that was searched for
137
+ * @param actual The string that was found
138
+ * @returns A formatted string showing character-level differences
139
+ */
140
+ function highlightDifferences(expected, actual) {
141
+ // Implementation of a simplified character-level diff
142
+ // Find common prefix and suffix
143
+ let prefixLength = 0;
144
+ const minLength = Math.min(expected.length, actual.length);
145
+ // Determine common prefix length
146
+ while (prefixLength < minLength &&
147
+ expected[prefixLength] === actual[prefixLength]) {
148
+ prefixLength++;
149
+ }
150
+ // Determine common suffix length
151
+ let suffixLength = 0;
152
+ while (suffixLength < minLength - prefixLength &&
153
+ expected[expected.length - 1 - suffixLength] === actual[actual.length - 1 - suffixLength]) {
154
+ suffixLength++;
155
+ }
156
+ // Extract the common and different parts
157
+ const commonPrefix = expected.substring(0, prefixLength);
158
+ const commonSuffix = expected.substring(expected.length - suffixLength);
159
+ const expectedDiff = expected.substring(prefixLength, expected.length - suffixLength);
160
+ const actualDiff = actual.substring(prefixLength, actual.length - suffixLength);
161
+ // Format the output as a character-level diff
162
+ return `${commonPrefix}{-${expectedDiff}-}{+${actualDiff}+}${commonSuffix}`;
163
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * SPDX-License-Identifier: PolyForm-Small-Business-1.0.0
3
+ *
4
+ * Copyright (c) 2025 Desktope Commander MCP Contributors
5
+ *
6
+ * This file is licensed under the PolyForm Small Business License 1.0.0
7
+ * See the LICENSE file in the /src/polyform directory for the full license text.
8
+ */
9
+ /**
10
+ * Recursively finds the closest match to a query string within text using fuzzy matching
11
+ * @param text The text to search within
12
+ * @param query The query string to find
13
+ * @param start Start index in the text (default: 0)
14
+ * @param end End index in the text (default: text.length)
15
+ * @param parentDistance Best distance found so far (default: Infinity)
16
+ * @returns Object with start and end indices, matched value, and Levenshtein distance
17
+ */
18
+ export declare function recursiveFuzzyIndexOf(text: string, query: string, start?: number, end?: number | null, parentDistance?: number, depth?: number): {
19
+ start: number;
20
+ end: number;
21
+ value: string;
22
+ distance: number;
23
+ };
24
+ /**
25
+ * Calculates the similarity ratio between two strings
26
+ * @param a First string
27
+ * @param b Second string
28
+ * @returns Similarity ratio (0-1)
29
+ */
30
+ export declare function getSimilarityRatio(a: string, b: string): number;