@wonderwhy-er/desktop-commander 0.1.37 → 0.1.39
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 +1 -1
- package/dist/REPLSessionManager.d.ts +109 -0
- package/dist/REPLSessionManager.js +364 -0
- package/dist/REPLSessionManager.test.d.ts +1 -0
- package/dist/REPLSessionManager.test.js +75 -0
- package/dist/client/replClient.d.ts +63 -0
- package/dist/client/replClient.js +217 -0
- package/dist/client/sshClient.d.ts +82 -0
- package/dist/client/sshClient.js +200 -0
- package/dist/handlers/repl-handlers.d.ts +21 -0
- package/dist/handlers/repl-handlers.js +37 -0
- package/dist/handlers/replCommandHandler.d.ts +125 -0
- package/dist/handlers/replCommandHandler.js +255 -0
- package/dist/handlers/replCommandHandler.test.d.ts +1 -0
- package/dist/handlers/replCommandHandler.test.js +103 -0
- package/dist/index.js +1 -2
- package/dist/repl-manager.d.ts +73 -0
- package/dist/repl-manager.js +407 -0
- package/dist/replIntegration.d.ts +14 -0
- package/dist/replIntegration.js +27 -0
- package/dist/setup-claude-server.js +14 -15
- package/dist/tools/edit.js +83 -24
- package/dist/tools/enhanced-read-output.js +69 -0
- package/dist/tools/enhanced-send-input.js +111 -0
- package/dist/tools/filesystem.js +6 -5
- package/dist/tools/repl.d.ts +21 -0
- package/dist/tools/repl.js +217 -0
- package/dist/tools/send-input.d.ts +2 -0
- package/dist/tools/send-input.js +45 -0
- package/dist/utils/lineEndingHandler.d.ts +21 -0
- package/dist/utils/lineEndingHandler.js +77 -0
- package/dist/utils/lineEndingHandler_optimized.d.ts +21 -0
- package/dist/utils/lineEndingHandler_optimized.js +77 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/tools/edit.js
CHANGED
|
@@ -3,11 +3,64 @@ import { recursiveFuzzyIndexOf, getSimilarityRatio } from './fuzzySearch.js';
|
|
|
3
3
|
import { capture } from '../utils/capture.js';
|
|
4
4
|
import { EditBlockArgsSchema } from "./schemas.js";
|
|
5
5
|
import path from 'path';
|
|
6
|
+
import { detectLineEnding, normalizeLineEndings } from '../utils/lineEndingHandler.js';
|
|
6
7
|
/**
|
|
7
8
|
* Threshold for fuzzy matching - similarity must be at least this value to be considered
|
|
8
9
|
* (0-1 scale where 1 is perfect match and 0 is completely different)
|
|
9
10
|
*/
|
|
10
11
|
const FUZZY_THRESHOLD = 0.7;
|
|
12
|
+
/**
|
|
13
|
+
* Extract character code data from diff
|
|
14
|
+
* @param expected The string that was searched for
|
|
15
|
+
* @param actual The string that was found
|
|
16
|
+
* @returns Character code statistics
|
|
17
|
+
*/
|
|
18
|
+
function getCharacterCodeData(expected, actual) {
|
|
19
|
+
// Find common prefix and suffix
|
|
20
|
+
let prefixLength = 0;
|
|
21
|
+
const minLength = Math.min(expected.length, actual.length);
|
|
22
|
+
// Determine common prefix length
|
|
23
|
+
while (prefixLength < minLength &&
|
|
24
|
+
expected[prefixLength] === actual[prefixLength]) {
|
|
25
|
+
prefixLength++;
|
|
26
|
+
}
|
|
27
|
+
// Determine common suffix length
|
|
28
|
+
let suffixLength = 0;
|
|
29
|
+
while (suffixLength < minLength - prefixLength &&
|
|
30
|
+
expected[expected.length - 1 - suffixLength] === actual[actual.length - 1 - suffixLength]) {
|
|
31
|
+
suffixLength++;
|
|
32
|
+
}
|
|
33
|
+
// Extract the different parts
|
|
34
|
+
const expectedDiff = expected.substring(prefixLength, expected.length - suffixLength);
|
|
35
|
+
const actualDiff = actual.substring(prefixLength, actual.length - suffixLength);
|
|
36
|
+
// Count unique character codes in the diff
|
|
37
|
+
const characterCodes = new Map();
|
|
38
|
+
const fullDiff = expectedDiff + actualDiff;
|
|
39
|
+
for (let i = 0; i < fullDiff.length; i++) {
|
|
40
|
+
const charCode = fullDiff.charCodeAt(i);
|
|
41
|
+
characterCodes.set(charCode, (characterCodes.get(charCode) || 0) + 1);
|
|
42
|
+
}
|
|
43
|
+
// Create character codes string report
|
|
44
|
+
const charCodeReport = [];
|
|
45
|
+
characterCodes.forEach((count, code) => {
|
|
46
|
+
// Include character representation for better readability
|
|
47
|
+
const char = String.fromCharCode(code);
|
|
48
|
+
// Make special characters more readable
|
|
49
|
+
const charDisplay = code < 32 || code > 126 ? `\\x${code.toString(16).padStart(2, '0')}` : char;
|
|
50
|
+
charCodeReport.push(`${code}:${count}[${charDisplay}]`);
|
|
51
|
+
});
|
|
52
|
+
// Sort by character code for consistency
|
|
53
|
+
charCodeReport.sort((a, b) => {
|
|
54
|
+
const codeA = parseInt(a.split(':')[0]);
|
|
55
|
+
const codeB = parseInt(b.split(':')[0]);
|
|
56
|
+
return codeA - codeB;
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
report: charCodeReport.join(','),
|
|
60
|
+
uniqueCount: characterCodes.size,
|
|
61
|
+
diffLength: fullDiff.length
|
|
62
|
+
};
|
|
63
|
+
}
|
|
11
64
|
export async function performSearchReplace(filePath, block, expectedReplacements = 1) {
|
|
12
65
|
// Check for empty search string to prevent infinite loops
|
|
13
66
|
if (block.search === "") {
|
|
@@ -28,13 +81,17 @@ export async function performSearchReplace(filePath, block, expectedReplacements
|
|
|
28
81
|
if (typeof content !== 'string') {
|
|
29
82
|
throw new Error('Wrong content for file ' + filePath);
|
|
30
83
|
}
|
|
84
|
+
// Detect file's line ending style
|
|
85
|
+
const fileLineEnding = detectLineEnding(content);
|
|
86
|
+
// Normalize search string to match file's line endings
|
|
87
|
+
const normalizedSearch = normalizeLineEndings(block.search, fileLineEnding);
|
|
31
88
|
// First try exact match
|
|
32
89
|
let tempContent = content;
|
|
33
90
|
let count = 0;
|
|
34
|
-
let pos = tempContent.indexOf(
|
|
91
|
+
let pos = tempContent.indexOf(normalizedSearch);
|
|
35
92
|
while (pos !== -1) {
|
|
36
93
|
count++;
|
|
37
|
-
pos = tempContent.indexOf(
|
|
94
|
+
pos = tempContent.indexOf(normalizedSearch, pos + 1);
|
|
38
95
|
}
|
|
39
96
|
// If exact match found and count matches expected replacements, proceed with exact replacement
|
|
40
97
|
if (count > 0 && count === expectedReplacements) {
|
|
@@ -42,15 +99,15 @@ export async function performSearchReplace(filePath, block, expectedReplacements
|
|
|
42
99
|
let newContent = content;
|
|
43
100
|
// If we're only replacing one occurrence, replace it directly
|
|
44
101
|
if (expectedReplacements === 1) {
|
|
45
|
-
const searchIndex = newContent.indexOf(
|
|
102
|
+
const searchIndex = newContent.indexOf(normalizedSearch);
|
|
46
103
|
newContent =
|
|
47
104
|
newContent.substring(0, searchIndex) +
|
|
48
|
-
block.replace +
|
|
49
|
-
newContent.substring(searchIndex +
|
|
105
|
+
normalizeLineEndings(block.replace, fileLineEnding) +
|
|
106
|
+
newContent.substring(searchIndex + normalizedSearch.length);
|
|
50
107
|
}
|
|
51
108
|
else {
|
|
52
109
|
// Replace all occurrences using split and join for multiple replacements
|
|
53
|
-
newContent = newContent.split(
|
|
110
|
+
newContent = newContent.split(normalizedSearch).join(normalizeLineEndings(block.replace, fileLineEnding));
|
|
54
111
|
}
|
|
55
112
|
await writeFile(filePath, newContent);
|
|
56
113
|
return {
|
|
@@ -81,19 +138,26 @@ export async function performSearchReplace(filePath, block, expectedReplacements
|
|
|
81
138
|
const similarity = getSimilarityRatio(block.search, fuzzyResult.value);
|
|
82
139
|
// Calculate execution time in milliseconds
|
|
83
140
|
const executionTime = performance.now() - startTime;
|
|
141
|
+
// Generate diff and gather character code data
|
|
142
|
+
const diff = highlightDifferences(block.search, fuzzyResult.value);
|
|
143
|
+
// Count character codes in diff
|
|
144
|
+
const characterCodeData = getCharacterCodeData(block.search, fuzzyResult.value);
|
|
145
|
+
// Combine all fuzzy search data for single capture
|
|
146
|
+
const fuzzySearchData = {
|
|
147
|
+
similarity: similarity,
|
|
148
|
+
execution_time_ms: executionTime,
|
|
149
|
+
search_length: block.search.length,
|
|
150
|
+
file_size: content.length,
|
|
151
|
+
threshold: FUZZY_THRESHOLD,
|
|
152
|
+
found_text_length: fuzzyResult.value.length,
|
|
153
|
+
character_codes: characterCodeData.report,
|
|
154
|
+
unique_character_count: characterCodeData.uniqueCount,
|
|
155
|
+
total_diff_length: characterCodeData.diffLength
|
|
156
|
+
};
|
|
84
157
|
// Check if the fuzzy match is "close enough"
|
|
85
158
|
if (similarity >= FUZZY_THRESHOLD) {
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
// Capture the fuzzy search event
|
|
89
|
-
capture('server_fuzzy_search_performed', {
|
|
90
|
-
similarity: similarity,
|
|
91
|
-
execution_time_ms: executionTime,
|
|
92
|
-
search_length: block.search.length,
|
|
93
|
-
file_size: content.length,
|
|
94
|
-
threshold: FUZZY_THRESHOLD,
|
|
95
|
-
found_text_length: fuzzyResult.value.length
|
|
96
|
-
});
|
|
159
|
+
// Capture the fuzzy search event with all data
|
|
160
|
+
capture('server_fuzzy_search_performed', fuzzySearchData);
|
|
97
161
|
// If we allow fuzzy matches, we would make the replacement here
|
|
98
162
|
// For now, we'll return a detailed message about the fuzzy match
|
|
99
163
|
return {
|
|
@@ -107,14 +171,9 @@ export async function performSearchReplace(filePath, block, expectedReplacements
|
|
|
107
171
|
}
|
|
108
172
|
else {
|
|
109
173
|
// If the fuzzy match isn't close enough
|
|
110
|
-
// Still capture the fuzzy search event
|
|
174
|
+
// Still capture the fuzzy search event with all data
|
|
111
175
|
capture('server_fuzzy_search_performed', {
|
|
112
|
-
|
|
113
|
-
execution_time_ms: executionTime,
|
|
114
|
-
search_length: block.search.length,
|
|
115
|
-
file_size: content.length,
|
|
116
|
-
threshold: FUZZY_THRESHOLD,
|
|
117
|
-
found_text_length: fuzzyResult.value.length,
|
|
176
|
+
...fuzzySearchData,
|
|
118
177
|
below_threshold: true
|
|
119
178
|
});
|
|
120
179
|
return {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { terminalManager } from '../terminal-manager.js';
|
|
2
|
+
import { ReadOutputArgsSchema } from './schemas.js';
|
|
3
|
+
|
|
4
|
+
export async function readOutput(args) {
|
|
5
|
+
const parsed = ReadOutputArgsSchema.safeParse(args);
|
|
6
|
+
if (!parsed.success) {
|
|
7
|
+
return {
|
|
8
|
+
content: [{ type: "text", text: `Error: Invalid arguments for read_output: ${parsed.error}` }],
|
|
9
|
+
isError: true,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { pid, timeout_ms = 5000 } = parsed.data;
|
|
14
|
+
|
|
15
|
+
// Check if the process exists
|
|
16
|
+
const session = terminalManager.getSession(pid);
|
|
17
|
+
if (!session) {
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: "text", text: `No session found for PID ${pid}` }],
|
|
20
|
+
isError: true,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Wait for output with timeout
|
|
25
|
+
let output = "";
|
|
26
|
+
let timeoutReached = false;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// Create a promise that resolves when new output is available or when timeout is reached
|
|
30
|
+
const outputPromise = new Promise((resolve) => {
|
|
31
|
+
// Check for initial output
|
|
32
|
+
const initialOutput = terminalManager.getNewOutput(pid);
|
|
33
|
+
if (initialOutput && initialOutput.length > 0) {
|
|
34
|
+
resolve(initialOutput);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Setup an interval to poll for output
|
|
39
|
+
const interval = setInterval(() => {
|
|
40
|
+
const newOutput = terminalManager.getNewOutput(pid);
|
|
41
|
+
if (newOutput && newOutput.length > 0) {
|
|
42
|
+
clearInterval(interval);
|
|
43
|
+
resolve(newOutput);
|
|
44
|
+
}
|
|
45
|
+
}, 100); // Check every 100ms
|
|
46
|
+
|
|
47
|
+
// Set a timeout to stop waiting
|
|
48
|
+
setTimeout(() => {
|
|
49
|
+
clearInterval(interval);
|
|
50
|
+
timeoutReached = true;
|
|
51
|
+
resolve(terminalManager.getNewOutput(pid) || "");
|
|
52
|
+
}, timeout_ms);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
output = await outputPromise;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: "text", text: `Error reading output: ${error}` }],
|
|
59
|
+
isError: true,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
content: [{
|
|
65
|
+
type: "text",
|
|
66
|
+
text: output || 'No new output available' + (timeoutReached ? ' (timeout reached)' : '')
|
|
67
|
+
}],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { terminalManager } from '../terminal-manager.js';
|
|
2
|
+
import { SendInputArgsSchema } from './schemas.js';
|
|
3
|
+
import { capture } from "../utils/capture.js";
|
|
4
|
+
|
|
5
|
+
export async function sendInput(args) {
|
|
6
|
+
const parsed = SendInputArgsSchema.safeParse(args);
|
|
7
|
+
if (!parsed.success) {
|
|
8
|
+
capture('server_send_input_failed', {
|
|
9
|
+
error: 'Invalid arguments'
|
|
10
|
+
});
|
|
11
|
+
return {
|
|
12
|
+
content: [{ type: "text", text: `Error: Invalid arguments for send_input: ${parsed.error}` }],
|
|
13
|
+
isError: true,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { pid, input, timeout_ms = 5000, wait_for_prompt = false } = parsed.data;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
capture('server_send_input', {
|
|
21
|
+
pid: pid,
|
|
22
|
+
inputLength: input.length
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Try to send input to the process
|
|
26
|
+
const success = terminalManager.sendInputToProcess(pid, input);
|
|
27
|
+
|
|
28
|
+
if (!success) {
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: "text", text: `Error: Failed to send input to process ${pid}. The process may have exited or doesn't accept input.` }],
|
|
31
|
+
isError: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// If we don't need to wait for output, return immediately
|
|
36
|
+
if (!wait_for_prompt) {
|
|
37
|
+
return {
|
|
38
|
+
content: [{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: `Successfully sent input to process ${pid}. Use read_output to get the process response.`
|
|
41
|
+
}],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Wait for output with timeout
|
|
46
|
+
let output = "";
|
|
47
|
+
let timeoutReached = false;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// Create a promise that resolves when new output is available or when timeout is reached
|
|
51
|
+
const outputPromise = new Promise((resolve) => {
|
|
52
|
+
// Setup an interval to poll for output
|
|
53
|
+
const interval = setInterval(() => {
|
|
54
|
+
const newOutput = terminalManager.getNewOutput(pid);
|
|
55
|
+
|
|
56
|
+
if (newOutput && newOutput.length > 0) {
|
|
57
|
+
output += newOutput;
|
|
58
|
+
|
|
59
|
+
// Check if output contains a prompt pattern (indicating the REPL is ready for more input)
|
|
60
|
+
const promptPatterns = [/^>\s*$/, /^>>>\s*$/, /^\.{3}\s*$/]; // Common REPL prompts
|
|
61
|
+
const lines = output.split('\n');
|
|
62
|
+
const lastLine = lines[lines.length - 1];
|
|
63
|
+
const hasPrompt = promptPatterns.some(pattern => pattern.test(lastLine.trim()));
|
|
64
|
+
|
|
65
|
+
if (hasPrompt) {
|
|
66
|
+
clearInterval(interval);
|
|
67
|
+
resolve(output);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}, 100); // Check every 100ms
|
|
71
|
+
|
|
72
|
+
// Set a timeout to stop waiting
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
clearInterval(interval);
|
|
75
|
+
timeoutReached = true;
|
|
76
|
+
|
|
77
|
+
// Get any final output
|
|
78
|
+
const finalOutput = terminalManager.getNewOutput(pid);
|
|
79
|
+
if (finalOutput) {
|
|
80
|
+
output += finalOutput;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
resolve(output);
|
|
84
|
+
}, timeout_ms);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await outputPromise;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
return {
|
|
90
|
+
content: [{ type: "text", text: `Error reading output after sending input: ${error}` }],
|
|
91
|
+
isError: true,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
content: [{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: `Input sent to process ${pid}.\n\nOutput received:\n${output || '(No output)'}${timeoutReached ? ' (timeout reached)' : ''}`
|
|
99
|
+
}],
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
103
|
+
capture('server_send_input_error', {
|
|
104
|
+
error: errorMessage
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
content: [{ type: "text", text: `Error sending input: ${errorMessage}` }],
|
|
108
|
+
isError: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
package/dist/tools/filesystem.js
CHANGED
|
@@ -256,7 +256,8 @@ export async function readFileFromDisk(filePath) {
|
|
|
256
256
|
else {
|
|
257
257
|
// For all other files, try to read as UTF-8 text
|
|
258
258
|
try {
|
|
259
|
-
const
|
|
259
|
+
const buffer = await fs.readFile(validPath);
|
|
260
|
+
const content = buffer.toString('utf-8');
|
|
260
261
|
return { content, mimeType, isImage };
|
|
261
262
|
}
|
|
262
263
|
catch (error) {
|
|
@@ -293,7 +294,7 @@ export async function writeFile(filePath, content) {
|
|
|
293
294
|
const fileExtension = path.extname(validPath).toLowerCase();
|
|
294
295
|
// Capture file extension in telemetry without capturing the file path
|
|
295
296
|
capture('server_write_file', { fileExtension: fileExtension });
|
|
296
|
-
await fs.writeFile(validPath, content
|
|
297
|
+
await fs.writeFile(validPath, content);
|
|
297
298
|
}
|
|
298
299
|
export async function readMultipleFiles(paths) {
|
|
299
300
|
return Promise.all(paths.map(async (filePath) => {
|
|
@@ -339,9 +340,9 @@ export async function searchFiles(rootPath, pattern) {
|
|
|
339
340
|
}
|
|
340
341
|
catch (error) {
|
|
341
342
|
// Only sanitize for telemetry, not for the returned error
|
|
342
|
-
capture('
|
|
343
|
+
capture('server_search_read_dir_error', {
|
|
343
344
|
errorType: error instanceof Error ? error.name : 'Unknown',
|
|
344
|
-
|
|
345
|
+
error: 'Error reading directory',
|
|
345
346
|
isReadDirError: true
|
|
346
347
|
});
|
|
347
348
|
return; // Skip this directory on error
|
|
@@ -377,7 +378,7 @@ export async function searchFiles(rootPath, pattern) {
|
|
|
377
378
|
// For telemetry only - sanitize error info
|
|
378
379
|
capture('server_search_files_error', {
|
|
379
380
|
errorType: error instanceof Error ? error.name : 'Unknown',
|
|
380
|
-
|
|
381
|
+
error: 'Error with root path',
|
|
381
382
|
isRootPathError: true
|
|
382
383
|
});
|
|
383
384
|
// Re-throw the original error for the caller
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ServerResult } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create a new REPL session
|
|
4
|
+
*/
|
|
5
|
+
export declare function createREPLSession(args: unknown): Promise<ServerResult>;
|
|
6
|
+
/**
|
|
7
|
+
* Execute code in a REPL session
|
|
8
|
+
*/
|
|
9
|
+
export declare function executeREPLCode(args: unknown): Promise<ServerResult>;
|
|
10
|
+
/**
|
|
11
|
+
* Terminate a REPL session
|
|
12
|
+
*/
|
|
13
|
+
export declare function terminateREPLSession(args: unknown): Promise<ServerResult>;
|
|
14
|
+
/**
|
|
15
|
+
* List all active REPL sessions
|
|
16
|
+
*/
|
|
17
|
+
export declare function listREPLSessions(args: unknown): Promise<ServerResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Get information about a specific REPL session
|
|
20
|
+
*/
|
|
21
|
+
export declare function getREPLSessionInfo(args: unknown): Promise<ServerResult>;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { replManager } from '../repl-manager.js';
|
|
2
|
+
import { capture } from '../utils/capture.js';
|
|
3
|
+
import { CreateREPLSessionArgsSchema, ExecuteREPLCodeArgsSchema, TerminateREPLSessionArgsSchema, ListREPLSessionsArgsSchema, GetREPLSessionInfoArgsSchema } from './schemas.js';
|
|
4
|
+
/**
|
|
5
|
+
* Create a new REPL session
|
|
6
|
+
*/
|
|
7
|
+
export async function createREPLSession(args) {
|
|
8
|
+
const parsed = CreateREPLSessionArgsSchema.safeParse(args);
|
|
9
|
+
if (!parsed.success) {
|
|
10
|
+
capture('server_create_repl_session_failed', {
|
|
11
|
+
error: 'Invalid arguments'
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: "text", text: `Error: Invalid arguments for create_repl_session: ${parsed.error}` }],
|
|
15
|
+
isError: true,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const pid = await replManager.createSession(parsed.data.language, parsed.data.timeout);
|
|
20
|
+
return {
|
|
21
|
+
content: [{
|
|
22
|
+
type: "text",
|
|
23
|
+
text: `Successfully created ${parsed.data.language} REPL session with PID ${pid}`
|
|
24
|
+
}],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
29
|
+
capture('server_create_repl_session_error', {
|
|
30
|
+
error: errorMessage
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: `Error creating REPL session: ${errorMessage}` }],
|
|
34
|
+
isError: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Execute code in a REPL session
|
|
40
|
+
*/
|
|
41
|
+
export async function executeREPLCode(args) {
|
|
42
|
+
const parsed = ExecuteREPLCodeArgsSchema.safeParse(args);
|
|
43
|
+
if (!parsed.success) {
|
|
44
|
+
capture('server_execute_repl_code_failed', {
|
|
45
|
+
error: 'Invalid arguments'
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
content: [{ type: "text", text: `Error: Invalid arguments for execute_repl_code: ${parsed.error}` }],
|
|
49
|
+
isError: true,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const result = await replManager.executeCode(parsed.data.pid, parsed.data.code, {
|
|
54
|
+
timeout: parsed.data.timeout,
|
|
55
|
+
waitForPrompt: parsed.data.waitForPrompt,
|
|
56
|
+
ignoreErrors: parsed.data.ignoreErrors,
|
|
57
|
+
multiline: parsed.data.multiline
|
|
58
|
+
});
|
|
59
|
+
if (!result.success) {
|
|
60
|
+
return {
|
|
61
|
+
content: [{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: result.error
|
|
64
|
+
? `Error executing code: ${result.error}`
|
|
65
|
+
: `Failed to execute code in REPL session ${parsed.data.pid}` +
|
|
66
|
+
(result.timeout ? ` (timed out)` : '') +
|
|
67
|
+
(result.output ? `\nOutput received: ${result.output}` : '')
|
|
68
|
+
}],
|
|
69
|
+
isError: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
content: [{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: `Code executed in REPL session ${parsed.data.pid}` +
|
|
76
|
+
(result.timeout ? ` (timed out, partial output below)` : '') +
|
|
77
|
+
`\n\nOutput:\n${result.output || '(No output)'}`
|
|
78
|
+
}],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
83
|
+
capture('server_execute_repl_code_error', {
|
|
84
|
+
error: errorMessage
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
content: [{ type: "text", text: `Error executing REPL code: ${errorMessage}` }],
|
|
88
|
+
isError: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Terminate a REPL session
|
|
94
|
+
*/
|
|
95
|
+
export async function terminateREPLSession(args) {
|
|
96
|
+
const parsed = TerminateREPLSessionArgsSchema.safeParse(args);
|
|
97
|
+
if (!parsed.success) {
|
|
98
|
+
capture('server_terminate_repl_session_failed', {
|
|
99
|
+
error: 'Invalid arguments'
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
content: [{ type: "text", text: `Error: Invalid arguments for terminate_repl_session: ${parsed.error}` }],
|
|
103
|
+
isError: true,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const success = await replManager.terminateSession(parsed.data.pid);
|
|
108
|
+
if (!success) {
|
|
109
|
+
return {
|
|
110
|
+
content: [{ type: "text", text: `Failed to terminate REPL session ${parsed.data.pid}. The session may have already ended.` }],
|
|
111
|
+
isError: true,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
content: [{
|
|
116
|
+
type: "text",
|
|
117
|
+
text: `Successfully terminated REPL session ${parsed.data.pid}`
|
|
118
|
+
}],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
123
|
+
capture('server_terminate_repl_session_error', {
|
|
124
|
+
error: errorMessage
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
content: [{ type: "text", text: `Error terminating REPL session: ${errorMessage}` }],
|
|
128
|
+
isError: true,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* List all active REPL sessions
|
|
134
|
+
*/
|
|
135
|
+
export async function listREPLSessions(args) {
|
|
136
|
+
const parsed = ListREPLSessionsArgsSchema.safeParse(args);
|
|
137
|
+
if (!parsed.success) {
|
|
138
|
+
capture('server_list_repl_sessions_failed', {
|
|
139
|
+
error: 'Invalid arguments'
|
|
140
|
+
});
|
|
141
|
+
return {
|
|
142
|
+
content: [{ type: "text", text: `Error: Invalid arguments for list_repl_sessions: ${parsed.error}` }],
|
|
143
|
+
isError: true,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const sessions = replManager.listSessions();
|
|
148
|
+
if (sessions.length === 0) {
|
|
149
|
+
return {
|
|
150
|
+
content: [{
|
|
151
|
+
type: "text",
|
|
152
|
+
text: `No active REPL sessions found`
|
|
153
|
+
}],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const sessionsText = sessions.map(s => `PID: ${s.pid}, Language: ${s.language}, Runtime: ${Math.round(s.runtime / 1000)}s`).join('\n');
|
|
157
|
+
return {
|
|
158
|
+
content: [{
|
|
159
|
+
type: "text",
|
|
160
|
+
text: `Active REPL sessions:\n${sessionsText}`
|
|
161
|
+
}],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
166
|
+
capture('server_list_repl_sessions_error', {
|
|
167
|
+
error: errorMessage
|
|
168
|
+
});
|
|
169
|
+
return {
|
|
170
|
+
content: [{ type: "text", text: `Error listing REPL sessions: ${errorMessage}` }],
|
|
171
|
+
isError: true,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Get information about a specific REPL session
|
|
177
|
+
*/
|
|
178
|
+
export async function getREPLSessionInfo(args) {
|
|
179
|
+
const parsed = GetREPLSessionInfoArgsSchema.safeParse(args);
|
|
180
|
+
if (!parsed.success) {
|
|
181
|
+
capture('server_get_repl_session_info_failed', {
|
|
182
|
+
error: 'Invalid arguments'
|
|
183
|
+
});
|
|
184
|
+
return {
|
|
185
|
+
content: [{ type: "text", text: `Error: Invalid arguments for get_repl_session_info: ${parsed.error}` }],
|
|
186
|
+
isError: true,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const info = replManager.getSessionInfo(parsed.data.pid);
|
|
191
|
+
if (!info) {
|
|
192
|
+
return {
|
|
193
|
+
content: [{
|
|
194
|
+
type: "text",
|
|
195
|
+
text: `No active REPL session found with PID ${parsed.data.pid}`
|
|
196
|
+
}],
|
|
197
|
+
isError: true,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
content: [{
|
|
202
|
+
type: "text",
|
|
203
|
+
text: `REPL Session ${parsed.data.pid}:\nLanguage: ${info.language}\nRuntime: ${Math.round(info.runtime / 1000)}s`
|
|
204
|
+
}],
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
209
|
+
capture('server_get_repl_session_info_error', {
|
|
210
|
+
error: errorMessage
|
|
211
|
+
});
|
|
212
|
+
return {
|
|
213
|
+
content: [{ type: "text", text: `Error getting REPL session info: ${errorMessage}` }],
|
|
214
|
+
isError: true,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { terminalManager } from '../terminal-manager.js';
|
|
2
|
+
import { SendInputArgsSchema } from './schemas.js';
|
|
3
|
+
import { capture } from "../utils/capture.js";
|
|
4
|
+
export async function sendInput(args) {
|
|
5
|
+
const parsed = SendInputArgsSchema.safeParse(args);
|
|
6
|
+
if (!parsed.success) {
|
|
7
|
+
capture('server_send_input_failed', {
|
|
8
|
+
error: 'Invalid arguments'
|
|
9
|
+
});
|
|
10
|
+
return {
|
|
11
|
+
content: [{ type: "text", text: `Error: Invalid arguments for send_input: ${parsed.error}` }],
|
|
12
|
+
isError: true,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
capture('server_send_input', {
|
|
17
|
+
pid: parsed.data.pid,
|
|
18
|
+
inputLength: parsed.data.input.length
|
|
19
|
+
});
|
|
20
|
+
// Try to send input to the process
|
|
21
|
+
const success = terminalManager.sendInputToProcess(parsed.data.pid, parsed.data.input);
|
|
22
|
+
if (!success) {
|
|
23
|
+
return {
|
|
24
|
+
content: [{ type: "text", text: `Error: Failed to send input to process ${parsed.data.pid}. The process may have exited or doesn't accept input.` }],
|
|
25
|
+
isError: true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
content: [{
|
|
30
|
+
type: "text",
|
|
31
|
+
text: `Successfully sent input to process ${parsed.data.pid}. Use read_output to get the process response.`
|
|
32
|
+
}],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
37
|
+
capture('server_send_input_error', {
|
|
38
|
+
error: errorMessage
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: `Error sending input: ${errorMessage}` }],
|
|
42
|
+
isError: true,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|