@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.
Files changed (36) hide show
  1. package/README.md +1 -1
  2. package/dist/REPLSessionManager.d.ts +109 -0
  3. package/dist/REPLSessionManager.js +364 -0
  4. package/dist/REPLSessionManager.test.d.ts +1 -0
  5. package/dist/REPLSessionManager.test.js +75 -0
  6. package/dist/client/replClient.d.ts +63 -0
  7. package/dist/client/replClient.js +217 -0
  8. package/dist/client/sshClient.d.ts +82 -0
  9. package/dist/client/sshClient.js +200 -0
  10. package/dist/handlers/repl-handlers.d.ts +21 -0
  11. package/dist/handlers/repl-handlers.js +37 -0
  12. package/dist/handlers/replCommandHandler.d.ts +125 -0
  13. package/dist/handlers/replCommandHandler.js +255 -0
  14. package/dist/handlers/replCommandHandler.test.d.ts +1 -0
  15. package/dist/handlers/replCommandHandler.test.js +103 -0
  16. package/dist/index.js +1 -2
  17. package/dist/repl-manager.d.ts +73 -0
  18. package/dist/repl-manager.js +407 -0
  19. package/dist/replIntegration.d.ts +14 -0
  20. package/dist/replIntegration.js +27 -0
  21. package/dist/setup-claude-server.js +14 -15
  22. package/dist/tools/edit.js +83 -24
  23. package/dist/tools/enhanced-read-output.js +69 -0
  24. package/dist/tools/enhanced-send-input.js +111 -0
  25. package/dist/tools/filesystem.js +6 -5
  26. package/dist/tools/repl.d.ts +21 -0
  27. package/dist/tools/repl.js +217 -0
  28. package/dist/tools/send-input.d.ts +2 -0
  29. package/dist/tools/send-input.js +45 -0
  30. package/dist/utils/lineEndingHandler.d.ts +21 -0
  31. package/dist/utils/lineEndingHandler.js +77 -0
  32. package/dist/utils/lineEndingHandler_optimized.d.ts +21 -0
  33. package/dist/utils/lineEndingHandler_optimized.js +77 -0
  34. package/dist/version.d.ts +1 -1
  35. package/dist/version.js +1 -1
  36. package/package.json +1 -1
@@ -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(block.search);
91
+ let pos = tempContent.indexOf(normalizedSearch);
35
92
  while (pos !== -1) {
36
93
  count++;
37
- pos = tempContent.indexOf(block.search, pos + 1);
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(block.search);
102
+ const searchIndex = newContent.indexOf(normalizedSearch);
46
103
  newContent =
47
104
  newContent.substring(0, searchIndex) +
48
- block.replace +
49
- newContent.substring(searchIndex + block.search.length);
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(block.search).join(block.replace);
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
- // Format differences for clearer output
87
- const diff = highlightDifferences(block.search, fuzzyResult.value);
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 even for unsuccessful matches
174
+ // Still capture the fuzzy search event with all data
111
175
  capture('server_fuzzy_search_performed', {
112
- similarity: similarity,
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
+ }
@@ -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 content = await fs.readFile(validPath, "utf-8");
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, "utf-8");
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('server_search_files_error', {
343
+ capture('server_search_read_dir_error', {
343
344
  errorType: error instanceof Error ? error.name : 'Unknown',
344
- errorMessage: 'Error reading directory',
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
- errorMessage: 'Error with root path',
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,2 @@
1
+ import { ServerResult } from '../types.js';
2
+ export declare function sendInput(args: unknown): Promise<ServerResult>;
@@ -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
+ }