@wonderwhy-er/desktop-commander 0.2.2 → 0.2.4
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 +27 -4
- package/dist/config-manager.d.ts +10 -0
- package/dist/config-manager.js +7 -1
- package/dist/handlers/edit-search-handlers.js +25 -6
- package/dist/handlers/filesystem-handlers.js +2 -4
- package/dist/handlers/terminal-handlers.d.ts +8 -4
- package/dist/handlers/terminal-handlers.js +16 -10
- package/dist/index-dxt.d.ts +2 -0
- package/dist/index-dxt.js +76 -0
- package/dist/index-with-startup-detection.d.ts +5 -0
- package/dist/index-with-startup-detection.js +180 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +381 -65
- package/dist/terminal-manager.d.ts +7 -0
- package/dist/terminal-manager.js +93 -18
- package/dist/tools/client.d.ts +10 -0
- package/dist/tools/client.js +13 -0
- package/dist/tools/config.d.ts +1 -1
- package/dist/tools/config.js +21 -3
- package/dist/tools/edit.js +4 -3
- package/dist/tools/environment.d.ts +55 -0
- package/dist/tools/environment.js +65 -0
- package/dist/tools/feedback.d.ts +8 -0
- package/dist/tools/feedback.js +132 -0
- package/dist/tools/filesystem.d.ts +10 -0
- package/dist/tools/filesystem.js +410 -60
- package/dist/tools/improved-process-tools.d.ts +24 -0
- package/dist/tools/improved-process-tools.js +453 -0
- package/dist/tools/schemas.d.ts +20 -2
- package/dist/tools/schemas.js +20 -3
- package/dist/tools/usage.d.ts +5 -0
- package/dist/tools/usage.js +24 -0
- package/dist/utils/capture.d.ts +2 -0
- package/dist/utils/capture.js +40 -9
- package/dist/utils/early-logger.d.ts +4 -0
- package/dist/utils/early-logger.js +35 -0
- package/dist/utils/mcp-logger.d.ts +30 -0
- package/dist/utils/mcp-logger.js +59 -0
- package/dist/utils/process-detection.d.ts +23 -0
- package/dist/utils/process-detection.js +150 -0
- package/dist/utils/smithery-detector.d.ts +94 -0
- package/dist/utils/smithery-detector.js +292 -0
- package/dist/utils/startup-detector.d.ts +65 -0
- package/dist/utils/startup-detector.js +390 -0
- package/dist/utils/system-info.d.ts +30 -0
- package/dist/utils/system-info.js +146 -0
- package/dist/utils/usageTracker.d.ts +85 -0
- package/dist/utils/usageTracker.js +280 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -1
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import { terminalManager } from '../terminal-manager.js';
|
|
2
|
+
import { commandManager } from '../command-manager.js';
|
|
3
|
+
import { StartProcessArgsSchema, ReadProcessOutputArgsSchema, InteractWithProcessArgsSchema, ForceTerminateArgsSchema } from './schemas.js';
|
|
4
|
+
import { capture } from "../utils/capture.js";
|
|
5
|
+
import { analyzeProcessState, cleanProcessOutput, formatProcessStateMessage } from '../utils/process-detection.js';
|
|
6
|
+
import * as os from 'os';
|
|
7
|
+
import { configManager } from '../config-manager.js';
|
|
8
|
+
/**
|
|
9
|
+
* Start a new process (renamed from execute_command)
|
|
10
|
+
* Includes early detection of process waiting for input
|
|
11
|
+
*/
|
|
12
|
+
export async function startProcess(args) {
|
|
13
|
+
const parsed = StartProcessArgsSchema.safeParse(args);
|
|
14
|
+
if (!parsed.success) {
|
|
15
|
+
capture('server_start_process_failed');
|
|
16
|
+
return {
|
|
17
|
+
content: [{ type: "text", text: `Error: Invalid arguments for start_process: ${parsed.error}` }],
|
|
18
|
+
isError: true,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const commands = commandManager.extractCommands(parsed.data.command).join(', ');
|
|
23
|
+
capture('server_start_process', {
|
|
24
|
+
command: commandManager.getBaseCommand(parsed.data.command),
|
|
25
|
+
commands: commands
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
capture('server_start_process', {
|
|
30
|
+
command: commandManager.getBaseCommand(parsed.data.command)
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const isAllowed = await commandManager.validateCommand(parsed.data.command);
|
|
34
|
+
if (!isAllowed) {
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: "text", text: `Error: Command not allowed: ${parsed.data.command}` }],
|
|
37
|
+
isError: true,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
let shellUsed = parsed.data.shell;
|
|
41
|
+
if (!shellUsed) {
|
|
42
|
+
const config = await configManager.getConfig();
|
|
43
|
+
if (config.defaultShell) {
|
|
44
|
+
shellUsed = config.defaultShell;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const isWindows = os.platform() === 'win32';
|
|
48
|
+
if (isWindows && process.env.COMSPEC) {
|
|
49
|
+
shellUsed = process.env.COMSPEC;
|
|
50
|
+
}
|
|
51
|
+
else if (!isWindows && process.env.SHELL) {
|
|
52
|
+
shellUsed = process.env.SHELL;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
shellUsed = isWindows ? 'cmd.exe' : '/bin/sh';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const result = await terminalManager.executeCommand(parsed.data.command, parsed.data.timeout_ms, shellUsed);
|
|
60
|
+
if (result.pid === -1) {
|
|
61
|
+
return {
|
|
62
|
+
content: [{ type: "text", text: result.output }],
|
|
63
|
+
isError: true,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// Analyze the process state to detect if it's waiting for input
|
|
67
|
+
const processState = analyzeProcessState(result.output, result.pid);
|
|
68
|
+
let statusMessage = '';
|
|
69
|
+
if (processState.isWaitingForInput) {
|
|
70
|
+
statusMessage = `\n🔄 ${formatProcessStateMessage(processState, result.pid)}`;
|
|
71
|
+
}
|
|
72
|
+
else if (processState.isFinished) {
|
|
73
|
+
statusMessage = `\n✅ ${formatProcessStateMessage(processState, result.pid)}`;
|
|
74
|
+
}
|
|
75
|
+
else if (result.isBlocked) {
|
|
76
|
+
statusMessage = '\n⏳ Process is running. Use read_process_output to get more output.';
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
content: [{
|
|
80
|
+
type: "text",
|
|
81
|
+
text: `Process started with PID ${result.pid} (shell: ${shellUsed})\nInitial output:\n${result.output}${statusMessage}`
|
|
82
|
+
}],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Read output from a running process (renamed from read_output)
|
|
87
|
+
* Includes early detection of process waiting for input
|
|
88
|
+
*/
|
|
89
|
+
export async function readProcessOutput(args) {
|
|
90
|
+
const parsed = ReadProcessOutputArgsSchema.safeParse(args);
|
|
91
|
+
if (!parsed.success) {
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: "text", text: `Error: Invalid arguments for read_process_output: ${parsed.error}` }],
|
|
94
|
+
isError: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const { pid, timeout_ms = 5000 } = parsed.data;
|
|
98
|
+
const session = terminalManager.getSession(pid);
|
|
99
|
+
if (!session) {
|
|
100
|
+
return {
|
|
101
|
+
content: [{ type: "text", text: `No active session found for PID ${pid}` }],
|
|
102
|
+
isError: true,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
let output = "";
|
|
106
|
+
let timeoutReached = false;
|
|
107
|
+
let earlyExit = false;
|
|
108
|
+
let processState;
|
|
109
|
+
try {
|
|
110
|
+
const outputPromise = new Promise((resolve) => {
|
|
111
|
+
const initialOutput = terminalManager.getNewOutput(pid);
|
|
112
|
+
if (initialOutput && initialOutput.length > 0) {
|
|
113
|
+
// Immediate check on existing output
|
|
114
|
+
const state = analyzeProcessState(initialOutput, pid);
|
|
115
|
+
if (state.isWaitingForInput) {
|
|
116
|
+
earlyExit = true;
|
|
117
|
+
processState = state;
|
|
118
|
+
}
|
|
119
|
+
resolve(initialOutput);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
let resolved = false;
|
|
123
|
+
let interval = null;
|
|
124
|
+
let timeout = null;
|
|
125
|
+
// Quick prompt patterns for immediate detection
|
|
126
|
+
const quickPromptPatterns = />>>\s*$|>\s*$|\$\s*$|#\s*$/;
|
|
127
|
+
const cleanup = () => {
|
|
128
|
+
if (interval)
|
|
129
|
+
clearInterval(interval);
|
|
130
|
+
if (timeout)
|
|
131
|
+
clearTimeout(timeout);
|
|
132
|
+
};
|
|
133
|
+
let resolveOnce = (value, isTimeout = false) => {
|
|
134
|
+
if (resolved)
|
|
135
|
+
return;
|
|
136
|
+
resolved = true;
|
|
137
|
+
cleanup();
|
|
138
|
+
timeoutReached = isTimeout;
|
|
139
|
+
resolve(value);
|
|
140
|
+
};
|
|
141
|
+
// Monitor for new output with immediate detection
|
|
142
|
+
const session = terminalManager.getSession(pid);
|
|
143
|
+
if (session && session.process && session.process.stdout && session.process.stderr) {
|
|
144
|
+
const immediateDetector = (data) => {
|
|
145
|
+
const text = data.toString();
|
|
146
|
+
// Immediate check for obvious prompts
|
|
147
|
+
if (quickPromptPatterns.test(text)) {
|
|
148
|
+
const newOutput = terminalManager.getNewOutput(pid) || text;
|
|
149
|
+
const state = analyzeProcessState(output + newOutput, pid);
|
|
150
|
+
if (state.isWaitingForInput) {
|
|
151
|
+
earlyExit = true;
|
|
152
|
+
processState = state;
|
|
153
|
+
resolveOnce(newOutput);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
session.process.stdout.on('data', immediateDetector);
|
|
159
|
+
session.process.stderr.on('data', immediateDetector);
|
|
160
|
+
// Cleanup immediate detectors when done
|
|
161
|
+
const originalResolveOnce = resolveOnce;
|
|
162
|
+
const cleanupDetectors = () => {
|
|
163
|
+
if (session.process.stdout) {
|
|
164
|
+
session.process.stdout.removeListener('data', immediateDetector);
|
|
165
|
+
}
|
|
166
|
+
if (session.process.stderr) {
|
|
167
|
+
session.process.stderr.removeListener('data', immediateDetector);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
// Override resolveOnce to include cleanup
|
|
171
|
+
const resolveOnceWithCleanup = (value, isTimeout = false) => {
|
|
172
|
+
cleanupDetectors();
|
|
173
|
+
originalResolveOnce(value, isTimeout);
|
|
174
|
+
};
|
|
175
|
+
// Replace the local resolveOnce reference
|
|
176
|
+
resolveOnce = resolveOnceWithCleanup;
|
|
177
|
+
}
|
|
178
|
+
interval = setInterval(() => {
|
|
179
|
+
const newOutput = terminalManager.getNewOutput(pid);
|
|
180
|
+
if (newOutput && newOutput.length > 0) {
|
|
181
|
+
const currentOutput = output + newOutput;
|
|
182
|
+
const state = analyzeProcessState(currentOutput, pid);
|
|
183
|
+
// Early exit if process is clearly waiting for input
|
|
184
|
+
if (state.isWaitingForInput) {
|
|
185
|
+
earlyExit = true;
|
|
186
|
+
processState = state;
|
|
187
|
+
resolveOnce(newOutput);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
output = currentOutput;
|
|
191
|
+
// Continue collecting if still running
|
|
192
|
+
if (!state.isFinished) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
// Process finished
|
|
196
|
+
processState = state;
|
|
197
|
+
resolveOnce(newOutput);
|
|
198
|
+
}
|
|
199
|
+
}, 200); // Check every 200ms
|
|
200
|
+
timeout = setTimeout(() => {
|
|
201
|
+
const finalOutput = terminalManager.getNewOutput(pid) || "";
|
|
202
|
+
resolveOnce(finalOutput, true);
|
|
203
|
+
}, timeout_ms);
|
|
204
|
+
});
|
|
205
|
+
const newOutput = await outputPromise;
|
|
206
|
+
output += newOutput;
|
|
207
|
+
// Analyze final state if not already done
|
|
208
|
+
if (!processState) {
|
|
209
|
+
processState = analyzeProcessState(output, pid);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
return {
|
|
214
|
+
content: [{ type: "text", text: `Error reading output: ${error}` }],
|
|
215
|
+
isError: true,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
// Format response based on what we detected
|
|
219
|
+
let statusMessage = '';
|
|
220
|
+
if (earlyExit && processState?.isWaitingForInput) {
|
|
221
|
+
statusMessage = `\n🔄 ${formatProcessStateMessage(processState, pid)}`;
|
|
222
|
+
}
|
|
223
|
+
else if (processState?.isFinished) {
|
|
224
|
+
statusMessage = `\n✅ ${formatProcessStateMessage(processState, pid)}`;
|
|
225
|
+
}
|
|
226
|
+
else if (timeoutReached) {
|
|
227
|
+
statusMessage = '\n⏱️ Timeout reached - process may still be running';
|
|
228
|
+
}
|
|
229
|
+
const responseText = output || 'No new output available';
|
|
230
|
+
return {
|
|
231
|
+
content: [{
|
|
232
|
+
type: "text",
|
|
233
|
+
text: `${responseText}${statusMessage}`
|
|
234
|
+
}],
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Interact with a running process (renamed from send_input)
|
|
239
|
+
* Automatically detects when process is ready and returns output
|
|
240
|
+
*/
|
|
241
|
+
export async function interactWithProcess(args) {
|
|
242
|
+
const parsed = InteractWithProcessArgsSchema.safeParse(args);
|
|
243
|
+
if (!parsed.success) {
|
|
244
|
+
capture('server_interact_with_process_failed', {
|
|
245
|
+
error: 'Invalid arguments'
|
|
246
|
+
});
|
|
247
|
+
return {
|
|
248
|
+
content: [{ type: "text", text: `Error: Invalid arguments for interact_with_process: ${parsed.error}` }],
|
|
249
|
+
isError: true,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
const { pid, input, timeout_ms = 8000, wait_for_prompt = true } = parsed.data;
|
|
253
|
+
try {
|
|
254
|
+
capture('server_interact_with_process', {
|
|
255
|
+
pid: pid,
|
|
256
|
+
inputLength: input.length
|
|
257
|
+
});
|
|
258
|
+
const success = terminalManager.sendInputToProcess(pid, input);
|
|
259
|
+
if (!success) {
|
|
260
|
+
return {
|
|
261
|
+
content: [{ type: "text", text: `Error: Failed to send input to process ${pid}. The process may have exited or doesn't accept input.` }],
|
|
262
|
+
isError: true,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
// If not waiting for response, return immediately
|
|
266
|
+
if (!wait_for_prompt) {
|
|
267
|
+
return {
|
|
268
|
+
content: [{
|
|
269
|
+
type: "text",
|
|
270
|
+
text: `✅ Input sent to process ${pid}. Use read_process_output to get the response.`
|
|
271
|
+
}],
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
// Smart waiting with immediate and periodic detection
|
|
275
|
+
let output = "";
|
|
276
|
+
let processState;
|
|
277
|
+
let earlyExit = false;
|
|
278
|
+
// Quick prompt patterns for immediate detection
|
|
279
|
+
const quickPromptPatterns = />>>\s*$|>\s*$|\$\s*$|#\s*$/;
|
|
280
|
+
const waitForResponse = () => {
|
|
281
|
+
return new Promise((resolve) => {
|
|
282
|
+
let resolved = false;
|
|
283
|
+
let attempts = 0;
|
|
284
|
+
const maxAttempts = Math.ceil(timeout_ms / 200);
|
|
285
|
+
let interval = null;
|
|
286
|
+
let resolveOnce = () => {
|
|
287
|
+
if (resolved)
|
|
288
|
+
return;
|
|
289
|
+
resolved = true;
|
|
290
|
+
if (interval)
|
|
291
|
+
clearInterval(interval);
|
|
292
|
+
resolve();
|
|
293
|
+
};
|
|
294
|
+
// Set up immediate detection on the process streams
|
|
295
|
+
const session = terminalManager.getSession(pid);
|
|
296
|
+
if (session && session.process && session.process.stdout && session.process.stderr) {
|
|
297
|
+
const immediateDetector = (data) => {
|
|
298
|
+
const text = data.toString();
|
|
299
|
+
// Immediate check for obvious prompts
|
|
300
|
+
if (quickPromptPatterns.test(text)) {
|
|
301
|
+
// Get the latest output and analyze
|
|
302
|
+
setTimeout(() => {
|
|
303
|
+
const newOutput = terminalManager.getNewOutput(pid);
|
|
304
|
+
if (newOutput) {
|
|
305
|
+
output += newOutput;
|
|
306
|
+
const state = analyzeProcessState(output, pid);
|
|
307
|
+
if (state.isWaitingForInput) {
|
|
308
|
+
processState = state;
|
|
309
|
+
earlyExit = true;
|
|
310
|
+
resolveOnce();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}, 50); // Small delay to ensure output is captured
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
session.process.stdout.on('data', immediateDetector);
|
|
317
|
+
session.process.stderr.on('data', immediateDetector);
|
|
318
|
+
// Cleanup when done
|
|
319
|
+
const cleanupDetectors = () => {
|
|
320
|
+
if (session.process.stdout) {
|
|
321
|
+
session.process.stdout.removeListener('data', immediateDetector);
|
|
322
|
+
}
|
|
323
|
+
if (session.process.stderr) {
|
|
324
|
+
session.process.stderr.removeListener('data', immediateDetector);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
// Override resolveOnce to include cleanup
|
|
328
|
+
const originalResolveOnce = resolveOnce;
|
|
329
|
+
const resolveOnceWithCleanup = () => {
|
|
330
|
+
cleanupDetectors();
|
|
331
|
+
originalResolveOnce();
|
|
332
|
+
};
|
|
333
|
+
// Replace the local resolveOnce reference
|
|
334
|
+
resolveOnce = resolveOnceWithCleanup;
|
|
335
|
+
}
|
|
336
|
+
// Periodic check as fallback
|
|
337
|
+
interval = setInterval(() => {
|
|
338
|
+
if (resolved)
|
|
339
|
+
return;
|
|
340
|
+
const newOutput = terminalManager.getNewOutput(pid);
|
|
341
|
+
if (newOutput && newOutput.length > 0) {
|
|
342
|
+
output += newOutput;
|
|
343
|
+
// Analyze current state
|
|
344
|
+
processState = analyzeProcessState(output, pid);
|
|
345
|
+
// Exit early if we detect the process is waiting for input
|
|
346
|
+
if (processState.isWaitingForInput) {
|
|
347
|
+
earlyExit = true;
|
|
348
|
+
resolveOnce();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
// Also exit if process finished
|
|
352
|
+
if (processState.isFinished) {
|
|
353
|
+
resolveOnce();
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
attempts++;
|
|
358
|
+
if (attempts >= maxAttempts) {
|
|
359
|
+
resolveOnce();
|
|
360
|
+
}
|
|
361
|
+
}, 200);
|
|
362
|
+
});
|
|
363
|
+
};
|
|
364
|
+
await waitForResponse();
|
|
365
|
+
// Clean and format output
|
|
366
|
+
const cleanOutput = cleanProcessOutput(output, input);
|
|
367
|
+
const timeoutReached = !earlyExit && !processState?.isFinished && !processState?.isWaitingForInput;
|
|
368
|
+
// Determine final state
|
|
369
|
+
if (!processState) {
|
|
370
|
+
processState = analyzeProcessState(output, pid);
|
|
371
|
+
}
|
|
372
|
+
let statusMessage = '';
|
|
373
|
+
if (processState.isWaitingForInput) {
|
|
374
|
+
statusMessage = `\n🔄 ${formatProcessStateMessage(processState, pid)}`;
|
|
375
|
+
}
|
|
376
|
+
else if (processState.isFinished) {
|
|
377
|
+
statusMessage = `\n✅ ${formatProcessStateMessage(processState, pid)}`;
|
|
378
|
+
}
|
|
379
|
+
else if (timeoutReached) {
|
|
380
|
+
statusMessage = '\n⏱️ Response may be incomplete (timeout reached)';
|
|
381
|
+
}
|
|
382
|
+
if (cleanOutput.trim().length === 0 && !timeoutReached) {
|
|
383
|
+
return {
|
|
384
|
+
content: [{
|
|
385
|
+
type: "text",
|
|
386
|
+
text: `✅ Input executed in process ${pid}.\n📭 (No output produced)${statusMessage}`
|
|
387
|
+
}],
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
// Format response with better structure and consistent emojis
|
|
391
|
+
let responseText = `✅ Input executed in process ${pid}`;
|
|
392
|
+
if (cleanOutput && cleanOutput.trim().length > 0) {
|
|
393
|
+
responseText += `:\n\n📤 Output:\n${cleanOutput}`;
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
responseText += `.\n📭 (No output produced)`;
|
|
397
|
+
}
|
|
398
|
+
if (statusMessage) {
|
|
399
|
+
responseText += `\n\n${statusMessage}`;
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
content: [{
|
|
403
|
+
type: "text",
|
|
404
|
+
text: responseText
|
|
405
|
+
}],
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
410
|
+
capture('server_interact_with_process_error', {
|
|
411
|
+
error: errorMessage
|
|
412
|
+
});
|
|
413
|
+
return {
|
|
414
|
+
content: [{ type: "text", text: `Error interacting with process: ${errorMessage}` }],
|
|
415
|
+
isError: true,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Force terminate a process
|
|
421
|
+
*/
|
|
422
|
+
export async function forceTerminate(args) {
|
|
423
|
+
const parsed = ForceTerminateArgsSchema.safeParse(args);
|
|
424
|
+
if (!parsed.success) {
|
|
425
|
+
return {
|
|
426
|
+
content: [{ type: "text", text: `Error: Invalid arguments for force_terminate: ${parsed.error}` }],
|
|
427
|
+
isError: true,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
const success = terminalManager.forceTerminate(parsed.data.pid);
|
|
431
|
+
return {
|
|
432
|
+
content: [{
|
|
433
|
+
type: "text",
|
|
434
|
+
text: success
|
|
435
|
+
? `Successfully initiated termination of session ${parsed.data.pid}`
|
|
436
|
+
: `No active session found for PID ${parsed.data.pid}`
|
|
437
|
+
}],
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* List active sessions
|
|
442
|
+
*/
|
|
443
|
+
export async function listSessions() {
|
|
444
|
+
const sessions = terminalManager.listActiveSessions();
|
|
445
|
+
return {
|
|
446
|
+
content: [{
|
|
447
|
+
type: "text",
|
|
448
|
+
text: sessions.length === 0
|
|
449
|
+
? 'No active sessions'
|
|
450
|
+
: sessions.map(s => `PID: ${s.pid}, Blocked: ${s.isBlocked}, Runtime: ${Math.round(s.runtime / 1000)}s`).join('\n')
|
|
451
|
+
}],
|
|
452
|
+
};
|
|
453
|
+
}
|
package/dist/tools/schemas.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare const SetConfigValueArgsSchema: z.ZodObject<{
|
|
|
11
11
|
value?: any;
|
|
12
12
|
}>;
|
|
13
13
|
export declare const ListProcessesArgsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
|
|
14
|
-
export declare const
|
|
14
|
+
export declare const StartProcessArgsSchema: z.ZodObject<{
|
|
15
15
|
command: z.ZodString;
|
|
16
16
|
timeout_ms: z.ZodNumber;
|
|
17
17
|
shell: z.ZodOptional<z.ZodString>;
|
|
@@ -24,7 +24,7 @@ export declare const ExecuteCommandArgsSchema: z.ZodObject<{
|
|
|
24
24
|
timeout_ms: number;
|
|
25
25
|
shell?: string | undefined;
|
|
26
26
|
}>;
|
|
27
|
-
export declare const
|
|
27
|
+
export declare const ReadProcessOutputArgsSchema: z.ZodObject<{
|
|
28
28
|
pid: z.ZodNumber;
|
|
29
29
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
30
30
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -173,3 +173,21 @@ export declare const EditBlockArgsSchema: z.ZodObject<{
|
|
|
173
173
|
new_string: string;
|
|
174
174
|
expected_replacements?: number | undefined;
|
|
175
175
|
}>;
|
|
176
|
+
export declare const InteractWithProcessArgsSchema: z.ZodObject<{
|
|
177
|
+
pid: z.ZodNumber;
|
|
178
|
+
input: z.ZodString;
|
|
179
|
+
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
180
|
+
wait_for_prompt: z.ZodOptional<z.ZodBoolean>;
|
|
181
|
+
}, "strip", z.ZodTypeAny, {
|
|
182
|
+
pid: number;
|
|
183
|
+
input: string;
|
|
184
|
+
timeout_ms?: number | undefined;
|
|
185
|
+
wait_for_prompt?: boolean | undefined;
|
|
186
|
+
}, {
|
|
187
|
+
pid: number;
|
|
188
|
+
input: string;
|
|
189
|
+
timeout_ms?: number | undefined;
|
|
190
|
+
wait_for_prompt?: boolean | undefined;
|
|
191
|
+
}>;
|
|
192
|
+
export declare const GetUsageStatsArgsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
|
|
193
|
+
export declare const GiveFeedbackArgsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
|
package/dist/tools/schemas.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
console.error("Loading schemas.ts");
|
|
3
2
|
// Config tools schemas
|
|
4
3
|
export const GetConfigArgsSchema = z.object({});
|
|
5
4
|
export const SetConfigValueArgsSchema = z.object({
|
|
@@ -9,12 +8,12 @@ export const SetConfigValueArgsSchema = z.object({
|
|
|
9
8
|
// Empty schemas
|
|
10
9
|
export const ListProcessesArgsSchema = z.object({});
|
|
11
10
|
// Terminal tools schemas
|
|
12
|
-
export const
|
|
11
|
+
export const StartProcessArgsSchema = z.object({
|
|
13
12
|
command: z.string(),
|
|
14
13
|
timeout_ms: z.number(),
|
|
15
14
|
shell: z.string().optional(),
|
|
16
15
|
});
|
|
17
|
-
export const
|
|
16
|
+
export const ReadProcessOutputArgsSchema = z.object({
|
|
18
17
|
pid: z.number(),
|
|
19
18
|
timeout_ms: z.number().optional(),
|
|
20
19
|
});
|
|
@@ -76,3 +75,21 @@ export const EditBlockArgsSchema = z.object({
|
|
|
76
75
|
new_string: z.string(),
|
|
77
76
|
expected_replacements: z.number().optional().default(1),
|
|
78
77
|
});
|
|
78
|
+
// Send input to process schema
|
|
79
|
+
export const InteractWithProcessArgsSchema = z.object({
|
|
80
|
+
pid: z.number(),
|
|
81
|
+
input: z.string(),
|
|
82
|
+
timeout_ms: z.number().optional(),
|
|
83
|
+
wait_for_prompt: z.boolean().optional(),
|
|
84
|
+
});
|
|
85
|
+
// Usage stats schema
|
|
86
|
+
export const GetUsageStatsArgsSchema = z.object({});
|
|
87
|
+
// Feedback tool schema - no pre-filled parameters, all user input
|
|
88
|
+
export const GiveFeedbackArgsSchema = z.object({
|
|
89
|
+
// No parameters needed - form will be filled manually by user
|
|
90
|
+
// Only auto-filled hidden fields remain:
|
|
91
|
+
// - tool_call_count (auto)
|
|
92
|
+
// - days_using (auto)
|
|
93
|
+
// - platform (auto)
|
|
94
|
+
// - client_id (auto)
|
|
95
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { usageTracker } from '../utils/usageTracker.js';
|
|
2
|
+
/**
|
|
3
|
+
* Get usage statistics for debugging and analysis
|
|
4
|
+
*/
|
|
5
|
+
export async function getUsageStats() {
|
|
6
|
+
try {
|
|
7
|
+
const summary = await usageTracker.getUsageSummary();
|
|
8
|
+
return {
|
|
9
|
+
content: [{
|
|
10
|
+
type: "text",
|
|
11
|
+
text: summary
|
|
12
|
+
}]
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
return {
|
|
17
|
+
content: [{
|
|
18
|
+
type: "text",
|
|
19
|
+
text: `Error retrieving usage stats: ${error instanceof Error ? error.message : String(error)}`
|
|
20
|
+
}],
|
|
21
|
+
isError: true
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
package/dist/utils/capture.d.ts
CHANGED
|
@@ -12,4 +12,6 @@ export declare function sanitizeError(error: any): {
|
|
|
12
12
|
* @param event Event name
|
|
13
13
|
* @param properties Optional event properties
|
|
14
14
|
*/
|
|
15
|
+
export declare const captureBase: (captureURL: string, event: string, properties?: any) => Promise<void>;
|
|
16
|
+
export declare const capture_call_tool: (event: string, properties?: any) => Promise<void>;
|
|
15
17
|
export declare const capture: (event: string, properties?: any) => Promise<void>;
|
package/dist/utils/capture.js
CHANGED
|
@@ -10,11 +10,6 @@ try {
|
|
|
10
10
|
catch {
|
|
11
11
|
// Continue without version info if not available
|
|
12
12
|
}
|
|
13
|
-
// Configuration
|
|
14
|
-
const GA_MEASUREMENT_ID = 'G-NGGDNL0K4L'; // Replace with your GA4 Measurement ID
|
|
15
|
-
const GA_API_SECRET = '5M0mC--2S_6t94m8WrI60A'; // Replace with your GA4 API Secret
|
|
16
|
-
const GA_BASE_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
|
|
17
|
-
const GA_DEBUG_BASE_URL = `https://www.google-analytics.com/debug/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
|
|
18
13
|
// Will be initialized when needed
|
|
19
14
|
let uniqueUserId = 'unknown';
|
|
20
15
|
// Function to get or create a persistent UUID
|
|
@@ -70,18 +65,27 @@ export function sanitizeError(error) {
|
|
|
70
65
|
* @param event Event name
|
|
71
66
|
* @param properties Optional event properties
|
|
72
67
|
*/
|
|
73
|
-
export const
|
|
68
|
+
export const captureBase = async (captureURL, event, properties) => {
|
|
74
69
|
try {
|
|
75
70
|
// Check if telemetry is enabled in config (defaults to true if not set)
|
|
76
71
|
const telemetryEnabled = await configManager.getValue('telemetryEnabled');
|
|
77
72
|
// If telemetry is explicitly disabled or GA credentials are missing, don't send
|
|
78
|
-
if (telemetryEnabled === false || !
|
|
73
|
+
if (telemetryEnabled === false || !captureURL) {
|
|
79
74
|
return;
|
|
80
75
|
}
|
|
81
76
|
// Get or create the client ID if not already initialized
|
|
82
77
|
if (uniqueUserId === 'unknown') {
|
|
83
78
|
uniqueUserId = await getOrCreateUUID();
|
|
84
79
|
}
|
|
80
|
+
// Get current client information for all events
|
|
81
|
+
const currentClient = configManager.getCurrentClientInfo();
|
|
82
|
+
let clientContext = {};
|
|
83
|
+
if (currentClient) {
|
|
84
|
+
clientContext = {
|
|
85
|
+
client_name: currentClient.name,
|
|
86
|
+
client_version: currentClient.version,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
85
89
|
// Create a deep copy of properties to avoid modifying the original objects
|
|
86
90
|
// This ensures we don't alter error objects that are also returned to the AI
|
|
87
91
|
let sanitizedProperties;
|
|
@@ -114,16 +118,29 @@ export const capture = async (event, properties) => {
|
|
|
114
118
|
delete sanitizedProperties[key];
|
|
115
119
|
}
|
|
116
120
|
}
|
|
121
|
+
// Is MCP installed with DXT
|
|
122
|
+
let isDXT = 'false';
|
|
123
|
+
if (process.env.MCP_DXT) {
|
|
124
|
+
isDXT = 'true';
|
|
125
|
+
}
|
|
126
|
+
// Is MCP running in a Docker container
|
|
127
|
+
let isDocker = 'false';
|
|
128
|
+
if (process.env.MCP_CLIENT_DOCKER) {
|
|
129
|
+
isDocker = 'true';
|
|
130
|
+
}
|
|
117
131
|
// Prepare standard properties
|
|
118
132
|
const baseProperties = {
|
|
119
133
|
timestamp: new Date().toISOString(),
|
|
120
134
|
platform: platform(),
|
|
135
|
+
isDocker,
|
|
136
|
+
isDXT,
|
|
121
137
|
app_version: VERSION,
|
|
122
138
|
engagement_time_msec: "100"
|
|
123
139
|
};
|
|
124
|
-
// Combine with sanitized properties
|
|
140
|
+
// Combine with sanitized properties and client context
|
|
125
141
|
const eventProperties = {
|
|
126
142
|
...baseProperties,
|
|
143
|
+
...clientContext,
|
|
127
144
|
...sanitizedProperties
|
|
128
145
|
};
|
|
129
146
|
// Prepare GA4 payload
|
|
@@ -145,7 +162,7 @@ export const capture = async (event, properties) => {
|
|
|
145
162
|
'Content-Length': Buffer.byteLength(postData)
|
|
146
163
|
}
|
|
147
164
|
};
|
|
148
|
-
const req = https.request(
|
|
165
|
+
const req = https.request(captureURL, options, (res) => {
|
|
149
166
|
// Response handling (optional)
|
|
150
167
|
let data = '';
|
|
151
168
|
res.on('data', (chunk) => {
|
|
@@ -173,3 +190,17 @@ export const capture = async (event, properties) => {
|
|
|
173
190
|
// Silently fail - we don't want analytics issues to break functionality
|
|
174
191
|
}
|
|
175
192
|
};
|
|
193
|
+
export const capture_call_tool = async (event, properties) => {
|
|
194
|
+
const GA_MEASUREMENT_ID = 'G-35YKFM782B'; // Replace with your GA4 Measurement ID
|
|
195
|
+
const GA_API_SECRET = 'qM5VNk6aQy6NN5s-tCppZw'; // Replace with your GA4 API Secret
|
|
196
|
+
const GA_BASE_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
|
|
197
|
+
const GA_DEBUG_BASE_URL = `https://www.google-analytics.com/debug/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
|
|
198
|
+
return await captureBase(GA_BASE_URL, event, properties);
|
|
199
|
+
};
|
|
200
|
+
export const capture = async (event, properties) => {
|
|
201
|
+
const GA_MEASUREMENT_ID = 'G-NGGDNL0K4L'; // Replace with your GA4 Measurement ID
|
|
202
|
+
const GA_API_SECRET = '5M0mC--2S_6t94m8WrI60A'; // Replace with your GA4 API Secret
|
|
203
|
+
const GA_BASE_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
|
|
204
|
+
const GA_DEBUG_BASE_URL = `https://www.google-analytics.com/debug/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
|
|
205
|
+
return await captureBase(GA_BASE_URL, event, properties);
|
|
206
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function setTransportInstance(transport: any): void;
|
|
2
|
+
export declare function logError(message: string, data?: any): void;
|
|
3
|
+
export declare function logInfo(message: string, data?: any): void;
|
|
4
|
+
export declare function logWarning(message: string, data?: any): void;
|