@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
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Client-side utility for interacting with the REPL functionality
3
+ */
4
+ export class REPLClient {
5
+ constructor(apiClient) {
6
+ this.apiClient = apiClient;
7
+ this.sessions = {};
8
+ }
9
+ /**
10
+ * Create a new REPL session for a specific language
11
+ * @param language - Language for the REPL (python, node, bash)
12
+ * @param options - Optional configuration
13
+ * @returns Session information
14
+ */
15
+ async createSession(language, options = {}) {
16
+ try {
17
+ const response = await this.apiClient.executeCommand('repl.create', {
18
+ language,
19
+ options
20
+ });
21
+ if (response.success && response.pid) {
22
+ this.sessions[response.pid] = {
23
+ language,
24
+ pid: response.pid,
25
+ createdAt: new Date()
26
+ };
27
+ }
28
+ return response;
29
+ }
30
+ catch (error) {
31
+ console.error('Failed to create REPL session:', error);
32
+ return {
33
+ success: false,
34
+ error: error.message || 'Failed to create REPL session'
35
+ };
36
+ }
37
+ }
38
+ /**
39
+ * Execute code in an existing REPL session
40
+ * @param pid - Process ID of the session
41
+ * @param code - Code to execute
42
+ * @param options - Optional execution options
43
+ * @returns Execution results
44
+ */
45
+ async executeCode(pid, code, options = {}) {
46
+ try {
47
+ return await this.apiClient.executeCommand('repl.execute', {
48
+ pid,
49
+ code,
50
+ options
51
+ });
52
+ }
53
+ catch (error) {
54
+ console.error('Failed to execute code:', error);
55
+ return {
56
+ success: false,
57
+ error: error.message || 'Failed to execute code'
58
+ };
59
+ }
60
+ }
61
+ /**
62
+ * List all active REPL sessions
63
+ * @returns List of active sessions
64
+ */
65
+ async listSessions() {
66
+ try {
67
+ return await this.apiClient.executeCommand('repl.list');
68
+ }
69
+ catch (error) {
70
+ console.error('Failed to list REPL sessions:', error);
71
+ return {
72
+ success: false,
73
+ error: error.message || 'Failed to list REPL sessions'
74
+ };
75
+ }
76
+ }
77
+ /**
78
+ * Close a specific REPL session
79
+ * @param pid - Process ID to close
80
+ * @returns Result indicating success
81
+ */
82
+ async closeSession(pid) {
83
+ try {
84
+ const response = await this.apiClient.executeCommand('repl.close', { pid });
85
+ if (response.success) {
86
+ delete this.sessions[pid];
87
+ }
88
+ return response;
89
+ }
90
+ catch (error) {
91
+ console.error('Failed to close REPL session:', error);
92
+ return {
93
+ success: false,
94
+ error: error.message || 'Failed to close REPL session'
95
+ };
96
+ }
97
+ }
98
+ /**
99
+ * Close all idle REPL sessions
100
+ * @param maxIdleMs - Maximum idle time in milliseconds
101
+ * @returns Result with number of closed sessions
102
+ */
103
+ async closeIdleSessions(maxIdleMs) {
104
+ try {
105
+ const response = await this.apiClient.executeCommand('repl.closeIdle', { maxIdleMs });
106
+ // If successful, refresh our local session cache
107
+ if (response.success && response.closedCount > 0) {
108
+ const activeSessionsResponse = await this.listSessions();
109
+ if (activeSessionsResponse.success) {
110
+ // Reset our session tracking
111
+ this.sessions = {};
112
+ // Add back active sessions
113
+ activeSessionsResponse.sessions.forEach((session) => {
114
+ this.sessions[session.pid] = {
115
+ language: session.language,
116
+ pid: session.pid,
117
+ createdAt: new Date(session.startTime)
118
+ };
119
+ });
120
+ }
121
+ }
122
+ return response;
123
+ }
124
+ catch (error) {
125
+ console.error('Failed to close idle REPL sessions:', error);
126
+ return {
127
+ success: false,
128
+ error: error.message || 'Failed to close idle REPL sessions'
129
+ };
130
+ }
131
+ }
132
+ /**
133
+ * Execute a multi-line code block in the appropriate REPL session
134
+ * @param codeBlock - Code block with language marker
135
+ * @param options - Optional execution options
136
+ * @returns Execution results
137
+ */
138
+ async executeCodeBlock(codeBlock, options = {}) {
139
+ try {
140
+ // Parse code block format with language marker
141
+ // Example: ```python
142
+ // print("Hello")
143
+ // ```
144
+ const match = codeBlock.match(/```(\w+)\s*\n([\s\S]+?)```/);
145
+ if (!match) {
146
+ return {
147
+ success: false,
148
+ error: 'Invalid code block format. Use ```language\\ncode```'
149
+ };
150
+ }
151
+ const language = match[1].toLowerCase();
152
+ const code = match[2].trim();
153
+ // Find or create session for this language
154
+ let pid = null;
155
+ // Look for existing session
156
+ for (const sessionPid in this.sessions) {
157
+ if (this.sessions[sessionPid].language === language) {
158
+ pid = parseInt(sessionPid);
159
+ break;
160
+ }
161
+ }
162
+ // Create new session if needed
163
+ if (!pid) {
164
+ const createResult = await this.createSession(language, options);
165
+ if (!createResult.success) {
166
+ return createResult;
167
+ }
168
+ pid = createResult.pid;
169
+ }
170
+ // Execute the code
171
+ return await this.executeCode(pid, code, options);
172
+ }
173
+ catch (error) {
174
+ console.error('Failed to execute code block:', error);
175
+ return {
176
+ success: false,
177
+ error: error.message || 'Failed to execute code block'
178
+ };
179
+ }
180
+ }
181
+ /**
182
+ * Run a simple one-liner code snippet
183
+ * @param language - The programming language
184
+ * @param code - Code to execute (single line)
185
+ * @param options - Optional execution options
186
+ * @returns Execution results
187
+ */
188
+ async runSnippet(language, code, options = {}) {
189
+ try {
190
+ // Look for existing session
191
+ let pid = null;
192
+ for (const sessionPid in this.sessions) {
193
+ if (this.sessions[sessionPid].language === language.toLowerCase()) {
194
+ pid = parseInt(sessionPid);
195
+ break;
196
+ }
197
+ }
198
+ // Create new session if needed
199
+ if (!pid) {
200
+ const createResult = await this.createSession(language, options);
201
+ if (!createResult.success) {
202
+ return createResult;
203
+ }
204
+ pid = createResult.pid;
205
+ }
206
+ // Execute the code
207
+ return await this.executeCode(pid, code, options);
208
+ }
209
+ catch (error) {
210
+ console.error('Failed to run snippet:', error);
211
+ return {
212
+ success: false,
213
+ error: error.message || 'Failed to run snippet'
214
+ };
215
+ }
216
+ }
217
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Client-side utility for SSH interactions
3
+ */
4
+ interface ApiClient {
5
+ executeCommand: (command: string, params?: any) => Promise<any>;
6
+ }
7
+ interface SSHSession {
8
+ host: string;
9
+ username?: string;
10
+ pid: number;
11
+ createdAt: Date;
12
+ lastActivity: Date;
13
+ }
14
+ interface SSHOptions {
15
+ username?: string;
16
+ port?: number;
17
+ identity?: string;
18
+ password?: string;
19
+ timeout?: number;
20
+ stopOnError?: boolean;
21
+ }
22
+ export declare class SSHClient {
23
+ private apiClient;
24
+ private activeSessions;
25
+ constructor(apiClient: ApiClient);
26
+ /**
27
+ * Connect to an SSH server
28
+ * @param host - SSH host to connect to
29
+ * @param options - Connection options
30
+ * @returns Connection information
31
+ */
32
+ connect(host: string, options?: SSHOptions): Promise<any>;
33
+ /**
34
+ * Execute a command on the remote server
35
+ * @param pid - Process ID of the SSH session
36
+ * @param command - Command to execute
37
+ * @param options - Execution options
38
+ * @returns Command results
39
+ */
40
+ executeCommand(pid: number | string, command: string, options?: SSHOptions): Promise<any>;
41
+ /**
42
+ * Execute a command on a specific host (creates session if needed)
43
+ * @param host - SSH host
44
+ * @param command - Command to execute
45
+ * @param options - Connection and execution options
46
+ * @returns Command results
47
+ */
48
+ executeCommandOnHost(host: string, command: string, options?: SSHOptions): Promise<any>;
49
+ /**
50
+ * Execute multiple commands in sequence
51
+ * @param pid - Process ID of the SSH session
52
+ * @param commands - List of commands to execute
53
+ * @param options - Execution options
54
+ * @returns Results for each command
55
+ */
56
+ executeSequence(pid: number | string, commands: string[], options?: SSHOptions): Promise<any>;
57
+ /**
58
+ * Execute a multi-line script as separate commands
59
+ * @param pid - Process ID of the SSH session
60
+ * @param script - Multi-line script
61
+ * @param options - Execution options
62
+ * @returns Script execution results
63
+ */
64
+ executeScript(pid: number | string, script: string, options?: SSHOptions): Promise<any>;
65
+ /**
66
+ * Close an SSH session
67
+ * @param pid - Process ID to close
68
+ * @returns Result indicating success
69
+ */
70
+ disconnect(pid: number | string): Promise<any>;
71
+ /**
72
+ * List all active SSH sessions
73
+ * @returns List of active sessions
74
+ */
75
+ listSessions(): SSHSession[];
76
+ /**
77
+ * Close all SSH sessions
78
+ * @returns Result with number of closed sessions
79
+ */
80
+ disconnectAll(): Promise<any>;
81
+ }
82
+ export {};
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Client-side utility for SSH interactions
3
+ */
4
+ export class SSHClient {
5
+ constructor(apiClient) {
6
+ this.apiClient = apiClient;
7
+ this.activeSessions = {};
8
+ }
9
+ /**
10
+ * Connect to an SSH server
11
+ * @param host - SSH host to connect to
12
+ * @param options - Connection options
13
+ * @returns Connection information
14
+ */
15
+ async connect(host, options = {}) {
16
+ try {
17
+ // Check if we already have an active session for this host
18
+ for (const pid in this.activeSessions) {
19
+ const session = this.activeSessions[pid];
20
+ if (session.host === host && session.username === options.username) {
21
+ return {
22
+ success: true,
23
+ pid: parseInt(pid),
24
+ host,
25
+ username: options.username,
26
+ message: `Reusing existing SSH session to ${host} (PID: ${pid})`
27
+ };
28
+ }
29
+ }
30
+ // Create new session
31
+ const response = await this.apiClient.executeCommand('ssh.create', {
32
+ host,
33
+ ...options
34
+ });
35
+ if (response.success && response.pid) {
36
+ this.activeSessions[response.pid] = {
37
+ host,
38
+ username: options.username,
39
+ pid: response.pid,
40
+ createdAt: new Date(),
41
+ lastActivity: new Date()
42
+ };
43
+ }
44
+ return response;
45
+ }
46
+ catch (error) {
47
+ console.error('SSH connection failed:', error);
48
+ return {
49
+ success: false,
50
+ error: error.message || 'SSH connection failed'
51
+ };
52
+ }
53
+ }
54
+ /**
55
+ * Execute a command on the remote server
56
+ * @param pid - Process ID of the SSH session
57
+ * @param command - Command to execute
58
+ * @param options - Execution options
59
+ * @returns Command results
60
+ */
61
+ async executeCommand(pid, command, options = {}) {
62
+ try {
63
+ // Update last activity time
64
+ const pidNum = typeof pid === 'string' ? parseInt(pid) : pid;
65
+ if (this.activeSessions[pidNum]) {
66
+ this.activeSessions[pidNum].lastActivity = new Date();
67
+ }
68
+ return await this.apiClient.executeCommand('ssh.execute', {
69
+ pid,
70
+ command,
71
+ options
72
+ });
73
+ }
74
+ catch (error) {
75
+ console.error('SSH command execution failed:', error);
76
+ return {
77
+ success: false,
78
+ error: error.message || 'SSH command execution failed'
79
+ };
80
+ }
81
+ }
82
+ /**
83
+ * Execute a command on a specific host (creates session if needed)
84
+ * @param host - SSH host
85
+ * @param command - Command to execute
86
+ * @param options - Connection and execution options
87
+ * @returns Command results
88
+ */
89
+ async executeCommandOnHost(host, command, options = {}) {
90
+ try {
91
+ // First connect (or reuse connection)
92
+ const connResult = await this.connect(host, options);
93
+ if (!connResult.success) {
94
+ return connResult;
95
+ }
96
+ // Then execute the command
97
+ const result = await this.executeCommand(connResult.pid, command, options);
98
+ // Include host info in the result
99
+ return {
100
+ ...result,
101
+ host,
102
+ pid: connResult.pid
103
+ };
104
+ }
105
+ catch (error) {
106
+ console.error('SSH command execution failed:', error);
107
+ return {
108
+ success: false,
109
+ error: error.message || 'SSH command execution failed'
110
+ };
111
+ }
112
+ }
113
+ /**
114
+ * Execute multiple commands in sequence
115
+ * @param pid - Process ID of the SSH session
116
+ * @param commands - List of commands to execute
117
+ * @param options - Execution options
118
+ * @returns Results for each command
119
+ */
120
+ async executeSequence(pid, commands, options = {}) {
121
+ const results = [];
122
+ for (const command of commands) {
123
+ const result = await this.executeCommand(pid, command, options);
124
+ results.push(result);
125
+ // Stop on first error if requested
126
+ if (options.stopOnError && !result.success) {
127
+ break;
128
+ }
129
+ }
130
+ return {
131
+ success: results.every(r => r.success),
132
+ results,
133
+ completedCommands: results.length
134
+ };
135
+ }
136
+ /**
137
+ * Execute a multi-line script as separate commands
138
+ * @param pid - Process ID of the SSH session
139
+ * @param script - Multi-line script
140
+ * @param options - Execution options
141
+ * @returns Script execution results
142
+ */
143
+ async executeScript(pid, script, options = {}) {
144
+ const commands = script
145
+ .split('\n')
146
+ .map(line => line.trim())
147
+ .filter(line => line && !line.startsWith('#'));
148
+ return await this.executeSequence(pid, commands, options);
149
+ }
150
+ /**
151
+ * Close an SSH session
152
+ * @param pid - Process ID to close
153
+ * @returns Result indicating success
154
+ */
155
+ async disconnect(pid) {
156
+ try {
157
+ const response = await this.apiClient.executeCommand('repl.close', {
158
+ pid
159
+ });
160
+ const pidNum = typeof pid === 'string' ? parseInt(pid) : pid;
161
+ if (response.success) {
162
+ delete this.activeSessions[pidNum];
163
+ }
164
+ return response;
165
+ }
166
+ catch (error) {
167
+ console.error('Failed to close SSH session:', error);
168
+ return {
169
+ success: false,
170
+ error: error.message || 'Failed to close SSH session'
171
+ };
172
+ }
173
+ }
174
+ /**
175
+ * List all active SSH sessions
176
+ * @returns List of active sessions
177
+ */
178
+ listSessions() {
179
+ return Object.values(this.activeSessions);
180
+ }
181
+ /**
182
+ * Close all SSH sessions
183
+ * @returns Result with number of closed sessions
184
+ */
185
+ async disconnectAll() {
186
+ const pids = Object.keys(this.activeSessions);
187
+ let closedCount = 0;
188
+ for (const pid of pids) {
189
+ const result = await this.disconnect(pid);
190
+ if (result.success) {
191
+ closedCount++;
192
+ }
193
+ }
194
+ return {
195
+ success: true,
196
+ closedCount,
197
+ message: `Closed ${closedCount} SSH session(s)`
198
+ };
199
+ }
200
+ }
@@ -0,0 +1,21 @@
1
+ import { ServerResult } from '../types.js';
2
+ /**
3
+ * Handle create_repl_session command
4
+ */
5
+ export declare function handleCreateREPLSession(args: unknown): Promise<ServerResult>;
6
+ /**
7
+ * Handle execute_repl_code command
8
+ */
9
+ export declare function handleExecuteREPLCode(args: unknown): Promise<ServerResult>;
10
+ /**
11
+ * Handle terminate_repl_session command
12
+ */
13
+ export declare function handleTerminateREPLSession(args: unknown): Promise<ServerResult>;
14
+ /**
15
+ * Handle list_repl_sessions command
16
+ */
17
+ export declare function handleListREPLSessions(args?: unknown): Promise<ServerResult>;
18
+ /**
19
+ * Handle get_repl_session_info command
20
+ */
21
+ export declare function handleGetREPLSessionInfo(args: unknown): Promise<ServerResult>;
@@ -0,0 +1,37 @@
1
+ import { createREPLSession, executeREPLCode, terminateREPLSession, listREPLSessions, getREPLSessionInfo } from '../tools/repl.js';
2
+ import { CreateREPLSessionArgsSchema, ExecuteREPLCodeArgsSchema, TerminateREPLSessionArgsSchema, ListREPLSessionsArgsSchema, GetREPLSessionInfoArgsSchema } from '../tools/schemas.js';
3
+ /**
4
+ * Handle create_repl_session command
5
+ */
6
+ export async function handleCreateREPLSession(args) {
7
+ const parsed = CreateREPLSessionArgsSchema.parse(args);
8
+ return createREPLSession(parsed);
9
+ }
10
+ /**
11
+ * Handle execute_repl_code command
12
+ */
13
+ export async function handleExecuteREPLCode(args) {
14
+ const parsed = ExecuteREPLCodeArgsSchema.parse(args);
15
+ return executeREPLCode(parsed);
16
+ }
17
+ /**
18
+ * Handle terminate_repl_session command
19
+ */
20
+ export async function handleTerminateREPLSession(args) {
21
+ const parsed = TerminateREPLSessionArgsSchema.parse(args);
22
+ return terminateREPLSession(parsed);
23
+ }
24
+ /**
25
+ * Handle list_repl_sessions command
26
+ */
27
+ export async function handleListREPLSessions(args = {}) {
28
+ const parsed = ListREPLSessionsArgsSchema.parse(args);
29
+ return listREPLSessions(parsed);
30
+ }
31
+ /**
32
+ * Handle get_repl_session_info command
33
+ */
34
+ export async function handleGetREPLSessionInfo(args) {
35
+ const parsed = GetREPLSessionInfoArgsSchema.parse(args);
36
+ return getREPLSessionInfo(parsed);
37
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Initialize the REPL manager with a terminal manager instance
3
+ * @param terminalManager - The terminal manager instance
4
+ */
5
+ export declare function initializeREPLManager(terminalManager: any): void;
6
+ interface REPLSessionParams {
7
+ language: string;
8
+ options?: any;
9
+ }
10
+ interface ExecuteCodeParams {
11
+ pid: number | string;
12
+ code: string;
13
+ options?: any;
14
+ }
15
+ interface SSHSessionParams {
16
+ host: string;
17
+ username?: string;
18
+ port?: number;
19
+ identity?: string;
20
+ password?: string;
21
+ options?: any;
22
+ }
23
+ interface CommandParams {
24
+ pid: number | string;
25
+ command: string;
26
+ options?: any;
27
+ }
28
+ interface IdleSessionParams {
29
+ maxIdleMs?: number;
30
+ }
31
+ /**
32
+ * Command handler for REPL-related operations
33
+ */
34
+ export declare const replCommandHandler: {
35
+ /**
36
+ * Create a new REPL session
37
+ * @param params - Command parameters
38
+ * @returns Result object with session PID
39
+ */
40
+ createREPLSession: (params: REPLSessionParams) => Promise<{
41
+ success: boolean;
42
+ pid: number;
43
+ message: string;
44
+ error?: undefined;
45
+ } | {
46
+ success: boolean;
47
+ error: any;
48
+ pid?: undefined;
49
+ message?: undefined;
50
+ }>;
51
+ /**
52
+ * Execute code in an existing REPL session
53
+ * @param params - Command parameters
54
+ * @returns Result with execution output
55
+ */
56
+ executeREPLCode: (params: ExecuteCodeParams) => Promise<any>;
57
+ /**
58
+ * Create a new SSH session
59
+ * @param params - Command parameters
60
+ * @returns Result object with session PID
61
+ */
62
+ createSSHSession: (params: SSHSessionParams) => Promise<{
63
+ success: boolean;
64
+ pid: number;
65
+ message: string;
66
+ error?: undefined;
67
+ } | {
68
+ success: boolean;
69
+ error: any;
70
+ pid?: undefined;
71
+ message?: undefined;
72
+ }>;
73
+ /**
74
+ * Execute a command in an SSH session
75
+ * @param params - Command parameters
76
+ * @returns Result with execution output
77
+ */
78
+ executeSSHCommand: (params: CommandParams) => Promise<any>;
79
+ /**
80
+ * List all active REPL sessions
81
+ * @returns List of active sessions
82
+ */
83
+ listREPLSessions: () => {
84
+ success: boolean;
85
+ sessions: any[];
86
+ error?: undefined;
87
+ } | {
88
+ success: boolean;
89
+ error: any;
90
+ sessions?: undefined;
91
+ };
92
+ /**
93
+ * Close a specific REPL session
94
+ * @param params - Command parameters
95
+ * @returns Result indicating success
96
+ */
97
+ closeREPLSession: (params: {
98
+ pid: number | string;
99
+ }) => Promise<{
100
+ success: boolean;
101
+ message: string;
102
+ error?: undefined;
103
+ } | {
104
+ success: boolean;
105
+ error: any;
106
+ message?: undefined;
107
+ }>;
108
+ /**
109
+ * Close all idle REPL sessions
110
+ * @param params - Command parameters
111
+ * @returns Result with number of closed sessions
112
+ */
113
+ closeIdleREPLSessions: (params?: IdleSessionParams) => Promise<{
114
+ success: boolean;
115
+ closedCount: number;
116
+ message: string;
117
+ error?: undefined;
118
+ } | {
119
+ success: boolean;
120
+ error: any;
121
+ closedCount?: undefined;
122
+ message?: undefined;
123
+ }>;
124
+ };
125
+ export {};