@wonderwhy-er/desktop-commander 0.1.34 → 0.1.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +2 -2
- package/README.md +186 -56
- package/dist/command-manager.d.ts +1 -7
- package/dist/command-manager.js +31 -50
- package/dist/config-manager.d.ts +28 -16
- package/dist/config-manager.js +124 -189
- package/dist/config.d.ts +2 -2
- package/dist/config.js +7 -4
- package/dist/error-handlers.js +4 -0
- package/dist/handlers/edit-search-handlers.d.ts +3 -1
- package/dist/handlers/edit-search-handlers.js +9 -19
- package/dist/handlers/filesystem-handlers.d.ts +0 -4
- package/dist/handlers/filesystem-handlers.js +11 -19
- package/dist/handlers/index.d.ts +0 -1
- package/dist/handlers/index.js +0 -1
- package/dist/index.js +19 -4
- package/dist/polyform-license-src/edit/edit.d.ts +15 -0
- package/dist/polyform-license-src/edit/edit.js +163 -0
- package/dist/polyform-license-src/edit/fuzzySearch.d.ts +30 -0
- package/dist/polyform-license-src/edit/fuzzySearch.js +121 -0
- package/dist/polyform-license-src/edit/handlers.d.ts +16 -0
- package/dist/polyform-license-src/edit/handlers.js +24 -0
- package/dist/polyform-license-src/edit/index.d.ts +12 -0
- package/dist/polyform-license-src/edit/index.js +13 -0
- package/dist/polyform-license-src/edit/schemas.d.ts +25 -0
- package/dist/polyform-license-src/edit/schemas.js +16 -0
- package/dist/polyform-license-src/index.d.ts +9 -0
- package/dist/polyform-license-src/index.js +10 -0
- package/dist/sandbox/index.d.ts +9 -0
- package/dist/sandbox/index.js +50 -0
- package/dist/sandbox/mac-sandbox.d.ts +19 -0
- package/dist/sandbox/mac-sandbox.js +174 -0
- package/dist/server.js +181 -176
- package/dist/setup-claude-server.js +554 -244
- package/dist/terminal-manager.d.ts +1 -1
- package/dist/terminal-manager.js +22 -3
- package/dist/tools/config.d.ts +0 -58
- package/dist/tools/config.js +44 -107
- package/dist/tools/debug-path.d.ts +1 -0
- package/dist/tools/debug-path.js +44 -0
- package/dist/tools/edit.d.ts +8 -6
- package/dist/tools/edit.js +165 -35
- package/dist/tools/execute.js +6 -6
- package/dist/tools/filesystem-fixed.d.ts +22 -0
- package/dist/tools/filesystem-fixed.js +176 -0
- package/dist/tools/filesystem.d.ts +4 -6
- package/dist/tools/filesystem.js +157 -87
- package/dist/tools/fuzzySearch.d.ts +22 -0
- package/dist/tools/fuzzySearch.js +113 -0
- package/dist/tools/pdf-reader.d.ts +13 -0
- package/dist/tools/pdf-reader.js +214 -0
- package/dist/tools/schemas.d.ts +29 -19
- package/dist/tools/schemas.js +15 -8
- package/dist/tools/search.js +5 -4
- package/dist/utils/capture.d.ts +15 -0
- package/dist/utils/capture.js +175 -0
- package/dist/utils/withTimeout.d.ts +11 -0
- package/dist/utils/withTimeout.js +52 -0
- package/dist/utils.d.ts +15 -1
- package/dist/utils.js +174 -41
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -3
|
@@ -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;
|
|
@@ -0,0 +1,121 @@
|
|
|
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 { distance } from 'fastest-levenshtein';
|
|
10
|
+
import { capture } from '../../utils.js';
|
|
11
|
+
/**
|
|
12
|
+
* Recursively finds the closest match to a query string within text using fuzzy matching
|
|
13
|
+
* @param text The text to search within
|
|
14
|
+
* @param query The query string to find
|
|
15
|
+
* @param start Start index in the text (default: 0)
|
|
16
|
+
* @param end End index in the text (default: text.length)
|
|
17
|
+
* @param parentDistance Best distance found so far (default: Infinity)
|
|
18
|
+
* @returns Object with start and end indices, matched value, and Levenshtein distance
|
|
19
|
+
*/
|
|
20
|
+
export function recursiveFuzzyIndexOf(text, query, start = 0, end = null, parentDistance = Infinity, depth = 0) {
|
|
21
|
+
// For debugging and performance tracking purposes
|
|
22
|
+
if (depth === 0) {
|
|
23
|
+
const startTime = performance.now();
|
|
24
|
+
const result = recursiveFuzzyIndexOf(text, query, start, end, parentDistance, depth + 1);
|
|
25
|
+
const executionTime = performance.now() - startTime;
|
|
26
|
+
// Capture detailed metrics for the recursive search for in-depth analysis
|
|
27
|
+
capture('fuzzy_search_recursive_metrics', {
|
|
28
|
+
execution_time_ms: executionTime,
|
|
29
|
+
text_length: text.length,
|
|
30
|
+
query_length: query.length,
|
|
31
|
+
result_distance: result.distance
|
|
32
|
+
});
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
if (end === null)
|
|
36
|
+
end = text.length;
|
|
37
|
+
// For small text segments, use iterative approach
|
|
38
|
+
if (end - start <= 2 * query.length) {
|
|
39
|
+
return iterativeReduction(text, query, start, end, parentDistance);
|
|
40
|
+
}
|
|
41
|
+
let midPoint = start + Math.floor((end - start) / 2);
|
|
42
|
+
let leftEnd = Math.min(end, midPoint + query.length); // Include query length to cover overlaps
|
|
43
|
+
let rightStart = Math.max(start, midPoint - query.length); // Include query length to cover overlaps
|
|
44
|
+
// Calculate distance for current segments
|
|
45
|
+
let leftDistance = distance(text.substring(start, leftEnd), query);
|
|
46
|
+
let rightDistance = distance(text.substring(rightStart, end), query);
|
|
47
|
+
let bestDistance = Math.min(leftDistance, parentDistance, rightDistance);
|
|
48
|
+
// If parent distance is already the best, use iterative approach
|
|
49
|
+
if (parentDistance === bestDistance) {
|
|
50
|
+
return iterativeReduction(text, query, start, end, parentDistance);
|
|
51
|
+
}
|
|
52
|
+
// Recursively search the better half
|
|
53
|
+
if (leftDistance < rightDistance) {
|
|
54
|
+
return recursiveFuzzyIndexOf(text, query, start, leftEnd, bestDistance, depth + 1);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
return recursiveFuzzyIndexOf(text, query, rightStart, end, bestDistance, depth + 1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Iteratively refines the best match by reducing the search area
|
|
62
|
+
* @param text The text to search within
|
|
63
|
+
* @param query The query string to find
|
|
64
|
+
* @param start Start index in the text
|
|
65
|
+
* @param end End index in the text
|
|
66
|
+
* @param parentDistance Best distance found so far
|
|
67
|
+
* @returns Object with start and end indices, matched value, and Levenshtein distance
|
|
68
|
+
*/
|
|
69
|
+
function iterativeReduction(text, query, start, end, parentDistance) {
|
|
70
|
+
const startTime = performance.now();
|
|
71
|
+
let iterations = 0;
|
|
72
|
+
let bestDistance = parentDistance;
|
|
73
|
+
let bestStart = start;
|
|
74
|
+
let bestEnd = end;
|
|
75
|
+
// Improve start position
|
|
76
|
+
let nextDistance = distance(text.substring(bestStart + 1, bestEnd), query);
|
|
77
|
+
while (nextDistance < bestDistance) {
|
|
78
|
+
bestDistance = nextDistance;
|
|
79
|
+
bestStart++;
|
|
80
|
+
const smallerString = text.substring(bestStart + 1, bestEnd);
|
|
81
|
+
nextDistance = distance(smallerString, query);
|
|
82
|
+
iterations++;
|
|
83
|
+
}
|
|
84
|
+
// Improve end position
|
|
85
|
+
nextDistance = distance(text.substring(bestStart, bestEnd - 1), query);
|
|
86
|
+
while (nextDistance < bestDistance) {
|
|
87
|
+
bestDistance = nextDistance;
|
|
88
|
+
bestEnd--;
|
|
89
|
+
const smallerString = text.substring(bestStart, bestEnd - 1);
|
|
90
|
+
nextDistance = distance(smallerString, query);
|
|
91
|
+
iterations++;
|
|
92
|
+
}
|
|
93
|
+
const executionTime = performance.now() - startTime;
|
|
94
|
+
// Capture metrics for the iterative refinement phase
|
|
95
|
+
capture('fuzzy_search_iterative_metrics', {
|
|
96
|
+
execution_time_ms: executionTime,
|
|
97
|
+
iterations: iterations,
|
|
98
|
+
segment_length: end - start,
|
|
99
|
+
query_length: query.length,
|
|
100
|
+
final_distance: bestDistance
|
|
101
|
+
});
|
|
102
|
+
return {
|
|
103
|
+
start: bestStart,
|
|
104
|
+
end: bestEnd,
|
|
105
|
+
value: text.substring(bestStart, bestEnd),
|
|
106
|
+
distance: bestDistance
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Calculates the similarity ratio between two strings
|
|
111
|
+
* @param a First string
|
|
112
|
+
* @param b Second string
|
|
113
|
+
* @returns Similarity ratio (0-1)
|
|
114
|
+
*/
|
|
115
|
+
export function getSimilarityRatio(a, b) {
|
|
116
|
+
const maxLength = Math.max(a.length, b.length);
|
|
117
|
+
if (maxLength === 0)
|
|
118
|
+
return 1; // Both strings are empty
|
|
119
|
+
const levenshteinDistance = distance(a, b);
|
|
120
|
+
return 1 - (levenshteinDistance / maxLength);
|
|
121
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
/**
|
|
11
|
+
* Handle edit_block command with enhanced functionality
|
|
12
|
+
* - Supports multiple replacements
|
|
13
|
+
* - Validates expected replacements count
|
|
14
|
+
* - Provides detailed error messages
|
|
15
|
+
*/
|
|
16
|
+
export declare function handleEnhancedEditBlock(args: unknown): Promise<ServerResult>;
|
|
@@ -0,0 +1,24 @@
|
|
|
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 { performSearchReplace } from './edit.js';
|
|
10
|
+
import { EditBlockArgsSchema } from './schemas.js';
|
|
11
|
+
/**
|
|
12
|
+
* Handle edit_block command with enhanced functionality
|
|
13
|
+
* - Supports multiple replacements
|
|
14
|
+
* - Validates expected replacements count
|
|
15
|
+
* - Provides detailed error messages
|
|
16
|
+
*/
|
|
17
|
+
export async function handleEnhancedEditBlock(args) {
|
|
18
|
+
const parsed = EditBlockArgsSchema.parse(args);
|
|
19
|
+
const searchReplace = {
|
|
20
|
+
search: parsed.old_string,
|
|
21
|
+
replace: parsed.new_string
|
|
22
|
+
};
|
|
23
|
+
return performSearchReplace(parsed.file_path, searchReplace, parsed.expected_replacements);
|
|
24
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
export * from './edit.js';
|
|
10
|
+
export * from './fuzzySearch.js';
|
|
11
|
+
export * from './handlers.js';
|
|
12
|
+
export * from './schemas.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
// Export all enhanced edit functionality
|
|
10
|
+
export * from './edit.js';
|
|
11
|
+
export * from './fuzzySearch.js';
|
|
12
|
+
export * from './handlers.js';
|
|
13
|
+
export * from './schemas.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
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 { z } from 'zod';
|
|
10
|
+
export declare const EditBlockArgsSchema: z.ZodObject<{
|
|
11
|
+
file_path: z.ZodString;
|
|
12
|
+
old_string: z.ZodString;
|
|
13
|
+
new_string: z.ZodString;
|
|
14
|
+
expected_replacements: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
15
|
+
}, "strip", z.ZodTypeAny, {
|
|
16
|
+
file_path: string;
|
|
17
|
+
old_string: string;
|
|
18
|
+
new_string: string;
|
|
19
|
+
expected_replacements: number;
|
|
20
|
+
}, {
|
|
21
|
+
file_path: string;
|
|
22
|
+
old_string: string;
|
|
23
|
+
new_string: string;
|
|
24
|
+
expected_replacements?: number | undefined;
|
|
25
|
+
}>;
|
|
@@ -0,0 +1,16 @@
|
|
|
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 { z } from 'zod';
|
|
10
|
+
// Enhanced edit block schema with separate parameters for clarity
|
|
11
|
+
export const EditBlockArgsSchema = z.object({
|
|
12
|
+
file_path: z.string(),
|
|
13
|
+
old_string: z.string(),
|
|
14
|
+
new_string: z.string(),
|
|
15
|
+
expected_replacements: z.number().optional().default(1),
|
|
16
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
export * from './edit/index.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
// Export all PolyForm-licensed functionality
|
|
10
|
+
export * from './edit/index.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CommandExecutionResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Platform detection and sandbox execution router
|
|
4
|
+
*/
|
|
5
|
+
export declare function executeSandboxedCommand(command: string, timeoutMs?: number, shell?: string): Promise<CommandExecutionResult>;
|
|
6
|
+
/**
|
|
7
|
+
* Check if sandboxed execution is available for the current platform
|
|
8
|
+
*/
|
|
9
|
+
export declare function isSandboxAvailable(): boolean;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import { executeSandboxedCommand as macExecuteSandboxedCommand } from './mac-sandbox.js';
|
|
3
|
+
import { configManager } from '../config-manager.js';
|
|
4
|
+
/**
|
|
5
|
+
* Platform detection and sandbox execution router
|
|
6
|
+
*/
|
|
7
|
+
export async function executeSandboxedCommand(command, timeoutMs = 30000, shell) {
|
|
8
|
+
// Get the allowed directories from config
|
|
9
|
+
const config = await configManager.getConfig();
|
|
10
|
+
const allowedDirectories = config.allowedDirectories || [os.homedir()];
|
|
11
|
+
const platform = os.platform();
|
|
12
|
+
// Platform-specific sandbox execution
|
|
13
|
+
switch (platform) {
|
|
14
|
+
case 'darwin': // macOS
|
|
15
|
+
try {
|
|
16
|
+
const result = await macExecuteSandboxedCommand(command, allowedDirectories, timeoutMs);
|
|
17
|
+
return {
|
|
18
|
+
pid: result.pid,
|
|
19
|
+
output: result.output,
|
|
20
|
+
isBlocked: result.isBlocked
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error('Mac sandbox execution error:', error);
|
|
25
|
+
return {
|
|
26
|
+
pid: -1,
|
|
27
|
+
output: `Sandbox execution error: ${error instanceof Error ? error.message : String(error)}`,
|
|
28
|
+
isBlocked: false
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// Add cases for other platforms when implemented
|
|
32
|
+
// case 'linux':
|
|
33
|
+
// case 'win32':
|
|
34
|
+
default:
|
|
35
|
+
// For unsupported platforms, return an error
|
|
36
|
+
return {
|
|
37
|
+
pid: -1,
|
|
38
|
+
output: `Sandbox execution not supported on ${platform}. Command was not executed: ${command}`,
|
|
39
|
+
isBlocked: false
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Check if sandboxed execution is available for the current platform
|
|
45
|
+
*/
|
|
46
|
+
export function isSandboxAvailable() {
|
|
47
|
+
const platform = os.platform();
|
|
48
|
+
// Currently only implemented for macOS
|
|
49
|
+
return platform === 'darwin';
|
|
50
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a temporary sandbox profile for macOS that restricts access to allowed directories
|
|
3
|
+
* @param allowedDirectories Array of directories that should be accessible
|
|
4
|
+
* @returns Path to the generated sandbox profile file
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateSandboxProfile(allowedDirectories: string[]): Promise<string>;
|
|
7
|
+
/**
|
|
8
|
+
* Execute a command in a macOS sandbox with access restricted to allowed directories
|
|
9
|
+
* @param command Command to execute
|
|
10
|
+
* @param allowedDirectories Array of allowed directory paths
|
|
11
|
+
* @param options Additional execution options
|
|
12
|
+
* @returns Promise resolving to the execution result
|
|
13
|
+
*/
|
|
14
|
+
export declare function executeSandboxedCommand(command: string, allowedDirectories: string[], timeoutMs?: number): Promise<{
|
|
15
|
+
output: string;
|
|
16
|
+
exitCode: number | null;
|
|
17
|
+
isBlocked: boolean;
|
|
18
|
+
pid: number;
|
|
19
|
+
}>;
|