@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
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { REPLSessionManager } from '../REPLSessionManager.js';
|
|
2
|
+
let replManager = null;
|
|
3
|
+
/**
|
|
4
|
+
* Initialize the REPL manager with a terminal manager instance
|
|
5
|
+
* @param terminalManager - The terminal manager instance
|
|
6
|
+
*/
|
|
7
|
+
export function initializeREPLManager(terminalManager) {
|
|
8
|
+
if (!replManager) {
|
|
9
|
+
replManager = new REPLSessionManager(terminalManager);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Command handler for REPL-related operations
|
|
14
|
+
*/
|
|
15
|
+
export const replCommandHandler = {
|
|
16
|
+
/**
|
|
17
|
+
* Create a new REPL session
|
|
18
|
+
* @param params - Command parameters
|
|
19
|
+
* @returns Result object with session PID
|
|
20
|
+
*/
|
|
21
|
+
createREPLSession: async (params) => {
|
|
22
|
+
try {
|
|
23
|
+
if (!replManager) {
|
|
24
|
+
return {
|
|
25
|
+
success: false,
|
|
26
|
+
error: "REPL Manager not initialized"
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const language = params.language;
|
|
30
|
+
if (!language) {
|
|
31
|
+
return {
|
|
32
|
+
success: false,
|
|
33
|
+
error: "Language parameter is required"
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const pid = await replManager.createSession(language, params.options || {});
|
|
37
|
+
return {
|
|
38
|
+
success: true,
|
|
39
|
+
pid,
|
|
40
|
+
message: `${language} REPL session started with PID ${pid}`
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
error: error.message || "Failed to create REPL session"
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
/**
|
|
51
|
+
* Execute code in an existing REPL session
|
|
52
|
+
* @param params - Command parameters
|
|
53
|
+
* @returns Result with execution output
|
|
54
|
+
*/
|
|
55
|
+
executeREPLCode: async (params) => {
|
|
56
|
+
try {
|
|
57
|
+
if (!replManager) {
|
|
58
|
+
return {
|
|
59
|
+
success: false,
|
|
60
|
+
error: "REPL Manager not initialized"
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const { pid, code } = params;
|
|
64
|
+
if (!pid || !code) {
|
|
65
|
+
return {
|
|
66
|
+
success: false,
|
|
67
|
+
error: "Both pid and code parameters are required"
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const result = await replManager.executeCode(typeof pid === 'string' ? parseInt(pid) : pid, code, params.options || {});
|
|
71
|
+
return {
|
|
72
|
+
success: true,
|
|
73
|
+
...result
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
error: error.message || "Failed to execute code in REPL session"
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
/**
|
|
84
|
+
* Create a new SSH session
|
|
85
|
+
* @param params - Command parameters
|
|
86
|
+
* @returns Result object with session PID
|
|
87
|
+
*/
|
|
88
|
+
createSSHSession: async (params) => {
|
|
89
|
+
try {
|
|
90
|
+
if (!replManager) {
|
|
91
|
+
return {
|
|
92
|
+
success: false,
|
|
93
|
+
error: "REPL Manager not initialized"
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const { host, username, port, identity, password } = params;
|
|
97
|
+
if (!host) {
|
|
98
|
+
return {
|
|
99
|
+
success: false,
|
|
100
|
+
error: "Host parameter is required"
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const options = {
|
|
104
|
+
username,
|
|
105
|
+
port,
|
|
106
|
+
identity,
|
|
107
|
+
password,
|
|
108
|
+
...(params.options || {})
|
|
109
|
+
};
|
|
110
|
+
const pid = await replManager.createSSHSession(host, options);
|
|
111
|
+
return {
|
|
112
|
+
success: true,
|
|
113
|
+
pid,
|
|
114
|
+
message: `SSH session to ${host} started with PID ${pid}`
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
return {
|
|
119
|
+
success: false,
|
|
120
|
+
error: error.message || "Failed to create SSH session"
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
/**
|
|
125
|
+
* Execute a command in an SSH session
|
|
126
|
+
* @param params - Command parameters
|
|
127
|
+
* @returns Result with execution output
|
|
128
|
+
*/
|
|
129
|
+
executeSSHCommand: async (params) => {
|
|
130
|
+
try {
|
|
131
|
+
if (!replManager) {
|
|
132
|
+
return {
|
|
133
|
+
success: false,
|
|
134
|
+
error: "REPL Manager not initialized"
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const { pid, command } = params;
|
|
138
|
+
if (!pid || !command) {
|
|
139
|
+
return {
|
|
140
|
+
success: false,
|
|
141
|
+
error: "Both pid and command parameters are required"
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
// For SSH, we use the same sendAndReadREPL method but specify 'ssh' as the language
|
|
145
|
+
const parsedPid = typeof pid === 'string' ? parseInt(pid) : pid;
|
|
146
|
+
const session = replManager.listSessions().find(s => s.pid === parsedPid);
|
|
147
|
+
if (!session || session.language !== 'ssh') {
|
|
148
|
+
return {
|
|
149
|
+
success: false,
|
|
150
|
+
error: "Invalid SSH session PID"
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const result = await replManager.sendAndReadREPL(parsedPid, command, 'ssh', params.options?.timeout || 10000);
|
|
154
|
+
return {
|
|
155
|
+
success: true,
|
|
156
|
+
...result
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
return {
|
|
161
|
+
success: false,
|
|
162
|
+
error: error.message || "Failed to execute command in SSH session"
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
/**
|
|
167
|
+
* List all active REPL sessions
|
|
168
|
+
* @returns List of active sessions
|
|
169
|
+
*/
|
|
170
|
+
listREPLSessions: () => {
|
|
171
|
+
try {
|
|
172
|
+
if (!replManager) {
|
|
173
|
+
return {
|
|
174
|
+
success: false,
|
|
175
|
+
error: "REPL Manager not initialized"
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const sessions = replManager.listSessions();
|
|
179
|
+
return {
|
|
180
|
+
success: true,
|
|
181
|
+
sessions
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
return {
|
|
186
|
+
success: false,
|
|
187
|
+
error: error.message || "Failed to list REPL sessions"
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
/**
|
|
192
|
+
* Close a specific REPL session
|
|
193
|
+
* @param params - Command parameters
|
|
194
|
+
* @returns Result indicating success
|
|
195
|
+
*/
|
|
196
|
+
closeREPLSession: async (params) => {
|
|
197
|
+
try {
|
|
198
|
+
if (!replManager) {
|
|
199
|
+
return {
|
|
200
|
+
success: false,
|
|
201
|
+
error: "REPL Manager not initialized"
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const { pid } = params;
|
|
205
|
+
if (!pid) {
|
|
206
|
+
return {
|
|
207
|
+
success: false,
|
|
208
|
+
error: "PID parameter is required"
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const parsedPid = typeof pid === 'string' ? parseInt(pid) : pid;
|
|
212
|
+
const success = await replManager.closeSession(parsedPid);
|
|
213
|
+
return {
|
|
214
|
+
success,
|
|
215
|
+
message: success
|
|
216
|
+
? `REPL session with PID ${pid} closed successfully`
|
|
217
|
+
: `Failed to close REPL session with PID ${pid}`
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
return {
|
|
222
|
+
success: false,
|
|
223
|
+
error: error.message || "Failed to close REPL session"
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
/**
|
|
228
|
+
* Close all idle REPL sessions
|
|
229
|
+
* @param params - Command parameters
|
|
230
|
+
* @returns Result with number of closed sessions
|
|
231
|
+
*/
|
|
232
|
+
closeIdleREPLSessions: async (params) => {
|
|
233
|
+
try {
|
|
234
|
+
if (!replManager) {
|
|
235
|
+
return {
|
|
236
|
+
success: false,
|
|
237
|
+
error: "REPL Manager not initialized"
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const maxIdleMs = params?.maxIdleMs || 30 * 60 * 1000; // Default 30 minutes
|
|
241
|
+
const closedCount = await replManager.closeIdleSessions(maxIdleMs);
|
|
242
|
+
return {
|
|
243
|
+
success: true,
|
|
244
|
+
closedCount,
|
|
245
|
+
message: `Closed ${closedCount} idle REPL session(s)`
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
return {
|
|
250
|
+
success: false,
|
|
251
|
+
error: error.message || "Failed to close idle REPL sessions"
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import { describe, it, beforeEach, afterEach } from 'mocha';
|
|
3
|
+
import * as sinon from 'sinon';
|
|
4
|
+
import { initializeREPLManager, replCommandHandler } from './replCommandHandler';
|
|
5
|
+
describe('REPL Command Handler', () => {
|
|
6
|
+
let mockSessionManager;
|
|
7
|
+
let sandbox;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
// Create a sinon sandbox for test isolation
|
|
10
|
+
sandbox = sinon.createSandbox();
|
|
11
|
+
// Create mock for REPLSessionManager
|
|
12
|
+
mockSessionManager = {
|
|
13
|
+
createSession: sandbox.stub().resolves(12345),
|
|
14
|
+
executeCode: sandbox.stub().resolves({
|
|
15
|
+
success: true,
|
|
16
|
+
output: 'test output'
|
|
17
|
+
}),
|
|
18
|
+
createSSHSession: sandbox.stub().resolves(54321),
|
|
19
|
+
sendAndReadREPL: sandbox.stub().resolves({
|
|
20
|
+
success: true,
|
|
21
|
+
output: 'ssh output'
|
|
22
|
+
}),
|
|
23
|
+
listSessions: sandbox.stub().returns([
|
|
24
|
+
{ pid: 12345, language: 'node' },
|
|
25
|
+
{ pid: 54321, language: 'ssh' }
|
|
26
|
+
]),
|
|
27
|
+
closeSession: sandbox.stub().resolves(true),
|
|
28
|
+
closeIdleSessions: sandbox.stub().resolves(2)
|
|
29
|
+
};
|
|
30
|
+
// Create a mock terminal manager
|
|
31
|
+
const mockTerminalManager = {};
|
|
32
|
+
// Use a more direct approach to set up the testing environment
|
|
33
|
+
// Override the module's private replManager variable with our mock
|
|
34
|
+
global.replManager = mockSessionManager;
|
|
35
|
+
initializeREPLManager(mockTerminalManager);
|
|
36
|
+
});
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
// Restore all stubbed methods
|
|
39
|
+
sandbox.restore();
|
|
40
|
+
// Reset the module state
|
|
41
|
+
global.replManager = null;
|
|
42
|
+
});
|
|
43
|
+
it('should create a REPL session', async () => {
|
|
44
|
+
const result = await replCommandHandler.createREPLSession({
|
|
45
|
+
language: 'node'
|
|
46
|
+
});
|
|
47
|
+
expect(result.success).to.be.true;
|
|
48
|
+
expect(result.pid).to.equal(12345);
|
|
49
|
+
});
|
|
50
|
+
it('should execute code in a REPL session', async () => {
|
|
51
|
+
const result = await replCommandHandler.executeREPLCode({
|
|
52
|
+
pid: 12345,
|
|
53
|
+
code: 'console.log("test")'
|
|
54
|
+
});
|
|
55
|
+
expect(result.success).to.be.true;
|
|
56
|
+
expect(result.output).to.equal('test output');
|
|
57
|
+
});
|
|
58
|
+
it('should create an SSH session', async () => {
|
|
59
|
+
const result = await replCommandHandler.createSSHSession({
|
|
60
|
+
host: 'example.com',
|
|
61
|
+
username: 'user'
|
|
62
|
+
});
|
|
63
|
+
expect(result.success).to.be.true;
|
|
64
|
+
expect(result.pid).to.equal(54321);
|
|
65
|
+
});
|
|
66
|
+
it('should execute a command in an SSH session', async () => {
|
|
67
|
+
const result = await replCommandHandler.executeSSHCommand({
|
|
68
|
+
pid: 54321,
|
|
69
|
+
command: 'ls -la'
|
|
70
|
+
});
|
|
71
|
+
expect(result.success).to.be.true;
|
|
72
|
+
expect(result.output).to.equal('ssh output');
|
|
73
|
+
});
|
|
74
|
+
it('should list all REPL sessions', () => {
|
|
75
|
+
const result = replCommandHandler.listREPLSessions();
|
|
76
|
+
expect(result.success).to.be.true;
|
|
77
|
+
expect(result.sessions).to.have.length(2);
|
|
78
|
+
expect(result.sessions[0].pid).to.equal(12345);
|
|
79
|
+
expect(result.sessions[1].pid).to.equal(54321);
|
|
80
|
+
});
|
|
81
|
+
it('should close a REPL session', async () => {
|
|
82
|
+
const result = await replCommandHandler.closeREPLSession({
|
|
83
|
+
pid: 12345
|
|
84
|
+
});
|
|
85
|
+
expect(result.success).to.be.true;
|
|
86
|
+
});
|
|
87
|
+
it('should close idle REPL sessions', async () => {
|
|
88
|
+
const result = await replCommandHandler.closeIdleREPLSessions({
|
|
89
|
+
maxIdleMs: 3600000 // 1 hour
|
|
90
|
+
});
|
|
91
|
+
expect(result.success).to.be.true;
|
|
92
|
+
expect(result.closedCount).to.equal(2);
|
|
93
|
+
});
|
|
94
|
+
it('should handle errors when REPL manager is not initialized', async () => {
|
|
95
|
+
// Reset the handler to simulate uninitialized state
|
|
96
|
+
initializeREPLManager(null);
|
|
97
|
+
const result = await replCommandHandler.createREPLSession({
|
|
98
|
+
language: 'node'
|
|
99
|
+
});
|
|
100
|
+
expect(result.success).to.be.false;
|
|
101
|
+
expect(result.error).to.equal('REPL Manager not initialized');
|
|
102
|
+
});
|
|
103
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -45,13 +45,12 @@ async function runSetup() {
|
|
|
45
45
|
}
|
|
46
46
|
async function runServer() {
|
|
47
47
|
try {
|
|
48
|
-
const transport = new FilteredStdioServerTransport();
|
|
49
|
-
console.log("start");
|
|
50
48
|
// Check if first argument is "setup"
|
|
51
49
|
if (process.argv[2] === 'setup') {
|
|
52
50
|
await runSetup();
|
|
53
51
|
return;
|
|
54
52
|
}
|
|
53
|
+
const transport = new FilteredStdioServerTransport();
|
|
55
54
|
// Handle uncaught exceptions
|
|
56
55
|
process.on('uncaughtException', async (error) => {
|
|
57
56
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution result from a REPL command
|
|
3
|
+
*/
|
|
4
|
+
interface REPLExecutionResult {
|
|
5
|
+
success: boolean;
|
|
6
|
+
output: string | null;
|
|
7
|
+
error?: string;
|
|
8
|
+
timeout: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Options for executing code in a REPL
|
|
12
|
+
*/
|
|
13
|
+
interface REPLExecuteOptions {
|
|
14
|
+
timeout?: number;
|
|
15
|
+
waitForPrompt?: boolean;
|
|
16
|
+
ignoreErrors?: boolean;
|
|
17
|
+
multiline?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Manager for REPL (Read-Eval-Print Loop) sessions
|
|
21
|
+
* Provides a higher-level API for working with interactive programming environments
|
|
22
|
+
*/
|
|
23
|
+
export declare class REPLManager {
|
|
24
|
+
private sessions;
|
|
25
|
+
private readonly languageConfigs;
|
|
26
|
+
/**
|
|
27
|
+
* Calculate an appropriate timeout based on code complexity
|
|
28
|
+
*/
|
|
29
|
+
private calculateTimeout;
|
|
30
|
+
/**
|
|
31
|
+
* Detect when a REPL is ready for input by looking for prompt patterns
|
|
32
|
+
*/
|
|
33
|
+
private isReadyForInput;
|
|
34
|
+
/**
|
|
35
|
+
* Detect errors in REPL output
|
|
36
|
+
*/
|
|
37
|
+
private detectErrors;
|
|
38
|
+
/**
|
|
39
|
+
* Create a new REPL session for the specified language
|
|
40
|
+
*/
|
|
41
|
+
createSession(language: string, timeout?: number): Promise<number>;
|
|
42
|
+
/**
|
|
43
|
+
* Send and read REPL output with a timeout
|
|
44
|
+
* This combines sending input and reading output in a single operation
|
|
45
|
+
*/
|
|
46
|
+
sendAndReadREPL(pid: number, input: string, options?: REPLExecuteOptions): Promise<REPLExecutionResult>;
|
|
47
|
+
/**
|
|
48
|
+
* Execute a code block in a REPL session
|
|
49
|
+
*/
|
|
50
|
+
executeCode(pid: number, code: string, options?: REPLExecuteOptions): Promise<REPLExecutionResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Terminate a REPL session
|
|
53
|
+
*/
|
|
54
|
+
terminateSession(pid: number): Promise<boolean>;
|
|
55
|
+
/**
|
|
56
|
+
* List all active REPL sessions
|
|
57
|
+
*/
|
|
58
|
+
listSessions(): Array<{
|
|
59
|
+
pid: number;
|
|
60
|
+
language: string;
|
|
61
|
+
runtime: number;
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* Get information about a specific REPL session
|
|
65
|
+
*/
|
|
66
|
+
getSessionInfo(pid: number): {
|
|
67
|
+
pid: number;
|
|
68
|
+
language: string;
|
|
69
|
+
runtime: number;
|
|
70
|
+
} | null;
|
|
71
|
+
}
|
|
72
|
+
export declare const replManager: REPLManager;
|
|
73
|
+
export {};
|