@wonderwhy-er/desktop-commander 0.1.35 → 0.1.37

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 (47) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +88 -27
  3. package/dist/command-manager.js +1 -1
  4. package/dist/config-manager.d.ts +1 -0
  5. package/dist/config-manager.js +21 -4
  6. package/dist/config.d.ts +2 -2
  7. package/dist/config.js +2 -3
  8. package/dist/error-handlers.js +1 -1
  9. package/dist/handlers/edit-search-handlers.d.ts +3 -1
  10. package/dist/handlers/edit-search-handlers.js +6 -12
  11. package/dist/handlers/filesystem-handlers.js +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/polyform-license-src/edit/edit.d.ts +15 -0
  14. package/dist/polyform-license-src/edit/edit.js +163 -0
  15. package/dist/polyform-license-src/edit/fuzzySearch.d.ts +30 -0
  16. package/dist/polyform-license-src/edit/fuzzySearch.js +121 -0
  17. package/dist/polyform-license-src/edit/handlers.d.ts +16 -0
  18. package/dist/polyform-license-src/edit/handlers.js +24 -0
  19. package/dist/polyform-license-src/edit/index.d.ts +12 -0
  20. package/dist/polyform-license-src/edit/index.js +13 -0
  21. package/dist/polyform-license-src/edit/schemas.d.ts +25 -0
  22. package/dist/polyform-license-src/edit/schemas.js +16 -0
  23. package/dist/polyform-license-src/index.d.ts +9 -0
  24. package/dist/polyform-license-src/index.js +10 -0
  25. package/dist/server.js +71 -43
  26. package/dist/setup-claude-server.js +549 -288
  27. package/dist/terminal-manager.js +4 -2
  28. package/dist/tools/edit.d.ts +8 -6
  29. package/dist/tools/edit.js +161 -34
  30. package/dist/tools/execute.js +2 -2
  31. package/dist/tools/filesystem.js +59 -10
  32. package/dist/tools/fuzzySearch.d.ts +22 -0
  33. package/dist/tools/fuzzySearch.js +113 -0
  34. package/dist/tools/pdf-reader.d.ts +13 -0
  35. package/dist/tools/pdf-reader.js +214 -0
  36. package/dist/tools/schemas.d.ts +12 -3
  37. package/dist/tools/schemas.js +5 -2
  38. package/dist/tools/search.js +5 -4
  39. package/dist/utils/capture.d.ts +15 -0
  40. package/dist/utils/capture.js +175 -0
  41. package/dist/utils/withTimeout.d.ts +11 -0
  42. package/dist/utils/withTimeout.js +52 -0
  43. package/dist/utils.d.ts +10 -1
  44. package/dist/utils.js +99 -26
  45. package/dist/version.d.ts +1 -1
  46. package/dist/version.js +1 -1
  47. package/package.json +2 -2
@@ -1,31 +1,35 @@
1
1
  import { homedir, platform } from 'os';
2
2
  import { join } from 'path';
3
- import { readFileSync, writeFileSync, existsSync, appendFileSync } from 'fs';
3
+ import { readFileSync, writeFileSync, existsSync, appendFileSync, mkdirSync } from 'fs';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { dirname } from 'path';
6
6
  import { exec } from "node:child_process";
7
7
  import { version as nodeVersion } from 'process';
8
8
  import * as https from 'https';
9
+ import { randomUUID } from 'crypto';
9
10
 
10
11
  // Google Analytics configuration
11
12
  const GA_MEASUREMENT_ID = 'G-NGGDNL0K4L'; // Replace with your GA4 Measurement ID
12
- const GA_API_SECRET = '5M0mC--2S_6t94m8WrI60A'; // Replace with your GA4 API Secret
13
+ const GA_API_SECRET = '5M0mC--2S_6t94m8WrI60A'; // Replace with your GA4 API Secre
13
14
  const GA_BASE_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
14
15
 
15
- // Optional analytics - will gracefully degrade if dependencies aren't available
16
+ // Generate a unique anonymous ID using UUID - consistent with privacy policy
16
17
  let uniqueUserId = 'unknown';
17
18
 
18
19
  try {
19
- // Only dependency is node-machine-id
20
- const machineIdModule = await import('node-machine-id');
21
-
22
- // Get a unique user ID
23
- uniqueUserId = machineIdModule.machineIdSync();
20
+ // Use randomUUID from crypto module instead of machine-id
21
+ // This generates a truly random identifier not tied to hardware
22
+ uniqueUserId = randomUUID();
24
23
  } catch (error) {
25
- // Fall back to a semi-unique identifier if machine-id is not available
26
- uniqueUserId = `${platform()}-${process.env.USER || process.env.USERNAME || 'unknown'}-${Date.now()}`;
24
+ // Fall back to a semi-unique identifier if UUID generation fails
25
+ uniqueUserId = `random-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
27
26
  }
28
27
 
28
+ // Setup tracking
29
+ let setupSteps = []; // Track setup progress
30
+ let setupStartTime = Date.now();
31
+
32
+
29
33
  // Function to get npm version
30
34
  async function getNpmVersion() {
31
35
  try {
@@ -52,7 +56,7 @@ const getVersion = async () => {
52
56
  }
53
57
  };
54
58
 
55
- // Function to detect shell environment
59
+ // Function to detect shell environmen
56
60
  function detectShell() {
57
61
  // Check for Windows shells
58
62
  if (process.platform === 'win32') {
@@ -62,7 +66,7 @@ function detectShell() {
62
66
  if (process.env.TERM?.includes('xterm')) return 'xterm-on-windows';
63
67
  if (process.env.ComSpec?.toLowerCase().includes('powershell')) return 'powershell';
64
68
  if (process.env.PROMPT) return 'cmd';
65
-
69
+
66
70
  // WSL detection
67
71
  if (process.env.WSL_DISTRO_NAME || process.env.WSLENV) {
68
72
  return `wsl-${process.env.WSL_DISTRO_NAME || 'unknown'}`;
@@ -70,7 +74,7 @@ function detectShell() {
70
74
 
71
75
  return 'windows-unknown';
72
76
  }
73
-
77
+
74
78
  // Unix-based shells
75
79
  if (process.env.SHELL) {
76
80
  const shellPath = process.env.SHELL.toLowerCase();
@@ -82,30 +86,30 @@ function detectShell() {
82
86
  if (shellPath.includes('dash')) return 'dash';
83
87
  return `other-unix-${shellPath.split('/').pop()}`;
84
88
  }
85
-
89
+
86
90
  // Terminal emulators and IDE terminals
87
91
  if (process.env.TERM_PROGRAM) {
88
92
  return process.env.TERM_PROGRAM.toLowerCase();
89
93
  }
90
-
94
+
91
95
  return 'unknown-shell';
92
96
  }
93
97
 
94
98
  // Function to determine execution context
95
99
  function getExecutionContext() {
96
100
  // Check if running from npx
97
- const isNpx = process.env.npm_lifecycle_event === 'npx' ||
101
+ const isNpx = process.env.npm_lifecycle_event === 'npx' ||
98
102
  process.env.npm_execpath?.includes('npx') ||
99
103
  process.env._?.includes('npx') ||
100
104
  import.meta.url.includes('node_modules');
101
-
105
+
102
106
  // Check if installed globally
103
107
  const isGlobal = process.env.npm_config_global === 'true' ||
104
108
  process.argv[1]?.includes('node_modules/.bin');
105
-
109
+
106
110
  // Check if it's run from a script in package.json
107
111
  const isNpmScript = !!process.env.npm_lifecycle_script;
108
-
112
+
109
113
  return {
110
114
  runMethod: isNpx ? 'npx' : (isGlobal ? 'global' : (isNpmScript ? 'npm_script' : 'direct')),
111
115
  isCI: !!process.env.CI || !!process.env.GITHUB_ACTIONS || !!process.env.TRAVIS || !!process.env.CIRCLECI,
@@ -115,135 +119,170 @@ function getExecutionContext() {
115
119
 
116
120
  // Helper function to get standard environment properties for tracking
117
121
  let npmVersionCache = null;
118
- async function getTrackingProperties(additionalProps = {}) {
119
- if (npmVersionCache === null) {
120
- npmVersionCache = await getNpmVersion();
121
- }
122
-
123
- const context = getExecutionContext();
124
- const version = await getVersion();
125
- return {
126
- platform: platform(),
127
- node_version: nodeVersion,
128
- npm_version: npmVersionCache,
129
- execution_context: context.runMethod,
130
- is_ci: context.isCI,
131
- shell: context.shell,
132
- app_version: version,
133
- engagement_time_msec: "100",
134
- ...additionalProps
135
- };
136
- }
137
122
 
138
- // Helper function for tracking that handles errors gracefully
139
- async function trackEvent(eventName, additionalProps = {}) {
140
- if (!GA_MEASUREMENT_ID || !GA_API_SECRET) return; // Skip tracking if GA isn't configured
141
-
123
+ // Enhanced version with step tracking - will replace the original after initialization
124
+ async function enhancedGetTrackingProperties(additionalProps = {}) {
125
+ const propertiesStep = addSetupStep('get_tracking_properties');
142
126
  try {
143
- // Get enriched properties
144
- const eventProperties = await getTrackingProperties(additionalProps);
145
-
146
- // Prepare GA4 payload
147
- const payload = {
148
- client_id: uniqueUserId,
149
- non_personalized_ads: false,
150
- timestamp_micros: Date.now() * 1000,
151
- events: [{
152
- name: eventName,
153
- params: eventProperties
154
- }]
127
+ if (npmVersionCache === null) {
128
+ npmVersionCache = await getNpmVersion();
129
+ }
130
+
131
+ const context = getExecutionContext();
132
+ const version = await getVersion();
133
+
134
+ updateSetupStep(propertiesStep, 'completed');
135
+ return {
136
+ platform: platform(),
137
+ node_version: nodeVersion,
138
+ npm_version: npmVersionCache,
139
+ execution_context: context.runMethod,
140
+ is_ci: context.isCI,
141
+ shell: context.shell,
142
+ app_version: version,
143
+ engagement_time_msec: "100",
144
+ ...additionalProps
155
145
  };
156
-
157
- // Send to Google Analytics
158
- const postData = JSON.stringify(payload);
159
-
160
- const options = {
161
- method: 'POST',
162
- headers: {
163
- 'Content-Type': 'application/json',
164
- 'Content-Length': Buffer.byteLength(postData)
165
- }
146
+ } catch (error) {
147
+ updateSetupStep(propertiesStep, 'failed', error);
148
+ return {
149
+ platform: platform(),
150
+ node_version: nodeVersion,
151
+ error: error.message,
152
+ engagement_time_msec: "100",
153
+ ...additionalProps
166
154
  };
167
-
168
- return new Promise((resolve) => {
169
- const req = https.request(GA_BASE_URL, options, (res) => {
170
- // Optional response handling
171
- let data = '';
172
- res.on('data', (chunk) => {
173
- data += chunk;
174
- });
175
-
176
- res.on('end', () => {
177
- resolve(true);
178
- });
179
- });
180
-
181
- req.on('error', (error) => {
182
- // Silently fail - we don't want tracking issues to break functionality
183
- resolve(false);
184
- });
185
-
186
- // Set timeout to prevent blocking
187
- req.setTimeout(3000, () => {
188
- req.destroy();
189
- resolve(false);
190
- });
191
-
192
- req.write(postData);
193
- req.end();
194
-
195
- // read response from request
196
- req.on('response', (res) => {
197
- // Optional response handling
198
- let data = '';
199
- res.on('data', (chunk) => {
200
- data += chunk;
201
- });
155
+ }
156
+ }
202
157
 
203
- //response status
204
- res.on('error', (error) => {
205
- resolve(false);
206
- });
207
-
208
- res.on('end', () => {
209
- resolve(true);
210
- });
211
- });
212
- });
213
- } catch (error) {
214
- logToFile(`Error tracking event ${eventName}: ${error}`, true);
215
- // Silently fail if tracking fails - we don't want to break the application
158
+ // Enhanced tracking function with retries and better error handling
159
+ // This replaces the basic implementation for all tracking after initialization
160
+ async function trackEvent(eventName, additionalProps = {}) {
161
+ const trackingStep = addSetupStep(`track_event_${eventName}`);
162
+
163
+ if (!GA_MEASUREMENT_ID || !GA_API_SECRET) {
164
+ updateSetupStep(trackingStep, 'skipped', new Error('GA not configured'));
165
+ return;
166
+ }
167
+
168
+ // Add retry capability
169
+ const maxRetries = 2;
170
+ let attempt = 0;
171
+ let lastError = null;
172
+
173
+ while (attempt <= maxRetries) {
174
+ try {
175
+ attempt++;
176
+
177
+ // Get enriched properties
178
+ const eventProperties = await enhancedGetTrackingProperties(additionalProps);
179
+
180
+ // Prepare GA4 payload
181
+ const payload = {
182
+ client_id: uniqueUserId,
183
+ non_personalized_ads: false,
184
+ timestamp_micros: Date.now() * 1000,
185
+ events: [{
186
+ name: eventName,
187
+ params: eventProperties
188
+ }]
189
+ };
190
+
191
+ // Send to Google Analytics
192
+ const postData = JSON.stringify(payload);
193
+
194
+ const options = {
195
+ method: 'POST',
196
+ headers: {
197
+ 'Content-Type': 'application/json',
198
+ 'Content-Length': Buffer.byteLength(postData)
199
+ }
200
+ };
201
+
202
+ const result = await new Promise((resolve, reject) => {
203
+ const req = https.request(GA_BASE_URL, options);
204
+
205
+ // Set timeout to prevent blocking
206
+ const timeoutId = setTimeout(() => {
207
+ req.destroy();
208
+ reject(new Error('Request timeout'));
209
+ }, 5000); // Increased timeout to 5 seconds
210
+
211
+ req.on('error', (error) => {
212
+ clearTimeout(timeoutId);
213
+ reject(error);
214
+ });
215
+
216
+ req.on('response', (res) => {
217
+ clearTimeout(timeoutId);
218
+ let data = '';
219
+
220
+ res.on('data', (chunk) => {
221
+ data += chunk;
222
+ });
223
+
224
+ res.on('error', (error) => {
225
+ reject(error);
226
+ });
227
+
228
+ res.on('end', () => {
229
+ if (res.statusCode >= 200 && res.statusCode < 300) {
230
+ resolve({ success: true, data });
231
+ } else {
232
+ reject(new Error(`HTTP error ${res.statusCode}: ${data}`));
233
+ }
234
+ });
235
+ });
236
+
237
+ req.write(postData);
238
+ req.end();
239
+ });
240
+
241
+ updateSetupStep(trackingStep, 'completed');
242
+ return result;
243
+
244
+ } catch (error) {
245
+ lastError = error;
246
+ logToFile(`Error tracking event ${eventName} (attempt ${attempt}/${maxRetries + 1}): ${error}`, true);
247
+
248
+ if (attempt <= maxRetries) {
249
+ // Wait before retry (exponential backoff)
250
+ await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
251
+ }
252
+ }
253
+ }
254
+
255
+ // All retries failed
256
+ updateSetupStep(trackingStep, 'failed', lastError);
216
257
  return false;
217
- }
218
258
  }
219
- // Initial tracking
220
- trackEvent('npx_setup_start');
259
+
260
+ // Ensure tracking completes before process exits
261
+ async function ensureTrackingCompleted(eventName, additionalProps = {}, timeoutMs = 6000) {
262
+ return new Promise(async (resolve) => {
263
+ const timeoutId = setTimeout(() => {
264
+ logToFile(`Tracking timeout for ${eventName}`, true);
265
+ resolve(false);
266
+ }, timeoutMs);
267
+
268
+ try {
269
+ await trackEvent(eventName, additionalProps);
270
+ clearTimeout(timeoutId);
271
+ resolve(true);
272
+ } catch (error) {
273
+ clearTimeout(timeoutId);
274
+ logToFile(`Failed to complete tracking for ${eventName}: ${error}`, true);
275
+ resolve(false);
276
+ }
277
+ });
278
+ }
279
+
221
280
 
222
281
  // Fix for Windows ESM path resolution
223
282
  const __filename = fileURLToPath(import.meta.url);
224
283
  const __dirname = dirname(__filename);
225
284
 
226
- // Determine OS and set appropriate config path
227
- const os = platform();
228
- const isWindows = os === 'win32'; // Define isWindows variable
229
- let claudeConfigPath;
230
-
231
- switch (os) {
232
- case 'win32':
233
- claudeConfigPath = join(process.env.APPDATA, 'Claude', 'claude_desktop_config.json');
234
- break;
235
- case 'darwin':
236
- claudeConfigPath = join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
237
- break;
238
- case 'linux':
239
- claudeConfigPath = join(homedir(), '.config', 'Claude', 'claude_desktop_config.json');
240
- break;
241
- default:
242
- // Fallback for other platforms
243
- claudeConfigPath = join(homedir(), '.claude_desktop_config.json');
244
- }
245
-
246
- // Setup logging
285
+ // Setup logging early to capture everything
247
286
  const LOG_FILE = join(__dirname, 'setup.log');
248
287
 
249
288
  function logToFile(message, isError = false) {
@@ -251,7 +290,7 @@ function logToFile(message, isError = false) {
251
290
  const logMessage = `${timestamp} - ${isError ? 'ERROR: ' : ''}${message}\n`;
252
291
  try {
253
292
  appendFileSync(LOG_FILE, logMessage);
254
- // For setup script, we'll still output to console but in JSON format
293
+ // For setup script, we'll still output to console but in JSON forma
255
294
  const jsonOutput = {
256
295
  type: isError ? 'error' : 'info',
257
296
  timestamp,
@@ -268,227 +307,449 @@ function logToFile(message, isError = false) {
268
307
  }
269
308
  }
270
309
 
271
- async function execAsync(command) {
272
- return new Promise((resolve, reject) => {
273
- // Use PowerShell on Windows for better Unicode support and consistency
274
- const actualCommand = isWindows
275
- ? `cmd.exe /c ${command}`
276
- : command;
310
+ // Setup global error handlers
311
+ process.on('uncaughtException', async (error) => {
312
+ logToFile(`Uncaught exception: ${error.stack || error.message}`, true);
313
+ await trackEvent('npx_setup_uncaught_exception', { error: error.message });
314
+ process.exit(1);
315
+ });
316
+
317
+ process.on('unhandledRejection', async (reason, promise) => {
318
+ logToFile(`Unhandled rejection at: ${promise}, reason: ${reason}`, true);
319
+ await trackEvent('npx_setup_unhandled_rejection', { error: String(reason) });
320
+ process.exit(1);
321
+ });
322
+
323
+ // Track when the process is about to exi
324
+ let isExiting = false;
325
+ process.on('exit', () => {
326
+ if (!isExiting) {
327
+ isExiting = true;
328
+ // Synchronous tracking for exit handler
329
+ logToFile('Process is exiting. Some tracking events may not be sent.');
330
+ }
331
+ });
332
+
333
+
334
+ // Function to check for debug mode argument
335
+ function isDebugMode() {
336
+ return process.argv.includes('--debug');
337
+ }
277
338
 
278
- exec(actualCommand, (error, stdout, stderr) => {
339
+ // Initial tracking - ensure it completes before continuing
340
+ await ensureTrackingCompleted('npx_setup_start', {
341
+ argv: process.argv.join(' '),
342
+ start_time: new Date().toISOString()
343
+ });
344
+
345
+ // Determine OS and set appropriate config path
346
+ const os = platform();
347
+ const isWindows = os === 'win32';
348
+ let claudeConfigPath;
349
+
350
+ switch (os) {
351
+ case 'win32':
352
+ claudeConfigPath = join(process.env.APPDATA, 'Claude', 'claude_desktop_config.json');
353
+ break;
354
+ case 'darwin':
355
+ claudeConfigPath = join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
356
+ break;
357
+ case 'linux':
358
+ claudeConfigPath = join(homedir(), '.config', 'Claude', 'claude_desktop_config.json');
359
+ break;
360
+ default:
361
+ // Fallback for other platforms
362
+ claudeConfigPath = join(homedir(), '.claude_desktop_config.json');
363
+ }
364
+
365
+
366
+
367
+ // Tracking step functions
368
+ function addSetupStep(step, status = 'started', error = null) {
369
+ const timestamp = Date.now();
370
+ setupSteps.push({
371
+ step,
372
+ status,
373
+ timestamp,
374
+ timeFromStart: timestamp - setupStartTime,
375
+ error: error ? error.message || String(error) : null
376
+ });
377
+ return setupSteps.length - 1; // Return the index for later updates
378
+ }
379
+
380
+ function updateSetupStep(index, status, error = null) {
381
+ if (setupSteps[index]) {
382
+ const timestamp = Date.now();
383
+ setupSteps[index].status = status;
384
+ setupSteps[index].completionTime = timestamp;
385
+ setupSteps[index].timeFromStart = timestamp - setupStartTime;
279
386
  if (error) {
280
- reject(error);
281
- return;
387
+ setupSteps[index].error = error.message || String(error);
282
388
  }
283
- resolve({ stdout, stderr });
284
- });
389
+ }
390
+ }
391
+
392
+ try {
393
+ // Only dependency is node-machine-id
394
+ const machineIdInitStep = addSetupStep('initialize_machine_id');
395
+ try {
396
+ const machineIdModule = await import('node-machine-id');
397
+ // Get a unique user ID
398
+ uniqueUserId = machineIdModule.machineIdSync();
399
+ updateSetupStep(machineIdInitStep, 'completed');
400
+ } catch (error) {
401
+ // Fall back to a semi-unique identifier if machine-id is not available
402
+ uniqueUserId = `${platform()}-${process.env.USER || process.env.USERNAME || 'unknown'}-${Date.now()}`;
403
+ updateSetupStep(machineIdInitStep, 'fallback', error);
404
+ }
405
+ } catch (error) {
406
+ logToFile(`Error initializing user ID: ${error}`, true);
407
+ addSetupStep('initialize_machine_id', 'failed', error);
408
+ }
409
+
410
+
411
+
412
+
413
+ async function execAsync(command) {
414
+ const execStep = addSetupStep(`exec_${command.substring(0, 20)}...`);
415
+ return new Promise((resolve, reject) => {
416
+ // Use PowerShell on Windows for better Unicode support and consistency
417
+ const actualCommand = isWindows
418
+ ? `cmd.exe /c ${command}`
419
+ : command;
420
+
421
+ exec(actualCommand, { timeout: 10000 }, (error, stdout, stderr) => {
422
+ if (error) {
423
+ updateSetupStep(execStep, 'failed', error);
424
+ reject(error);
425
+ return;
426
+ }
427
+ updateSetupStep(execStep, 'completed');
428
+ resolve({ stdout, stderr });
429
+ });
285
430
  });
286
431
  }
287
432
 
288
433
  async function restartClaude() {
289
- try {
290
- const platform = process.platform
291
- // ignore errors on windows when claude is not running.
292
- // just silently kill the process
293
- try {
434
+ const restartStep = addSetupStep('restart_claude');
435
+ try {
436
+ const platform = process.platform;
437
+ // Track restart attempt
438
+ await trackEvent('npx_setup_restart_claude_attempt', { platform });
439
+
440
+ // Try to kill Claude process first
441
+ const killStep = addSetupStep('kill_claude_process');
442
+ try {
294
443
  switch (platform) {
295
444
  case "win32":
296
-
297
445
  await execAsync(
298
446
  `taskkill /F /IM "Claude.exe"`,
299
- )
447
+ );
300
448
  break;
301
449
  case "darwin":
302
450
  await execAsync(
303
451
  `killall "Claude"`,
304
- )
452
+ );
305
453
  break;
306
454
  case "linux":
307
455
  await execAsync(
308
456
  `pkill -f "claude"`,
309
- )
457
+ );
310
458
  break;
311
459
  }
312
- } catch {}
313
- await new Promise((resolve) => setTimeout(resolve, 3000))
460
+ updateSetupStep(killStep, 'completed');
461
+ await trackEvent('npx_setup_kill_claude_success', { platform });
462
+ } catch (killError) {
463
+ // It's okay if Claude isn't running - update step but continue
464
+ updateSetupStep(killStep, 'no_process_found', killError);
465
+ await trackEvent('npx_setup_kill_claude_not_needed', { platform });
466
+ }
467
+
468
+ // Wait a bit to ensure process termination
469
+ await new Promise((resolve) => setTimeout(resolve, 3000));
470
+
471
+ // Try to start Claude
472
+ const startStep = addSetupStep('start_claude_process');
314
473
  try {
315
474
  if (platform === "win32") {
316
- // it will never start claude
317
- // await execAsync(`start "" "Claude.exe"`)
475
+ // Windows - note it won't actually start Claude
476
+ logToFile("Windows: Claude restart skipped - requires manual restart");
477
+ updateSetupStep(startStep, 'skipped');
478
+ await trackEvent('npx_setup_start_claude_skipped', { platform });
318
479
  } else if (platform === "darwin") {
319
- await execAsync(`open -a "Claude"`)
480
+ await execAsync(`open -a "Claude"`);
481
+ updateSetupStep(startStep, 'completed');
482
+ await trackEvent('npx_setup_start_claude_success', { platform });
320
483
  } else if (platform === "linux") {
321
- await execAsync(`claude`)
484
+ await execAsync(`claude`);
485
+ updateSetupStep(startStep, 'completed');
486
+ await trackEvent('npx_setup_start_claude_success', { platform });
322
487
  }
323
- logToFile(`Claude has been restarted.`)
324
- } catch{
325
488
 
489
+ logToFile(`Claude has been restarted.`);
490
+ updateSetupStep(restartStep, 'completed');
491
+ await trackEvent('npx_setup_restart_claude_success', { platform });
492
+ } catch (startError) {
493
+ updateSetupStep(startStep, 'failed', startError);
494
+ await trackEvent('npx_setup_start_claude_error', {
495
+ platform,
496
+ error: startError.message
497
+ });
498
+ throw startError; // Re-throw to handle in the outer catch
326
499
  }
327
-
328
-
329
- } catch (error) {
500
+ } catch (error) {
501
+ updateSetupStep(restartStep, 'failed', error);
330
502
  await trackEvent('npx_setup_restart_claude_error', { error: error.message });
331
- logToFile(`Failed to restart Claude: ${error}. Please restart it manually.`, true)
332
- logToFile(`If Claude Desktop is not installed use this link to download https://claude.ai/download`, true)
333
- }
334
- }
335
-
336
- // Check if config file exists and create default if not
337
- if (!existsSync(claudeConfigPath)) {
338
- logToFile(`Claude config file not found at: ${claudeConfigPath}`);
339
- logToFile('Creating default config file...');
340
-
341
- // Track new installation
342
- await trackEvent('npx_setup_create_default_config');
343
-
344
- // Create the directory if it doesn't exist
345
- const configDir = dirname(claudeConfigPath);
346
- if (!existsSync(configDir)) {
347
- import('fs').then(fs => fs.mkdirSync(configDir, { recursive: true }));
503
+ logToFile(`Failed to restart Claude: ${error}. Please restart it manually.`, true);
504
+ logToFile(`If Claude Desktop is not installed use this link to download https://claude.ai/download`, true);
348
505
  }
349
-
350
- // Create default config with shell based on platform
351
- const defaultConfig = {
352
- "serverConfig": isWindows
353
- ? {
354
- "command": "cmd.exe",
355
- "args": ["/c"]
356
- }
357
- : {
358
- "command": "/bin/sh",
359
- "args": ["-c"]
360
- }
361
- };
362
-
363
- writeFileSync(claudeConfigPath, JSON.stringify(defaultConfig, null, 2));
364
- logToFile('Default config file created. Please update it with your Claude API credentials.');
365
506
  }
366
507
 
367
- // Function to check for debug mode argument
368
- function isDebugMode() {
369
- return process.argv.includes('--debug');
370
- }
371
508
 
372
509
  // Main function to export for ESM compatibility
373
510
  export default async function setup() {
511
+ // Add tracking for setup function entry
512
+ await trackEvent('npx_setup_function_started');
513
+
514
+ const setupStep = addSetupStep('main_setup');
374
515
  const debugMode = isDebugMode();
516
+
375
517
  if (debugMode) {
376
518
  logToFile('Debug mode enabled. Will configure with Node.js inspector options.');
519
+ await trackEvent('npx_setup_debug_mode', { enabled: true });
377
520
  }
521
+
378
522
  try {
379
- // Read existing config
380
- const configData = readFileSync(claudeConfigPath, 'utf8');
381
- const config = JSON.parse(configData);
523
+ // Check if config directory exists and create it if necessary
524
+ const configDirStep = addSetupStep('check_config_directory');
525
+ const configDir = dirname(claudeConfigPath);
526
+
527
+ try {
528
+ if (!existsSync(configDir)) {
529
+ logToFile(`Creating config directory: ${configDir}`);
530
+ mkdirSync(configDir, { recursive: true });
531
+ await trackEvent('npx_setup_create_config_dir', { path: configDir });
532
+ }
533
+ updateSetupStep(configDirStep, 'completed');
534
+ } catch (dirError) {
535
+ updateSetupStep(configDirStep, 'failed', dirError);
536
+ await trackEvent('npx_setup_create_config_dir_error', {
537
+ path: configDir,
538
+ error: dirError.message
539
+ });
540
+ throw new Error(`Failed to create config directory: ${dirError.message}`);
541
+ }
542
+
543
+ // Check if config file exists and create default if no
544
+ const configFileStep = addSetupStep('check_config_file');
545
+ let config;
546
+
547
+ if (!existsSync(claudeConfigPath)) {
548
+ logToFile(`Claude config file not found at: ${claudeConfigPath}`);
549
+ logToFile('Creating default config file...');
550
+
551
+ // Track new installation
552
+ await trackEvent('npx_setup_create_default_config');
553
+
554
+ // Create default config with shell based on platform
555
+ const defaultConfig = {
556
+ "serverConfig": isWindows
557
+ ? {
558
+ "command": "cmd.exe",
559
+ "args": ["/c"]
560
+ }
561
+ : {
562
+ "command": "/bin/sh",
563
+ "args": ["-c"]
564
+ }
565
+ };
566
+
567
+ try {
568
+ writeFileSync(claudeConfigPath, JSON.stringify(defaultConfig, null, 2));
569
+ logToFile('Default config file created.');
570
+ config = defaultConfig;
571
+ updateSetupStep(configFileStep, 'created');
572
+ await trackEvent('npx_setup_config_file_created');
573
+ } catch (writeError) {
574
+ updateSetupStep(configFileStep, 'create_failed', writeError);
575
+ await trackEvent('npx_setup_config_file_create_error', { error: writeError.message });
576
+ throw new Error(`Failed to create config file: ${writeError.message}`);
577
+ }
578
+ } else {
579
+ // Read existing config
580
+ const readConfigStep = addSetupStep('read_config_file');
581
+ try {
582
+ const configData = readFileSync(claudeConfigPath, 'utf8');
583
+ config = JSON.parse(configData);
584
+ updateSetupStep(readConfigStep, 'completed');
585
+ updateSetupStep(configFileStep, 'exists');
586
+ await trackEvent('npx_setup_config_file_read');
587
+ } catch (readError) {
588
+ updateSetupStep(readConfigStep, 'failed', readError);
589
+ await trackEvent('npx_setup_config_file_read_error', { error: readError.message });
590
+ throw new Error(`Failed to read config file: ${readError.message}`);
591
+ }
592
+ }
382
593
 
383
594
  // Prepare the new server config based on OS
595
+ const configPrepStep = addSetupStep('prepare_server_config');
596
+
384
597
  // Determine if running through npx or locally
385
598
  const isNpx = import.meta.url.includes('node_modules');
599
+ await trackEvent('npx_setup_execution_mode', { isNpx });
386
600
 
387
601
  // Fix Windows path handling for npx execution
388
602
  let serverConfig;
389
-
390
- if (debugMode) {
391
- // Use Node.js with inspector flag for debugging
392
- if (isNpx) {
393
- // Debug with npx
394
- logToFile('Setting up debug configuration with npx. The process will pause on start until a debugger connects.');
395
- // Add environment variables to help with debugging
396
- const debugEnv = {
397
- "NODE_OPTIONS": "--trace-warnings --trace-exit",
398
- "DEBUG": "*"
399
- };
400
-
401
- serverConfig = {
402
- "command": isWindows ? "node.exe" : "node",
403
- "args": [
404
- "--inspect-brk=9229",
405
- isWindows ?
406
- join(process.env.APPDATA || '', "npm", "npx.cmd").replace(/\\/g, '\\\\') :
407
- "$(which npx)",
408
- "@wonderwhy-er/desktop-commander@latest"
409
- ],
410
- "env": debugEnv
411
- };
412
- } else {
413
- // Debug with local installation path
414
- const indexPath = join(__dirname, 'dist', 'index.js');
415
- logToFile('Setting up debug configuration with local path. The process will pause on start until a debugger connects.');
416
- // Add environment variables to help with debugging
417
- const debugEnv = {
418
- "NODE_OPTIONS": "--trace-warnings --trace-exit",
419
- "DEBUG": "*"
420
- };
421
-
422
- serverConfig = {
423
- "command": isWindows ? "node.exe" : "node",
424
- "args": [
425
- "--inspect-brk=9229",
426
- indexPath.replace(/\\/g, '\\\\') // Double escape backslashes for JSON
427
- ],
428
- "env": debugEnv
429
- };
430
- }
431
- } else {
432
- // Standard configuration without debug
433
- if (isNpx) {
434
- serverConfig = {
435
- "command": isWindows ? "npx.cmd" : "npx",
436
- "args": [
437
- "@wonderwhy-er/desktop-commander@latest"
438
- ]
439
- };
603
+
604
+ try {
605
+ if (debugMode) {
606
+ // Use Node.js with inspector flag for debugging
607
+ if (isNpx) {
608
+ // Debug with npx
609
+ logToFile('Setting up debug configuration with npx. The process will pause on start until a debugger connects.');
610
+ // Add environment variables to help with debugging
611
+ const debugEnv = {
612
+ "NODE_OPTIONS": "--trace-warnings --trace-exit",
613
+ "DEBUG": "*"
614
+ };
615
+
616
+ serverConfig = {
617
+ "command": isWindows ? "node.exe" : "node",
618
+ "args": [
619
+ "--inspect-brk=9229",
620
+ isWindows ?
621
+ join(process.env.APPDATA || '', "npm", "npx.cmd").replace(/\\/g, '\\\\') :
622
+ "$(which npx)",
623
+ "@wonderwhy-er/desktop-commander@latest"
624
+ ],
625
+ "env": debugEnv
626
+ };
627
+ await trackEvent('npx_setup_config_debug_npx');
628
+ } else {
629
+ // Debug with local installation path
630
+ const indexPath = join(__dirname, 'dist', 'index.js');
631
+ logToFile('Setting up debug configuration with local path. The process will pause on start until a debugger connects.');
632
+ // Add environment variables to help with debugging
633
+ const debugEnv = {
634
+ "NODE_OPTIONS": "--trace-warnings --trace-exit",
635
+ "DEBUG": "*"
636
+ };
637
+
638
+ serverConfig = {
639
+ "command": isWindows ? "node.exe" : "node",
640
+ "args": [
641
+ "--inspect-brk=9229",
642
+ indexPath.replace(/\\/g, '\\\\') // Double escape backslashes for JSON
643
+ ],
644
+ "env": debugEnv
645
+ };
646
+ await trackEvent('npx_setup_config_debug_local');
647
+ }
440
648
  } else {
441
- // For local installation, use absolute path to handle Windows properly
442
- const indexPath = join(__dirname, 'dist', 'index.js');
443
- serverConfig = {
444
- "command": "node",
445
- "args": [
446
- indexPath.replace(/\\/g, '\\\\') // Double escape backslashes for JSON
447
- ]
448
- };
649
+ // Standard configuration without debug
650
+ if (isNpx) {
651
+ serverConfig = {
652
+ "command": isWindows ? "npx.cmd" : "npx",
653
+ "args": [
654
+ "@wonderwhy-er/desktop-commander@latest"
655
+ ]
656
+ };
657
+ await trackEvent('npx_setup_config_standard_npx');
658
+ } else {
659
+ // For local installation, use absolute path to handle Windows properly
660
+ const indexPath = join(__dirname, 'dist', 'index.js');
661
+ serverConfig = {
662
+ "command": "node",
663
+ "args": [
664
+ indexPath.replace(/\\/g, '\\\\') // Double escape backslashes for JSON
665
+ ]
666
+ };
667
+ await trackEvent('npx_setup_config_standard_local');
668
+ }
449
669
  }
670
+ updateSetupStep(configPrepStep, 'completed');
671
+ } catch (prepError) {
672
+ updateSetupStep(configPrepStep, 'failed', prepError);
673
+ await trackEvent('npx_setup_config_prep_error', { error: prepError.message });
674
+ throw new Error(`Failed to prepare server config: ${prepError.message}`);
450
675
  }
451
676
 
452
- // Initialize mcpServers if it doesn't exist
453
- if (!config.mcpServers) {
454
- config.mcpServers = {};
455
- }
677
+ // Update the config
678
+ const updateConfigStep = addSetupStep('update_config');
679
+ try {
680
+ // Initialize mcpServers if it doesn't exist
681
+ if (!config.mcpServers) {
682
+ config.mcpServers = {};
683
+ }
456
684
 
457
- // Check if the old "desktopCommander" exists and remove it
458
- if (config.mcpServers.desktopCommander) {
459
- logToFile('Found old "desktopCommander" installation. Removing it...');
460
- delete config.mcpServers.desktopCommander;
461
- }
685
+ // Check if the old "desktopCommander" exists and remove i
686
+ if (config.mcpServers.desktopCommander) {
687
+ logToFile('Found old "desktopCommander" installation. Removing it...');
688
+ delete config.mcpServers.desktopCommander;
689
+ await trackEvent('npx_setup_remove_old_config');
690
+ }
462
691
 
463
- // Add or update the terminal server config with the proper name "desktop-commander"
464
- config.mcpServers["desktop-commander"] = serverConfig;
692
+ // Add or update the terminal server config with the proper name "desktop-commander"
693
+ config.mcpServers["desktop-commander"] = serverConfig;
694
+
695
+ // Write the updated config back
696
+ writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2), 'utf8');
697
+ updateSetupStep(updateConfigStep, 'completed');
698
+ await trackEvent('npx_setup_update_config');
699
+ } catch (updateError) {
700
+ updateSetupStep(updateConfigStep, 'failed', updateError);
701
+ await trackEvent('npx_setup_update_config_error', { error: updateError.message });
702
+ throw new Error(`Failed to update config: ${updateError.message}`);
703
+ }
465
704
 
466
- // Write the updated config back
467
- writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2), 'utf8');
468
- await trackEvent('npx_setup_update_config');
469
705
  logToFile('Successfully added MCP server to Claude configuration!');
470
706
  logToFile(`Configuration location: ${claudeConfigPath}`);
471
-
707
+
472
708
  if (debugMode) {
473
709
  logToFile('\nTo use the debug server:\n1. Restart Claude if it\'s currently running\n2. The server will be available as "desktop-commander-debug" in Claude\'s MCP server list\n3. Connect your debugger to port 9229');
474
710
  } else {
475
711
  logToFile('\nTo use the server:\n1. Restart Claude if it\'s currently running\n2. The server will be available as "desktop-commander" in Claude\'s MCP server list');
476
712
  }
477
713
 
714
+ // Try to restart Claude
478
715
  await restartClaude();
479
-
480
- await trackEvent('npx_setup_complete');
481
-
716
+
717
+ // Mark the main setup as completed
718
+ updateSetupStep(setupStep, 'completed');
719
+
720
+ // Ensure final tracking event is sent before exi
721
+ await ensureTrackingCompleted('npx_setup_complete', {
722
+ total_steps: setupSteps.length,
723
+ total_time_ms: Date.now() - setupStartTime
724
+ });
725
+
726
+ return true;
482
727
  } catch (error) {
483
- await trackEvent('npx_setup_final_error', { error: error.message });
728
+ updateSetupStep(setupStep, 'failed', error);
729
+ // Send detailed info about the failure
730
+ await ensureTrackingCompleted('npx_setup_final_error', {
731
+ error: error.message,
732
+ error_stack: error.stack,
733
+ total_steps: setupSteps.length,
734
+ last_successful_step: setupSteps.filter(s => s.status === 'completed').pop()?.step || 'none'
735
+ });
736
+
484
737
  logToFile(`Error updating Claude configuration: ${error}`, true);
485
- process.exit(1);
738
+ return false;
486
739
  }
487
740
  }
488
741
 
489
742
  // Allow direct execution
490
743
  if (process.argv.length >= 2 && process.argv[1] === fileURLToPath(import.meta.url)) {
491
- setup().catch(error => {
744
+ setup().then(success => {
745
+ if (!success) {
746
+ process.exit(1);
747
+ }
748
+ }).catch(async error => {
749
+ await ensureTrackingCompleted('npx_setup_fatal_error', {
750
+ error: error.message,
751
+ error_stack: error.stack
752
+ });
492
753
  logToFile(`Fatal error: ${error}`, true);
493
754
  process.exit(1);
494
755
  });