@wonderwhy-er/desktop-commander 0.2.40 β 0.2.42
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 +4 -4
- package/dist/handlers/filesystem-handlers.js +28 -3
- package/dist/server.d.ts +2 -1
- package/dist/server.js +50 -13
- package/dist/setup-claude-server.js +56 -50
- package/dist/terminal-manager.js +46 -0
- package/dist/tools/edit.js +7 -1
- package/dist/tools/filesystem.d.ts +5 -0
- package/dist/tools/filesystem.js +91 -14
- package/dist/tools/pdf/markdown.d.ts +13 -0
- package/dist/tools/pdf/markdown.js +93 -29
- package/dist/track-installation.js +57 -38
- package/dist/types.d.ts +4 -0
- package/dist/ui/contracts.d.ts +1 -1
- package/dist/ui/contracts.js +4 -1
- package/dist/ui/file-preview/preview-runtime.js +114 -116
- package/dist/ui/file-preview/src/app.js +19 -22
- package/dist/ui/file-preview/src/directory-controller.js +9 -2
- package/dist/ui/file-preview/src/file-type-handlers.js +20 -9
- package/dist/ui/file-preview/src/host/external-actions.d.ts +0 -11
- package/dist/ui/file-preview/src/host/external-actions.js +0 -39
- package/dist/ui/file-preview/src/payload-utils.js +10 -1
- package/dist/uninstall-claude-server.js +54 -47
- package/dist/utils/ab-test.d.ts +4 -0
- package/dist/utils/ab-test.js +6 -0
- package/dist/utils/capture.d.ts +10 -2
- package/dist/utils/capture.js +80 -54
- package/dist/utils/feature-flags.d.ts +3 -0
- package/dist/utils/feature-flags.js +34 -5
- package/dist/utils/files/excel.js +26 -5
- package/dist/utils/mcp-ui-ab-test.d.ts +13 -0
- package/dist/utils/mcp-ui-ab-test.js +62 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -17,8 +17,6 @@ Work with code and text, run processes, and automate tasks, going far beyond oth
|
|
|
17
17
|
<img width="380" height="200" src="https://glama.ai/mcp/servers/zempur9oh4/badge" alt="Desktop Commander MCP" />
|
|
18
18
|
</a>
|
|
19
19
|
|
|
20
|
-
## π Weβre hiring β come build with us: https://desktopcommander.app/careers/
|
|
21
|
-
|
|
22
20
|
## π₯οΈ Try the Desktop Commander App (Beta)
|
|
23
21
|
|
|
24
22
|
**Want a better experience?** The Desktop Commander App gives you everything the MCP server does, plus:
|
|
@@ -335,9 +333,11 @@ Desktop Commander works with any MCP-compatible client. The standard JSON config
|
|
|
335
333
|
Add this to your client's MCP configuration file at the locations below:
|
|
336
334
|
|
|
337
335
|
<details>
|
|
338
|
-
<summary><b>Cursor</b></summary>
|
|
336
|
+
<summary><b>Cursor</b></summary><br>
|
|
337
|
+
|
|
338
|
+
[](https://cursor.com/en-US/install-mcp?name=desktop-commander&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB3b25kZXJ3aHktZXIvZGVza3RvcC1jb21tYW5kZXJAbGF0ZXN0Il19)
|
|
339
339
|
|
|
340
|
-
[
|
|
340
|
+
[View MCP Server in Directory](https://cursor.directory/mcp/desktop-commander-mcp)
|
|
341
341
|
|
|
342
342
|
Or add manually to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` in your project folder (project-specific).
|
|
343
343
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFile, readMultipleFiles, writeFile, createDirectory, listDirectory, moveFile, getFileInfo, writePdf } from '../tools/filesystem.js';
|
|
1
|
+
import { readFile, readMultipleFiles, writeFile, createDirectory, listDirectory, moveFile, getFileInfo, writePdf, getDefaultEditorMetadata } from '../tools/filesystem.js';
|
|
2
2
|
import { withTimeout } from '../utils/withTimeout.js';
|
|
3
3
|
import { createErrorResponse } from '../error-handlers.js';
|
|
4
4
|
import { configManager } from '../config-manager.js';
|
|
@@ -99,13 +99,20 @@ export async function handleReadFile(args) {
|
|
|
99
99
|
fileName: path.basename(resolvedFilePath),
|
|
100
100
|
filePath: resolvedFilePath,
|
|
101
101
|
fileType: 'unsupported',
|
|
102
|
+
sourceTool: 'read_file',
|
|
103
|
+
...await getDefaultEditorMetadata(resolvedFilePath),
|
|
104
|
+
content: pdfContent
|
|
105
|
+
.filter((item) => item.type === "text")
|
|
106
|
+
.map((item) => item.text)
|
|
107
|
+
.join("\n"),
|
|
102
108
|
},
|
|
103
109
|
};
|
|
104
110
|
}
|
|
105
111
|
// Handle image files
|
|
106
112
|
if (fileResult.metadata?.isImage) {
|
|
107
|
-
//
|
|
108
|
-
//
|
|
113
|
+
// Return the image bytes in the MCP content array so the host model can
|
|
114
|
+
// actually see the image. structuredContent additionally carries the bytes
|
|
115
|
+
// for the preview widget to render.
|
|
109
116
|
const imageData = typeof fileResult.content === 'string'
|
|
110
117
|
? fileResult.content
|
|
111
118
|
: fileResult.content.toString('base64');
|
|
@@ -115,12 +122,20 @@ export async function handleReadFile(args) {
|
|
|
115
122
|
{
|
|
116
123
|
type: "text",
|
|
117
124
|
text: imageSummary
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
type: "image",
|
|
128
|
+
data: imageData,
|
|
129
|
+
mimeType: fileResult.mimeType
|
|
118
130
|
}
|
|
119
131
|
],
|
|
120
132
|
structuredContent: {
|
|
121
133
|
fileName: path.basename(resolvedFilePath),
|
|
122
134
|
filePath: resolvedFilePath,
|
|
123
135
|
fileType: 'image',
|
|
136
|
+
sourceTool: 'read_file',
|
|
137
|
+
...await getDefaultEditorMetadata(resolvedFilePath),
|
|
138
|
+
content: imageData,
|
|
124
139
|
imageData,
|
|
125
140
|
mimeType: fileResult.mimeType
|
|
126
141
|
}
|
|
@@ -140,6 +155,9 @@ export async function handleReadFile(args) {
|
|
|
140
155
|
fileName: path.basename(resolvedFilePath),
|
|
141
156
|
filePath: resolvedFilePath,
|
|
142
157
|
fileType,
|
|
158
|
+
sourceTool: 'read_file',
|
|
159
|
+
...await getDefaultEditorMetadata(resolvedFilePath),
|
|
160
|
+
content: textContent,
|
|
143
161
|
},
|
|
144
162
|
};
|
|
145
163
|
}
|
|
@@ -246,6 +264,8 @@ export async function handleWriteFile(args) {
|
|
|
246
264
|
fileName: path.basename(resolvedWritePath),
|
|
247
265
|
filePath: resolvedWritePath,
|
|
248
266
|
fileType: resolvePreviewFileType(resolvedWritePath),
|
|
267
|
+
sourceTool: 'write_file',
|
|
268
|
+
...await getDefaultEditorMetadata(resolvedWritePath),
|
|
249
269
|
},
|
|
250
270
|
};
|
|
251
271
|
}
|
|
@@ -287,6 +307,11 @@ export async function handleListDirectory(args) {
|
|
|
287
307
|
fileName: path.basename(resolvedPath),
|
|
288
308
|
filePath: resolvedPath,
|
|
289
309
|
fileType: 'directory',
|
|
310
|
+
sourceTool: 'list_directory',
|
|
311
|
+
// Carry the listing in structuredContent too. Chat reads the text
|
|
312
|
+
// content array, but structuredContent-only consumers (e.g. Cowork)
|
|
313
|
+
// render from here and would otherwise show an empty directory.
|
|
314
|
+
content: resultText,
|
|
290
315
|
},
|
|
291
316
|
};
|
|
292
317
|
}
|
package/dist/server.d.ts
CHANGED
package/dist/server.js
CHANGED
|
@@ -21,8 +21,9 @@ import { handleWelcomePageOnboarding } from './utils/welcome-onboarding.js';
|
|
|
21
21
|
import { VERSION } from './version.js';
|
|
22
22
|
import { capture, capture_call_tool } from "./utils/capture.js";
|
|
23
23
|
import { logToStderr, logger } from './utils/logger.js';
|
|
24
|
-
import { buildUiToolMeta, CONFIG_EDITOR_RESOURCE_URI, FILE_PREVIEW_RESOURCE_URI } from './ui/contracts.js';
|
|
24
|
+
import { buildUiToolMeta, CONFIG_EDITOR_RESOURCE_URI, FILE_PREVIEW_RESOURCE_URI, } from './ui/contracts.js';
|
|
25
25
|
import { listUiResources, readUiResource } from './ui/resources.js';
|
|
26
|
+
import { shouldShowMcpUiPreviews } from './utils/mcp-ui-ab-test.js';
|
|
26
27
|
// Store startup messages to send after initialization
|
|
27
28
|
const deferredMessages = [];
|
|
28
29
|
function deferLog(level, message) {
|
|
@@ -70,6 +71,21 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
|
70
71
|
});
|
|
71
72
|
// Store current client info (simple variable)
|
|
72
73
|
let currentClient = { name: 'uninitialized', version: 'uninitialized' };
|
|
74
|
+
// Tracks whether the in-flight tool call originated from a remote device.
|
|
75
|
+
// Mirrors the module-level `currentClient` pattern so that telemetry events
|
|
76
|
+
// emitted deeper inside tool handlers (e.g. server_start_process,
|
|
77
|
+
// server_read_file) can be attributed to the remote path. The CallTool
|
|
78
|
+
// handler sets this on every call (true when _meta.remote is present,
|
|
79
|
+
// false otherwise) so the flag never leaks from a remote call to a
|
|
80
|
+
// subsequent local call.
|
|
81
|
+
let currentCallIsRemote = false;
|
|
82
|
+
/**
|
|
83
|
+
* Set whether the current tool call is from a remote device.
|
|
84
|
+
* Called once per tool call by the CallTool handler.
|
|
85
|
+
*/
|
|
86
|
+
function setCurrentCallIsRemote(isRemote) {
|
|
87
|
+
currentCallIsRemote = isRemote;
|
|
88
|
+
}
|
|
73
89
|
/**
|
|
74
90
|
* Unified way to update client information
|
|
75
91
|
*/
|
|
@@ -131,7 +147,7 @@ server.setRequestHandler(InitializeRequestSchema, async (request) => {
|
|
|
131
147
|
}
|
|
132
148
|
});
|
|
133
149
|
// Export current client info for access by other modules
|
|
134
|
-
export { currentClient };
|
|
150
|
+
export { currentClient, currentCallIsRemote };
|
|
135
151
|
deferLog('info', 'Setting up request handlers...');
|
|
136
152
|
/**
|
|
137
153
|
* Check if a tool should be included based on current client
|
|
@@ -150,6 +166,7 @@ function shouldIncludeTool(toolName) {
|
|
|
150
166
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
151
167
|
try {
|
|
152
168
|
// logToStderr('debug', 'Generating tools list...');
|
|
169
|
+
const showMcpUiPreviews = await shouldShowMcpUiPreviews();
|
|
153
170
|
// Build complete tools array
|
|
154
171
|
const allTools = [
|
|
155
172
|
// Configuration tools
|
|
@@ -169,7 +186,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
169
186
|
- systemInfo (operating system and environment details)
|
|
170
187
|
${CMD_PREFIX_DESCRIPTION}`,
|
|
171
188
|
inputSchema: zodToJsonSchema(GetConfigArgsSchema),
|
|
172
|
-
_meta: buildUiToolMeta(CONFIG_EDITOR_RESOURCE_URI, true),
|
|
189
|
+
_meta: buildUiToolMeta(CONFIG_EDITOR_RESOURCE_URI, true, showMcpUiPreviews),
|
|
173
190
|
annotations: {
|
|
174
191
|
title: "Get Configuration",
|
|
175
192
|
readOnlyHint: true,
|
|
@@ -262,7 +279,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
262
279
|
${PATH_GUIDANCE}
|
|
263
280
|
${CMD_PREFIX_DESCRIPTION}`,
|
|
264
281
|
inputSchema: zodToJsonSchema(ReadFileArgsSchema),
|
|
265
|
-
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true),
|
|
282
|
+
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true, showMcpUiPreviews),
|
|
266
283
|
annotations: {
|
|
267
284
|
title: "Read File or URL",
|
|
268
285
|
readOnlyHint: true,
|
|
@@ -330,7 +347,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
330
347
|
${PATH_GUIDANCE}
|
|
331
348
|
${CMD_PREFIX_DESCRIPTION}`,
|
|
332
349
|
inputSchema: zodToJsonSchema(WriteFileArgsSchema),
|
|
333
|
-
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true),
|
|
350
|
+
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true, showMcpUiPreviews),
|
|
334
351
|
annotations: {
|
|
335
352
|
title: "Write File",
|
|
336
353
|
readOnlyHint: false,
|
|
@@ -445,12 +462,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
445
462
|
[FILE] src/tools/filesystem.ts
|
|
446
463
|
|
|
447
464
|
If a directory cannot be accessed, it will show [DENIED] instead.
|
|
465
|
+
If a path does not exist, it will show [NOT_FOUND] instead.
|
|
448
466
|
Only works within allowed directories.
|
|
449
467
|
|
|
450
468
|
${PATH_GUIDANCE}
|
|
451
469
|
${CMD_PREFIX_DESCRIPTION}`,
|
|
452
470
|
inputSchema: zodToJsonSchema(ListDirectoryArgsSchema),
|
|
453
|
-
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true),
|
|
471
|
+
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true, showMcpUiPreviews),
|
|
454
472
|
annotations: {
|
|
455
473
|
title: "List Directory Contents",
|
|
456
474
|
readOnlyHint: true,
|
|
@@ -709,7 +727,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
709
727
|
${PATH_GUIDANCE}
|
|
710
728
|
${CMD_PREFIX_DESCRIPTION}`,
|
|
711
729
|
inputSchema: zodToJsonSchema(EditBlockArgsSchema),
|
|
712
|
-
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true),
|
|
730
|
+
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true, showMcpUiPreviews),
|
|
713
731
|
annotations: {
|
|
714
732
|
title: "Edit Block",
|
|
715
733
|
readOnlyHint: false,
|
|
@@ -1090,13 +1108,21 @@ import * as handlers from './handlers/index.js';
|
|
|
1090
1108
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1091
1109
|
const { name, arguments: args } = request.params;
|
|
1092
1110
|
const startTime = Date.now();
|
|
1111
|
+
// Hoisted above the try so the finally block can read them when emitting the
|
|
1112
|
+
// server_call_tool completion event (duration + status), even on the crash path.
|
|
1113
|
+
let telemetryData = { tool_name: name };
|
|
1114
|
+
let result;
|
|
1115
|
+
let isError = false;
|
|
1093
1116
|
try {
|
|
1094
|
-
//
|
|
1095
|
-
const telemetryData = { tool_name: name };
|
|
1096
|
-
// Extract metadata from _meta field if present
|
|
1117
|
+
// telemetryData declared above; extract metadata from _meta field if present
|
|
1097
1118
|
const metadata = request.params._meta;
|
|
1119
|
+
// Reset remote attribution for every call so a prior remote call never
|
|
1120
|
+
// leaks its flag onto a subsequent local call. Set to true only when
|
|
1121
|
+
// this call carries the remote marker in _meta.
|
|
1122
|
+
const isRemoteCall = !!(metadata && typeof metadata === 'object' && metadata.remote);
|
|
1123
|
+
setCurrentCallIsRemote(isRemoteCall);
|
|
1098
1124
|
if (metadata && typeof metadata === 'object') {
|
|
1099
|
-
// add remote flag if present (convert to string for
|
|
1125
|
+
// add remote flag if present (convert to string for telemetry)
|
|
1100
1126
|
if (metadata.remote) {
|
|
1101
1127
|
telemetryData.remote = String(metadata.remote);
|
|
1102
1128
|
}
|
|
@@ -1123,11 +1149,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1123
1149
|
telemetryData.prompt_id = promptArgs.promptId;
|
|
1124
1150
|
}
|
|
1125
1151
|
}
|
|
1126
|
-
capture_call_tool('server_call_tool', telemetryData);
|
|
1127
1152
|
// Track tool call
|
|
1128
1153
|
trackToolCall(name, args);
|
|
1129
1154
|
// Using a more structured approach with dedicated handlers
|
|
1130
|
-
|
|
1155
|
+
// (result is declared above so the finally block can read execution status)
|
|
1131
1156
|
switch (name) {
|
|
1132
1157
|
// Config tools
|
|
1133
1158
|
case "get_config":
|
|
@@ -1319,6 +1344,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1319
1344
|
}
|
|
1320
1345
|
// Add tool call to history (exclude only get_recent_tool_calls to prevent recursion)
|
|
1321
1346
|
const duration = Date.now() - startTime;
|
|
1347
|
+
isError = !!result.isError;
|
|
1322
1348
|
const EXCLUDED_TOOLS = [
|
|
1323
1349
|
'get_recent_tool_calls',
|
|
1324
1350
|
'track_ui_event'
|
|
@@ -1412,6 +1438,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1412
1438
|
return result;
|
|
1413
1439
|
}
|
|
1414
1440
|
catch (error) {
|
|
1441
|
+
isError = true;
|
|
1415
1442
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1416
1443
|
// Track the failure
|
|
1417
1444
|
await usageTracker.trackFailure(name);
|
|
@@ -1423,6 +1450,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1423
1450
|
isError: true,
|
|
1424
1451
|
};
|
|
1425
1452
|
}
|
|
1453
|
+
finally {
|
|
1454
|
+
// Single tool-call telemetry event, fired AFTER execution so it can carry
|
|
1455
|
+
// timing. In a finally so it still fires on the hard-crash path (the catch
|
|
1456
|
+
// above). Only missed if a tool never returns or throws (a true hang).
|
|
1457
|
+
capture_call_tool('server_call_tool', {
|
|
1458
|
+
...telemetryData,
|
|
1459
|
+
duration_ms: Date.now() - startTime,
|
|
1460
|
+
is_error: String(isError),
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1426
1463
|
});
|
|
1427
1464
|
// Add no-op handlers so Visual Studio initialization succeeds
|
|
1428
1465
|
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({ resourceTemplates: [] }));
|
|
@@ -10,10 +10,9 @@ import { version as nodeVersion } from 'process';
|
|
|
10
10
|
import * as https from 'https';
|
|
11
11
|
import { randomUUID } from 'crypto';
|
|
12
12
|
|
|
13
|
-
//
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const GA_BASE_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
|
|
13
|
+
// Telemetry proxy configuration
|
|
14
|
+
const TELEMETRY_PROXY_URL = 'https://telemetry.desktopcommander.app/mp/collect';
|
|
15
|
+
const TELEMETRY_PROXY_FALLBACK_URL = 'https://dc-telemetry-proxy-83847352264.europe-west1.run.app/mp/collect';
|
|
17
16
|
|
|
18
17
|
// Generate a unique anonymous ID using UUID - consistent with privacy policy
|
|
19
18
|
let uniqueUserId = 'unknown';
|
|
@@ -302,11 +301,6 @@ async function enhancedGetTrackingProperties(additionalProps = {}) {
|
|
|
302
301
|
async function trackEvent(eventName, additionalProps = {}) {
|
|
303
302
|
const trackingStep = addSetupStep(`track_event_${eventName}`);
|
|
304
303
|
|
|
305
|
-
if (!GA_MEASUREMENT_ID || !GA_API_SECRET) {
|
|
306
|
-
updateSetupStep(trackingStep, 'skipped', new Error('GA not configured'));
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
304
|
// Add retry capability
|
|
311
305
|
const maxRetries = 2;
|
|
312
306
|
let attempt = 0;
|
|
@@ -319,7 +313,7 @@ async function trackEvent(eventName, additionalProps = {}) {
|
|
|
319
313
|
// Get enriched properties
|
|
320
314
|
const eventProperties = await enhancedGetTrackingProperties(additionalProps);
|
|
321
315
|
|
|
322
|
-
// Prepare
|
|
316
|
+
// Prepare telemetry payload
|
|
323
317
|
const payload = {
|
|
324
318
|
client_id: uniqueUserId,
|
|
325
319
|
non_personalized_ads: false,
|
|
@@ -330,7 +324,7 @@ async function trackEvent(eventName, additionalProps = {}) {
|
|
|
330
324
|
}]
|
|
331
325
|
};
|
|
332
326
|
|
|
333
|
-
// Send to
|
|
327
|
+
// Send to telemetry proxy
|
|
334
328
|
const postData = JSON.stringify(payload);
|
|
335
329
|
|
|
336
330
|
const options = {
|
|
@@ -341,44 +335,8 @@ async function trackEvent(eventName, additionalProps = {}) {
|
|
|
341
335
|
}
|
|
342
336
|
};
|
|
343
337
|
|
|
344
|
-
const result = await
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
// Set timeout to prevent blocking
|
|
348
|
-
const timeoutId = setTimeout(() => {
|
|
349
|
-
req.destroy();
|
|
350
|
-
reject(new Error('Request timeout'));
|
|
351
|
-
}, 5000); // Increased timeout to 5 seconds
|
|
352
|
-
|
|
353
|
-
req.on('error', (error) => {
|
|
354
|
-
clearTimeout(timeoutId);
|
|
355
|
-
reject(error);
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
req.on('response', (res) => {
|
|
359
|
-
clearTimeout(timeoutId);
|
|
360
|
-
let data = '';
|
|
361
|
-
|
|
362
|
-
res.on('data', (chunk) => {
|
|
363
|
-
data += chunk;
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
res.on('error', (error) => {
|
|
367
|
-
reject(error);
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
res.on('end', () => {
|
|
371
|
-
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
372
|
-
resolve({ success: true, data });
|
|
373
|
-
} else {
|
|
374
|
-
reject(new Error(`HTTP error ${res.statusCode}: ${data}`));
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
req.write(postData);
|
|
380
|
-
req.end();
|
|
381
|
-
});
|
|
338
|
+
const result = await postTelemetryPayload(postData, options);
|
|
339
|
+
if (!result.success) throw new Error('Telemetry proxy request failed');
|
|
382
340
|
|
|
383
341
|
updateSetupStep(trackingStep, 'completed');
|
|
384
342
|
return result;
|
|
@@ -397,6 +355,54 @@ async function trackEvent(eventName, additionalProps = {}) {
|
|
|
397
355
|
return false;
|
|
398
356
|
}
|
|
399
357
|
|
|
358
|
+
async function postTelemetryPayload(postData, options) {
|
|
359
|
+
for (const endpoint of [TELEMETRY_PROXY_URL, TELEMETRY_PROXY_FALLBACK_URL]) {
|
|
360
|
+
const result = await new Promise((resolve) => {
|
|
361
|
+
let settled = false;
|
|
362
|
+
let timeoutId;
|
|
363
|
+
const finish = (result) => {
|
|
364
|
+
if (settled) return;
|
|
365
|
+
settled = true;
|
|
366
|
+
clearTimeout(timeoutId);
|
|
367
|
+
resolve(result);
|
|
368
|
+
};
|
|
369
|
+
const req = https.request(endpoint, options);
|
|
370
|
+
|
|
371
|
+
timeoutId = setTimeout(() => {
|
|
372
|
+
req.destroy();
|
|
373
|
+
finish({ success: false, data: '' });
|
|
374
|
+
}, 5000);
|
|
375
|
+
|
|
376
|
+
req.on('error', () => {
|
|
377
|
+
finish({ success: false, data: '' });
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
req.on('response', (res) => {
|
|
381
|
+
let data = '';
|
|
382
|
+
res.on('data', (chunk) => {
|
|
383
|
+
data += chunk;
|
|
384
|
+
});
|
|
385
|
+
res.on('error', () => {
|
|
386
|
+
finish({ success: false, data: '' });
|
|
387
|
+
});
|
|
388
|
+
res.on('end', () => {
|
|
389
|
+
finish({ success: res.statusCode >= 200 && res.statusCode < 300, data });
|
|
390
|
+
});
|
|
391
|
+
res.on('close', () => {
|
|
392
|
+
finish({ success: false, data: '' });
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
req.write(postData);
|
|
397
|
+
req.end();
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
if (result.success) return result;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return { success: false, data: '' };
|
|
404
|
+
}
|
|
405
|
+
|
|
400
406
|
// Ensure tracking completes before process exits
|
|
401
407
|
async function ensureTrackingCompleted(eventName, additionalProps = {}, timeoutMs = 6000) {
|
|
402
408
|
return new Promise(async (resolve) => {
|
|
@@ -933,4 +939,4 @@ if (process.argv.length >= 2 && process.argv[1] === fileURLToPath(import.meta.ur
|
|
|
933
939
|
process.exit(1);
|
|
934
940
|
}, 1000);
|
|
935
941
|
});
|
|
936
|
-
}
|
|
942
|
+
}
|
package/dist/terminal-manager.js
CHANGED
|
@@ -4,6 +4,34 @@ import { DEFAULT_COMMAND_TIMEOUT } from './config.js';
|
|
|
4
4
|
import { configManager } from './config-manager.js';
|
|
5
5
|
import { capture } from "./utils/capture.js";
|
|
6
6
|
import { analyzeProcessState } from './utils/process-detection.js';
|
|
7
|
+
/**
|
|
8
|
+
* Standard Windows PATHEXT value, used to repair a corrupted PATHEXT before
|
|
9
|
+
* spawning child shells.
|
|
10
|
+
*
|
|
11
|
+
* On some Windows Claude Desktop / DXT launches the server process inherits a
|
|
12
|
+
* broken PATHEXT (observed as ".CPL" only). Because we build the child env from
|
|
13
|
+
* { ...process.env }, that broken value would propagate into every spawned
|
|
14
|
+
* shell, stripping ".EXE" and breaking resolution of git / node / python / rg /
|
|
15
|
+
* etc. (and even full-path .exe invocations under PowerShell). See issue #481.
|
|
16
|
+
*/
|
|
17
|
+
const STANDARD_PATHEXT = '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC';
|
|
18
|
+
/**
|
|
19
|
+
* Return a healthy PATHEXT for spawned Windows shells.
|
|
20
|
+
* - Unset -> use the standard list.
|
|
21
|
+
* - Missing ".EXE" -> corrupted; merge the standard list with whatever was
|
|
22
|
+
* present (preserves any extra extensions, order-stable).
|
|
23
|
+
* - Otherwise -> leave the inherited value untouched.
|
|
24
|
+
*/
|
|
25
|
+
function getRepairedPathExt() {
|
|
26
|
+
const current = process.env.PATHEXT;
|
|
27
|
+
if (!current)
|
|
28
|
+
return STANDARD_PATHEXT;
|
|
29
|
+
const exts = current.split(';').map(e => e.trim().toUpperCase()).filter(Boolean);
|
|
30
|
+
if (!exts.includes('.EXE')) {
|
|
31
|
+
return [...new Set([...STANDARD_PATHEXT.split(';'), ...exts])].join(';');
|
|
32
|
+
}
|
|
33
|
+
return current;
|
|
34
|
+
}
|
|
7
35
|
/**
|
|
8
36
|
* Get the appropriate spawn configuration for a given shell
|
|
9
37
|
* This handles login shell flags for different shell types
|
|
@@ -39,6 +67,7 @@ function getShellSpawnArgs(shellPath, command) {
|
|
|
39
67
|
return {
|
|
40
68
|
executable: shellPath,
|
|
41
69
|
args: ['/c', command],
|
|
70
|
+
windowsVerbatim: true,
|
|
42
71
|
useShellOption: false
|
|
43
72
|
};
|
|
44
73
|
}
|
|
@@ -143,6 +172,23 @@ export class TerminalManager {
|
|
|
143
172
|
windowsHide: true // Prevent visible console windows on Windows
|
|
144
173
|
};
|
|
145
174
|
}
|
|
175
|
+
// Repair PATHEXT on Windows before spawning. On some Windows DXT launches
|
|
176
|
+
// the server process inherits a corrupted PATHEXT (e.g. ".CPL"), which we
|
|
177
|
+
// would otherwise propagate via { ...process.env } and break command
|
|
178
|
+
// resolution (git, node, python, rg, ...) in the spawned shell. See #481.
|
|
179
|
+
if (process.platform === 'win32' && spawnOptions.env) {
|
|
180
|
+
spawnOptions.env.PATHEXT = getRepairedPathExt();
|
|
181
|
+
}
|
|
182
|
+
// On Windows, when we invoke cmd.exe directly and pass the user's command as a
|
|
183
|
+
// single argument, Node/libuv applies MSVCRT-style quoting that escapes embedded
|
|
184
|
+
// double quotes as \" . cmd.exe does not understand that escaping, so any command
|
|
185
|
+
// containing quotes (e.g. a quoted path with spaces like "C:\Program Files\app.exe")
|
|
186
|
+
// is corrupted before the shell ever parses it. Passing arguments verbatim lets
|
|
187
|
+
// cmd handle its own quoting. Scoped to shells that set windowsVerbatim (cmd only)
|
|
188
|
+
// because PowerShell/pwsh have different quote rules and must NOT use verbatim.
|
|
189
|
+
if (process.platform === 'win32' && spawnConfig.windowsVerbatim) {
|
|
190
|
+
spawnOptions.windowsVerbatimArguments = true;
|
|
191
|
+
}
|
|
146
192
|
// Spawn the process with appropriate arguments
|
|
147
193
|
const childProcess = spawn(spawnConfig.executable, spawnConfig.args, spawnOptions);
|
|
148
194
|
let output = '';
|
package/dist/tools/edit.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* 2. Make this file a thin dispatch layer that routes to appropriate FileHandler
|
|
15
15
|
* 3. Unify the editRange() signature to handle both text search/replace and structured edits
|
|
16
16
|
*/
|
|
17
|
-
import { writeFile, readFileInternal, validatePath } from './filesystem.js';
|
|
17
|
+
import { getDefaultEditorMetadata, writeFile, readFileInternal, validatePath } from './filesystem.js';
|
|
18
18
|
import { recursiveFuzzyIndexOf, getSimilarityRatio } from './fuzzySearch.js';
|
|
19
19
|
import { capture } from '../utils/capture.js';
|
|
20
20
|
import { createErrorResponse } from '../error-handlers.js';
|
|
@@ -180,6 +180,8 @@ RECOMMENDATION: For large search/replace operations, consider breaking them into
|
|
|
180
180
|
fileName: path.basename(resolvedEditPath),
|
|
181
181
|
filePath: resolvedEditPath,
|
|
182
182
|
fileType: resolvePreviewFileType(resolvedEditPath),
|
|
183
|
+
sourceTool: 'edit_block',
|
|
184
|
+
...await getDefaultEditorMetadata(resolvedEditPath),
|
|
183
185
|
},
|
|
184
186
|
};
|
|
185
187
|
}
|
|
@@ -368,6 +370,8 @@ export async function handleEditBlock(args) {
|
|
|
368
370
|
fileName: path.basename(resolvedRangePath),
|
|
369
371
|
filePath: resolvedRangePath,
|
|
370
372
|
fileType: resolvePreviewFileType(resolvedRangePath),
|
|
373
|
+
sourceTool: 'edit_block',
|
|
374
|
+
...await getDefaultEditorMetadata(resolvedRangePath),
|
|
371
375
|
},
|
|
372
376
|
};
|
|
373
377
|
}
|
|
@@ -403,6 +407,8 @@ export async function handleEditBlock(args) {
|
|
|
403
407
|
fileName: path.basename(resolvedEditRangePath),
|
|
404
408
|
filePath: resolvedEditRangePath,
|
|
405
409
|
fileType: resolvePreviewFileType(resolvedEditRangePath),
|
|
410
|
+
sourceTool: 'edit_block',
|
|
411
|
+
...await getDefaultEditorMetadata(resolvedEditRangePath),
|
|
406
412
|
},
|
|
407
413
|
};
|
|
408
414
|
}
|
|
@@ -71,3 +71,8 @@ export declare function getFileInfo(filePath: string): Promise<Record<string, an
|
|
|
71
71
|
* @param options Options for PDF generation or modification. For modification, can include `sourcePdf`.
|
|
72
72
|
*/
|
|
73
73
|
export declare function writePdf(filePath: string, content: string | PdfOperations[], outputPath?: string, options?: any): Promise<void>;
|
|
74
|
+
type DefaultEditorMetadata = {
|
|
75
|
+
defaultEditorName?: string;
|
|
76
|
+
defaultEditorPath?: string;
|
|
77
|
+
};
|
|
78
|
+
export declare function getDefaultEditorMetadata(filePath: string): Promise<DefaultEditorMetadata>;
|