@wonderwhy-er/desktop-commander 0.1.34 → 0.1.36

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 (63) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +186 -56
  3. package/dist/command-manager.d.ts +1 -7
  4. package/dist/command-manager.js +31 -50
  5. package/dist/config-manager.d.ts +28 -16
  6. package/dist/config-manager.js +124 -189
  7. package/dist/config.d.ts +2 -2
  8. package/dist/config.js +7 -4
  9. package/dist/error-handlers.js +4 -0
  10. package/dist/handlers/edit-search-handlers.d.ts +3 -1
  11. package/dist/handlers/edit-search-handlers.js +9 -19
  12. package/dist/handlers/filesystem-handlers.d.ts +0 -4
  13. package/dist/handlers/filesystem-handlers.js +11 -19
  14. package/dist/handlers/index.d.ts +0 -1
  15. package/dist/handlers/index.js +0 -1
  16. package/dist/index.js +19 -4
  17. package/dist/polyform-license-src/edit/edit.d.ts +15 -0
  18. package/dist/polyform-license-src/edit/edit.js +163 -0
  19. package/dist/polyform-license-src/edit/fuzzySearch.d.ts +30 -0
  20. package/dist/polyform-license-src/edit/fuzzySearch.js +121 -0
  21. package/dist/polyform-license-src/edit/handlers.d.ts +16 -0
  22. package/dist/polyform-license-src/edit/handlers.js +24 -0
  23. package/dist/polyform-license-src/edit/index.d.ts +12 -0
  24. package/dist/polyform-license-src/edit/index.js +13 -0
  25. package/dist/polyform-license-src/edit/schemas.d.ts +25 -0
  26. package/dist/polyform-license-src/edit/schemas.js +16 -0
  27. package/dist/polyform-license-src/index.d.ts +9 -0
  28. package/dist/polyform-license-src/index.js +10 -0
  29. package/dist/sandbox/index.d.ts +9 -0
  30. package/dist/sandbox/index.js +50 -0
  31. package/dist/sandbox/mac-sandbox.d.ts +19 -0
  32. package/dist/sandbox/mac-sandbox.js +174 -0
  33. package/dist/server.js +181 -176
  34. package/dist/setup-claude-server.js +554 -244
  35. package/dist/terminal-manager.d.ts +1 -1
  36. package/dist/terminal-manager.js +22 -3
  37. package/dist/tools/config.d.ts +0 -58
  38. package/dist/tools/config.js +44 -107
  39. package/dist/tools/debug-path.d.ts +1 -0
  40. package/dist/tools/debug-path.js +44 -0
  41. package/dist/tools/edit.d.ts +8 -6
  42. package/dist/tools/edit.js +165 -35
  43. package/dist/tools/execute.js +6 -6
  44. package/dist/tools/filesystem-fixed.d.ts +22 -0
  45. package/dist/tools/filesystem-fixed.js +176 -0
  46. package/dist/tools/filesystem.d.ts +4 -6
  47. package/dist/tools/filesystem.js +157 -87
  48. package/dist/tools/fuzzySearch.d.ts +22 -0
  49. package/dist/tools/fuzzySearch.js +113 -0
  50. package/dist/tools/pdf-reader.d.ts +13 -0
  51. package/dist/tools/pdf-reader.js +214 -0
  52. package/dist/tools/schemas.d.ts +29 -19
  53. package/dist/tools/schemas.js +15 -8
  54. package/dist/tools/search.js +5 -4
  55. package/dist/utils/capture.d.ts +15 -0
  56. package/dist/utils/capture.js +175 -0
  57. package/dist/utils/withTimeout.d.ts +11 -0
  58. package/dist/utils/withTimeout.js +52 -0
  59. package/dist/utils.d.ts +15 -1
  60. package/dist/utils.js +174 -41
  61. package/dist/version.d.ts +1 -1
  62. package/dist/version.js +1 -1
  63. package/package.json +2 -3
@@ -1,34 +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
+ import * as https from 'https';
9
+ import { randomUUID } from 'crypto';
8
10
 
9
- // Optional analytics - will gracefully degrade if posthog-node isn't available
10
- let client = null;
11
+ // Google Analytics configuration
12
+ const GA_MEASUREMENT_ID = 'G-NGGDNL0K4L'; // Replace with your GA4 Measurement ID
13
+ const GA_API_SECRET = '5M0mC--2S_6t94m8WrI60A'; // Replace with your GA4 API Secre
14
+ const GA_BASE_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
15
+
16
+ // Generate a unique anonymous ID using UUID - consistent with privacy policy
11
17
  let uniqueUserId = 'unknown';
12
18
 
13
19
  try {
14
- const { PostHog } = await import('posthog-node');
15
- const machineIdModule = await import('node-machine-id');
16
-
17
- client = new PostHog(
18
- 'phc_BW8KJ0cajzj2v8qfMhvDQ4dtFdgHPzeYcMRvRFGvQdH',
19
- {
20
- host: 'https://eu.i.posthog.com',
21
- flushAt: 1, // send all every time
22
- flushInterval: 0 //send always
23
- }
24
- );
25
-
26
- // Get a unique user ID
27
- 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();
28
23
  } catch (error) {
29
- //console.error('Analytics module not available - continuing without tracking');
24
+ // Fall back to a semi-unique identifier if UUID generation fails
25
+ uniqueUserId = `random-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
30
26
  }
31
27
 
28
+ // Setup tracking
29
+ let setupSteps = []; // Track setup progress
30
+ let setupStartTime = Date.now();
31
+
32
+
32
33
  // Function to get npm version
33
34
  async function getNpmVersion() {
34
35
  try {
@@ -55,7 +56,7 @@ const getVersion = async () => {
55
56
  }
56
57
  };
57
58
 
58
- // Function to detect shell environment
59
+ // Function to detect shell environmen
59
60
  function detectShell() {
60
61
  // Check for Windows shells
61
62
  if (process.platform === 'win32') {
@@ -65,7 +66,7 @@ function detectShell() {
65
66
  if (process.env.TERM?.includes('xterm')) return 'xterm-on-windows';
66
67
  if (process.env.ComSpec?.toLowerCase().includes('powershell')) return 'powershell';
67
68
  if (process.env.PROMPT) return 'cmd';
68
-
69
+
69
70
  // WSL detection
70
71
  if (process.env.WSL_DISTRO_NAME || process.env.WSLENV) {
71
72
  return `wsl-${process.env.WSL_DISTRO_NAME || 'unknown'}`;
@@ -73,7 +74,7 @@ function detectShell() {
73
74
 
74
75
  return 'windows-unknown';
75
76
  }
76
-
77
+
77
78
  // Unix-based shells
78
79
  if (process.env.SHELL) {
79
80
  const shellPath = process.env.SHELL.toLowerCase();
@@ -85,30 +86,30 @@ function detectShell() {
85
86
  if (shellPath.includes('dash')) return 'dash';
86
87
  return `other-unix-${shellPath.split('/').pop()}`;
87
88
  }
88
-
89
+
89
90
  // Terminal emulators and IDE terminals
90
91
  if (process.env.TERM_PROGRAM) {
91
92
  return process.env.TERM_PROGRAM.toLowerCase();
92
93
  }
93
-
94
+
94
95
  return 'unknown-shell';
95
96
  }
96
97
 
97
98
  // Function to determine execution context
98
99
  function getExecutionContext() {
99
100
  // Check if running from npx
100
- const isNpx = process.env.npm_lifecycle_event === 'npx' ||
101
+ const isNpx = process.env.npm_lifecycle_event === 'npx' ||
101
102
  process.env.npm_execpath?.includes('npx') ||
102
103
  process.env._?.includes('npx') ||
103
104
  import.meta.url.includes('node_modules');
104
-
105
+
105
106
  // Check if installed globally
106
107
  const isGlobal = process.env.npm_config_global === 'true' ||
107
108
  process.argv[1]?.includes('node_modules/.bin');
108
-
109
+
109
110
  // Check if it's run from a script in package.json
110
111
  const isNpmScript = !!process.env.npm_lifecycle_script;
111
-
112
+
112
113
  return {
113
114
  runMethod: isNpx ? 'npx' : (isGlobal ? 'global' : (isNpmScript ? 'npm_script' : 'direct')),
114
115
  isCI: !!process.env.CI || !!process.env.GITHUB_ACTIONS || !!process.env.TRAVIS || !!process.env.CIRCLECI,
@@ -118,52 +119,232 @@ function getExecutionContext() {
118
119
 
119
120
  // Helper function to get standard environment properties for tracking
120
121
  let npmVersionCache = null;
121
- async function getTrackingProperties(additionalProps = {}) {
122
- if (npmVersionCache === null) {
123
- npmVersionCache = await getNpmVersion();
122
+
123
+ // Enhanced version with step tracking - will replace the original after initialization
124
+ async function enhancedGetTrackingProperties(additionalProps = {}) {
125
+ const propertiesStep = addSetupStep('get_tracking_properties');
126
+ try {
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
145
+ };
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
154
+ };
124
155
  }
125
-
126
- const context = getExecutionContext();
127
- const version = await getVersion();
128
- return {
129
- platform: platform(),
130
- nodeVersion: nodeVersion,
131
- npmVersion: npmVersionCache,
132
- executionContext: context.runMethod,
133
- isCI: context.isCI,
134
- shell: context.shell,
135
- DCVersion: version,
136
- timestamp: new Date().toISOString(),
137
- ...additionalProps
138
- };
139
156
  }
140
157
 
141
- // Helper function for tracking that handles missing PostHog gracefully
158
+ // Enhanced tracking function with retries and better error handling
159
+ // This replaces the basic implementation for all tracking after initialization
142
160
  async function trackEvent(eventName, additionalProps = {}) {
143
- if (!client) return; // Skip tracking if PostHog client isn't available
144
-
145
- try {
146
- client.capture({
147
- distinctId: uniqueUserId,
148
- event: eventName,
149
- properties: await getTrackingProperties(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);
257
+ return false;
258
+ }
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
+ }
150
277
  });
151
- } catch (error) {
152
- // Silently fail if tracking fails - we don't want to break the setup process
153
- //console.log(`Note: Event tracking unavailable for ${eventName}`);
154
- }
155
278
  }
156
279
 
157
- // Initial tracking
158
- trackEvent('npx_setup_start');
159
280
 
160
281
  // Fix for Windows ESM path resolution
161
282
  const __filename = fileURLToPath(import.meta.url);
162
283
  const __dirname = dirname(__filename);
163
284
 
285
+ // Setup logging early to capture everything
286
+ const LOG_FILE = join(__dirname, 'setup.log');
287
+
288
+ function logToFile(message, isError = false) {
289
+ const timestamp = new Date().toISOString();
290
+ const logMessage = `${timestamp} - ${isError ? 'ERROR: ' : ''}${message}\n`;
291
+ try {
292
+ appendFileSync(LOG_FILE, logMessage);
293
+ // For setup script, we'll still output to console but in JSON forma
294
+ const jsonOutput = {
295
+ type: isError ? 'error' : 'info',
296
+ timestamp,
297
+ message
298
+ };
299
+ process.stdout.write(JSON.stringify(jsonOutput) + '\n');
300
+ } catch (err) {
301
+ // Last resort error handling
302
+ process.stderr.write(JSON.stringify({
303
+ type: 'error',
304
+ timestamp: new Date().toISOString(),
305
+ message: `Failed to write to log file: ${err.message}`
306
+ }) + '\n');
307
+ }
308
+ }
309
+
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
+ }
338
+
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
+
164
345
  // Determine OS and set appropriate config path
165
346
  const os = platform();
166
- const isWindows = os === 'win32'; // Define isWindows variable
347
+ const isWindows = os === 'win32';
167
348
  let claudeConfigPath;
168
349
 
169
350
  switch (os) {
@@ -181,265 +362,394 @@ switch (os) {
181
362
  claudeConfigPath = join(homedir(), '.claude_desktop_config.json');
182
363
  }
183
364
 
184
- // Setup logging
185
- const LOG_FILE = join(__dirname, 'setup.log');
186
365
 
187
- function logToFile(message, isError = false) {
188
- const timestamp = new Date().toISOString();
189
- const logMessage = `${timestamp} - ${isError ? 'ERROR: ' : ''}${message}\n`;
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;
386
+ if (error) {
387
+ setupSteps[index].error = error.message || String(error);
388
+ }
389
+ }
390
+ }
391
+
392
+ try {
393
+ // Only dependency is node-machine-id
394
+ const machineIdInitStep = addSetupStep('initialize_machine_id');
190
395
  try {
191
- appendFileSync(LOG_FILE, logMessage);
192
- // For setup script, we'll still output to console but in JSON format
193
- const jsonOutput = {
194
- type: isError ? 'error' : 'info',
195
- timestamp,
196
- message
197
- };
198
- process.stdout.write(JSON.stringify(jsonOutput) + '\n');
199
- } catch (err) {
200
- // Last resort error handling
201
- process.stderr.write(JSON.stringify({
202
- type: 'error',
203
- timestamp: new Date().toISOString(),
204
- message: `Failed to write to log file: ${err.message}`
205
- }) + '\n');
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);
206
404
  }
405
+ } catch (error) {
406
+ logToFile(`Error initializing user ID: ${error}`, true);
407
+ addSetupStep('initialize_machine_id', 'failed', error);
207
408
  }
208
409
 
410
+
411
+
412
+
209
413
  async function execAsync(command) {
414
+ const execStep = addSetupStep(`exec_${command.substring(0, 20)}...`);
210
415
  return new Promise((resolve, reject) => {
211
- // Use PowerShell on Windows for better Unicode support and consistency
212
- const actualCommand = isWindows
213
- ? `cmd.exe /c ${command}`
214
- : command;
416
+ // Use PowerShell on Windows for better Unicode support and consistency
417
+ const actualCommand = isWindows
418
+ ? `cmd.exe /c ${command}`
419
+ : command;
215
420
 
216
- exec(actualCommand, (error, stdout, stderr) => {
217
- if (error) {
218
- reject(error);
219
- return;
220
- }
221
- resolve({ stdout, stderr });
222
- });
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
+ });
223
430
  });
224
431
  }
225
432
 
226
433
  async function restartClaude() {
227
- try {
228
- const platform = process.platform
229
- // ignore errors on windows when claude is not running.
230
- // just silently kill the process
231
- 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 {
232
443
  switch (platform) {
233
444
  case "win32":
234
-
235
445
  await execAsync(
236
446
  `taskkill /F /IM "Claude.exe"`,
237
- )
447
+ );
238
448
  break;
239
449
  case "darwin":
240
450
  await execAsync(
241
451
  `killall "Claude"`,
242
- )
452
+ );
243
453
  break;
244
454
  case "linux":
245
455
  await execAsync(
246
456
  `pkill -f "claude"`,
247
- )
457
+ );
248
458
  break;
249
459
  }
250
- } catch {}
251
- 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');
252
473
  try {
253
474
  if (platform === "win32") {
254
- // it will never start claude
255
- // 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 });
256
479
  } else if (platform === "darwin") {
257
- await execAsync(`open -a "Claude"`)
480
+ await execAsync(`open -a "Claude"`);
481
+ updateSetupStep(startStep, 'completed');
482
+ await trackEvent('npx_setup_start_claude_success', { platform });
258
483
  } else if (platform === "linux") {
259
- await execAsync(`claude`)
484
+ await execAsync(`claude`);
485
+ updateSetupStep(startStep, 'completed');
486
+ await trackEvent('npx_setup_start_claude_success', { platform });
260
487
  }
261
- } catch{}
262
488
 
263
- logToFile(`Claude has been restarted.`)
264
- } catch (error) {
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
499
+ }
500
+ } catch (error) {
501
+ updateSetupStep(restartStep, 'failed', error);
265
502
  await trackEvent('npx_setup_restart_claude_error', { error: error.message });
266
- logToFile(`Failed to restart Claude: ${error}`, true)
267
- }
268
- }
269
-
270
- // Check if config file exists and create default if not
271
- if (!existsSync(claudeConfigPath)) {
272
- logToFile(`Claude config file not found at: ${claudeConfigPath}`);
273
- logToFile('Creating default config file...');
274
-
275
- // Track new installation
276
- await trackEvent('npx_setup_create_default_config');
277
-
278
- // Create the directory if it doesn't exist
279
- const configDir = dirname(claudeConfigPath);
280
- if (!existsSync(configDir)) {
281
- 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);
282
505
  }
283
-
284
- // Create default config with shell based on platform
285
- const defaultConfig = {
286
- "serverConfig": isWindows
287
- ? {
288
- "command": "cmd.exe",
289
- "args": ["/c"]
290
- }
291
- : {
292
- "command": "/bin/sh",
293
- "args": ["-c"]
294
- }
295
- };
296
-
297
- writeFileSync(claudeConfigPath, JSON.stringify(defaultConfig, null, 2));
298
- logToFile('Default config file created. Please update it with your Claude API credentials.');
299
506
  }
300
507
 
301
- // Function to check for debug mode argument
302
- function isDebugMode() {
303
- return process.argv.includes('--debug');
304
- }
305
508
 
306
509
  // Main function to export for ESM compatibility
307
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');
308
515
  const debugMode = isDebugMode();
516
+
309
517
  if (debugMode) {
310
518
  logToFile('Debug mode enabled. Will configure with Node.js inspector options.');
519
+ await trackEvent('npx_setup_debug_mode', { enabled: true });
311
520
  }
521
+
312
522
  try {
313
- // Read existing config
314
- const configData = readFileSync(claudeConfigPath, 'utf8');
315
- 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
+ }
316
593
 
317
594
  // Prepare the new server config based on OS
595
+ const configPrepStep = addSetupStep('prepare_server_config');
596
+
318
597
  // Determine if running through npx or locally
319
598
  const isNpx = import.meta.url.includes('node_modules');
599
+ await trackEvent('npx_setup_execution_mode', { isNpx });
320
600
 
321
601
  // Fix Windows path handling for npx execution
322
602
  let serverConfig;
323
-
324
- if (debugMode) {
325
- // Use Node.js with inspector flag for debugging
326
- if (isNpx) {
327
- // Debug with npx
328
- logToFile('Setting up debug configuration with npx. The process will pause on start until a debugger connects.');
329
- // Add environment variables to help with debugging
330
- const debugEnv = {
331
- "NODE_OPTIONS": "--trace-warnings --trace-exit",
332
- "DEBUG": "*"
333
- };
334
-
335
- serverConfig = {
336
- "command": isWindows ? "node.exe" : "node",
337
- "args": [
338
- "--inspect-brk=9229",
339
- isWindows ?
340
- join(process.env.APPDATA || '', "npm", "npx.cmd").replace(/\\/g, '\\\\') :
341
- "$(which npx)",
342
- "@wonderwhy-er/desktop-commander@latest"
343
- ],
344
- "env": debugEnv
345
- };
346
- } else {
347
- // Debug with local installation path
348
- const indexPath = join(__dirname, 'dist', 'index.js');
349
- logToFile('Setting up debug configuration with local path. The process will pause on start until a debugger connects.');
350
- // Add environment variables to help with debugging
351
- const debugEnv = {
352
- "NODE_OPTIONS": "--trace-warnings --trace-exit",
353
- "DEBUG": "*"
354
- };
355
-
356
- serverConfig = {
357
- "command": isWindows ? "node.exe" : "node",
358
- "args": [
359
- "--inspect-brk=9229",
360
- indexPath.replace(/\\/g, '\\\\') // Double escape backslashes for JSON
361
- ],
362
- "env": debugEnv
363
- };
364
- }
365
- } else {
366
- // Standard configuration without debug
367
- if (isNpx) {
368
- serverConfig = {
369
- "command": isWindows ? "npx.cmd" : "npx",
370
- "args": [
371
- "@wonderwhy-er/desktop-commander@latest"
372
- ]
373
- };
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
+ }
374
648
  } else {
375
- // For local installation, use absolute path to handle Windows properly
376
- const indexPath = join(__dirname, 'dist', 'index.js');
377
- serverConfig = {
378
- "command": "node",
379
- "args": [
380
- indexPath.replace(/\\/g, '\\\\') // Double escape backslashes for JSON
381
- ]
382
- };
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
+ }
383
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}`);
384
675
  }
385
676
 
386
- // Initialize mcpServers if it doesn't exist
387
- if (!config.mcpServers) {
388
- config.mcpServers = {};
389
- }
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
+ }
390
684
 
391
- // Check if the old "desktopCommander" exists and remove it
392
- if (config.mcpServers.desktopCommander) {
393
- logToFile('Found old "desktopCommander" installation. Removing it...');
394
- delete config.mcpServers.desktopCommander;
395
- }
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
+ }
691
+
692
+ // Add or update the terminal server config with the proper name "desktop-commander"
693
+ config.mcpServers["desktop-commander"] = serverConfig;
396
694
 
397
- // Add or update the terminal server config with the proper name "desktop-commander"
398
- config.mcpServers["desktop-commander"] = serverConfig;
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
+ }
399
704
 
400
- // Write the updated config back
401
- writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2), 'utf8');
402
- await trackEvent('npx_setup_update_config');
403
705
  logToFile('Successfully added MCP server to Claude configuration!');
404
706
  logToFile(`Configuration location: ${claudeConfigPath}`);
405
-
707
+
406
708
  if (debugMode) {
407
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');
408
710
  } else {
409
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');
410
712
  }
411
713
 
714
+ // Try to restart Claude
412
715
  await restartClaude();
413
-
414
- await trackEvent('npx_setup_complete');
415
-
416
- // Safe shutdown
417
- if (client) {
418
- try {
419
- await client.shutdown();
420
- } catch (error) {
421
- // Ignore shutdown errors
422
- }
423
- }
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;
424
727
  } catch (error) {
425
- 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
+
426
737
  logToFile(`Error updating Claude configuration: ${error}`, true);
427
-
428
- // Safe shutdown
429
- if (client) {
430
- try {
431
- await client.shutdown();
432
- } catch (error) {
433
- // Ignore shutdown errors
434
- }
435
- }
436
- process.exit(1);
738
+ return false;
437
739
  }
438
740
  }
439
741
 
440
742
  // Allow direct execution
441
743
  if (process.argv.length >= 2 && process.argv[1] === fileURLToPath(import.meta.url)) {
442
- 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
+ });
443
753
  logToFile(`Fatal error: ${error}`, true);
444
754
  process.exit(1);
445
755
  });