@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.
Files changed (63) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +186 -56
  3. package/dist/command-manager.d.ts +1 -7
  4. package/dist/command-manager.js +31 -50
  5. package/dist/config-manager.d.ts +28 -16
  6. package/dist/config-manager.js +124 -189
  7. package/dist/config.d.ts +2 -2
  8. package/dist/config.js +7 -4
  9. package/dist/error-handlers.js +4 -0
  10. package/dist/handlers/edit-search-handlers.d.ts +3 -1
  11. package/dist/handlers/edit-search-handlers.js +9 -19
  12. package/dist/handlers/filesystem-handlers.d.ts +0 -4
  13. package/dist/handlers/filesystem-handlers.js +11 -19
  14. package/dist/handlers/index.d.ts +0 -1
  15. package/dist/handlers/index.js +0 -1
  16. package/dist/index.js +19 -4
  17. package/dist/polyform-license-src/edit/edit.d.ts +15 -0
  18. package/dist/polyform-license-src/edit/edit.js +163 -0
  19. package/dist/polyform-license-src/edit/fuzzySearch.d.ts +30 -0
  20. package/dist/polyform-license-src/edit/fuzzySearch.js +121 -0
  21. package/dist/polyform-license-src/edit/handlers.d.ts +16 -0
  22. package/dist/polyform-license-src/edit/handlers.js +24 -0
  23. package/dist/polyform-license-src/edit/index.d.ts +12 -0
  24. package/dist/polyform-license-src/edit/index.js +13 -0
  25. package/dist/polyform-license-src/edit/schemas.d.ts +25 -0
  26. package/dist/polyform-license-src/edit/schemas.js +16 -0
  27. package/dist/polyform-license-src/index.d.ts +9 -0
  28. package/dist/polyform-license-src/index.js +10 -0
  29. package/dist/sandbox/index.d.ts +9 -0
  30. package/dist/sandbox/index.js +50 -0
  31. package/dist/sandbox/mac-sandbox.d.ts +19 -0
  32. package/dist/sandbox/mac-sandbox.js +174 -0
  33. package/dist/server.js +181 -176
  34. package/dist/setup-claude-server.js +554 -244
  35. package/dist/terminal-manager.d.ts +1 -1
  36. package/dist/terminal-manager.js +22 -3
  37. package/dist/tools/config.d.ts +0 -58
  38. package/dist/tools/config.js +44 -107
  39. package/dist/tools/debug-path.d.ts +1 -0
  40. package/dist/tools/debug-path.js +44 -0
  41. package/dist/tools/edit.d.ts +8 -6
  42. package/dist/tools/edit.js +165 -35
  43. package/dist/tools/execute.js +6 -6
  44. package/dist/tools/filesystem-fixed.d.ts +22 -0
  45. package/dist/tools/filesystem-fixed.js +176 -0
  46. package/dist/tools/filesystem.d.ts +4 -6
  47. package/dist/tools/filesystem.js +157 -87
  48. package/dist/tools/fuzzySearch.d.ts +22 -0
  49. package/dist/tools/fuzzySearch.js +113 -0
  50. package/dist/tools/pdf-reader.d.ts +13 -0
  51. package/dist/tools/pdf-reader.js +214 -0
  52. package/dist/tools/schemas.d.ts +29 -19
  53. package/dist/tools/schemas.js +15 -8
  54. package/dist/tools/search.js +5 -4
  55. package/dist/utils/capture.d.ts +15 -0
  56. package/dist/utils/capture.js +175 -0
  57. package/dist/utils/withTimeout.d.ts +11 -0
  58. package/dist/utils/withTimeout.js +52 -0
  59. package/dist/utils.d.ts +15 -1
  60. package/dist/utils.js +174 -41
  61. package/dist/version.d.ts +1 -1
  62. package/dist/version.js +1 -1
  63. 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
+ }>;