@wonderwhy-er/desktop-commander 0.2.17 → 0.2.18-alpha.0

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/dist/server.js CHANGED
@@ -591,12 +591,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
591
591
 
592
592
  STATES DETECTED:
593
593
  Process waiting for input (shows prompt)
594
- Process finished execution
594
+ Process finished execution
595
595
  Process running (use read_process_output)
596
-
596
+
597
+ PERFORMANCE DEBUGGING (verbose_timing parameter):
598
+ Set verbose_timing: true to get detailed timing information including:
599
+ - Exit reason (early_exit_quick_pattern, early_exit_periodic_check, process_exit, timeout)
600
+ - Total duration and time to first output
601
+ - Complete timeline of all output events with timestamps
602
+ - Which detection mechanism triggered early exit
603
+ Use this to identify missed optimization opportunities and improve detection patterns.
604
+
597
605
  ALWAYS USE FOR: Local file analysis, CSV processing, data exploration, system commands
598
606
  NEVER USE ANALYSIS TOOL FOR: Local file access (analysis tool is browser-only and WILL FAIL)
599
-
607
+
600
608
  ${PATH_GUIDANCE}
601
609
  ${CMD_PREFIX_DESCRIPTION}`,
602
610
  inputSchema: zodToJsonSchema(StartProcessArgsSchema),
@@ -630,7 +638,15 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
630
638
  Process waiting for input (ready for interact_with_process)
631
639
  Process finished execution
632
640
  Timeout reached (may still be running)
633
-
641
+
642
+ PERFORMANCE DEBUGGING (verbose_timing parameter):
643
+ Set verbose_timing: true to get detailed timing information including:
644
+ - Exit reason (early_exit_quick_pattern, early_exit_periodic_check, process_finished, timeout)
645
+ - Total duration and time to first output
646
+ - Complete timeline of all output events with timestamps
647
+ - Which detection mechanism triggered early exit
648
+ Use this to identify when timeouts could be reduced or detection patterns improved.
649
+
634
650
  ${CMD_PREFIX_DESCRIPTION}`,
635
651
  inputSchema: zodToJsonSchema(ReadProcessOutputArgsSchema),
636
652
  annotations: {
@@ -681,12 +697,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
681
697
  - input: Code/command to execute
682
698
  - timeout_ms: Max wait (default: 8000ms)
683
699
  - wait_for_prompt: Auto-wait for response (default: true)
684
-
700
+ - verbose_timing: Enable detailed performance telemetry (default: false)
701
+
685
702
  Returns execution result with status indicators.
686
-
703
+
704
+ PERFORMANCE DEBUGGING (verbose_timing parameter):
705
+ Set verbose_timing: true to get detailed timing information including:
706
+ - Exit reason (early_exit_quick_pattern, early_exit_periodic_check, process_finished, timeout, no_wait)
707
+ - Total duration and time to first output
708
+ - Complete timeline of all output events with timestamps
709
+ - Which detection mechanism triggered early exit
710
+ Use this to identify slow interactions and optimize detection patterns.
711
+
687
712
  ALWAYS USE FOR: CSV analysis, JSON processing, file statistics, data visualization prep, ANY local file work
688
713
  NEVER USE ANALYSIS TOOL FOR: Local file access (it cannot read files from disk and WILL FAIL)
689
-
714
+
690
715
  ${CMD_PREFIX_DESCRIPTION}`,
691
716
  inputSchema: zodToJsonSchema(InteractWithProcessArgsSchema),
692
717
  annotations: {
@@ -44,7 +44,7 @@ async function initConfigFile() {
44
44
  // Ensure config directory exists
45
45
  const configDir = path.dirname(CONFIG_FILE);
46
46
  if (!existsSync(configDir)) {
47
- await mkdir(configDir, { recursive: true });
47
+ mkdirSync(configDir, { recursive: true });
48
48
  }
49
49
 
50
50
  // Check if config file exists
@@ -206,6 +206,28 @@ function detectShell() {
206
206
  return 'unknown-shell';
207
207
  }
208
208
 
209
+ // Function to get the package spec that was used to run this script
210
+ function getPackageSpec() {
211
+ // Check if running via npx - look for the package spec in process.argv
212
+ // e.g., npx @wonderwhy-er/desktop-commander@0.2.18-alpha setup
213
+ const argv = process.argv;
214
+
215
+ // Look for the package name in argv
216
+ for (let i = 0; i < argv.length; i++) {
217
+ const arg = argv[i];
218
+ if (arg.includes('@wonderwhy-er/desktop-commander')) {
219
+ // Extract just the package spec (e.g., @wonderwhy-er/desktop-commander@0.2.18-alpha)
220
+ const match = arg.match(/(@wonderwhy-er\/desktop-commander(@[^\/\s]+)?)/);
221
+ if (match) {
222
+ return match[1];
223
+ }
224
+ }
225
+ }
226
+
227
+ // Fallback to @latest if we can't detect
228
+ return '@wonderwhy-er/desktop-commander@latest';
229
+ }
230
+
209
231
  // Function to determine execution context
210
232
  function getExecutionContext() {
211
233
  // Check if running from npx
@@ -728,6 +750,7 @@ export default async function setup() {
728
750
  "DEBUG": "*"
729
751
  };
730
752
 
753
+ const packageSpec = getPackageSpec();
731
754
  serverConfig = {
732
755
  "command": isWindows ? "node.exe" : "node",
733
756
  "args": [
@@ -735,11 +758,11 @@ export default async function setup() {
735
758
  isWindows ?
736
759
  join(process.env.APPDATA || '', "npm", "npx.cmd").replace(/\\/g, '\\\\') :
737
760
  "$(which npx)",
738
- "@wonderwhy-er/desktop-commander@latest"
761
+ packageSpec
739
762
  ],
740
763
  "env": debugEnv
741
764
  };
742
- await trackEvent('npx_setup_config_debug_npx');
765
+ await trackEvent('npx_setup_config_debug_npx', { packageSpec });
743
766
  } else {
744
767
  // Debug with local installation path
745
768
  const indexPath = join(__dirname, 'dist', 'index.js');
@@ -763,13 +786,14 @@ export default async function setup() {
763
786
  } else {
764
787
  // Standard configuration without debug
765
788
  if (isNpx) {
789
+ const packageSpec = getPackageSpec();
766
790
  serverConfig = {
767
791
  "command": isWindows ? "npx.cmd" : "npx",
768
792
  "args": [
769
- "@wonderwhy-er/desktop-commander@latest"
793
+ packageSpec
770
794
  ]
771
795
  };
772
- await trackEvent('npx_setup_config_standard_npx');
796
+ await trackEvent('npx_setup_config_standard_npx', { packageSpec });
773
797
  } else {
774
798
  // For local installation, use absolute path to handle Windows properly
775
799
  const indexPath = join(__dirname, 'dist', 'index.js');
@@ -16,7 +16,7 @@ export declare class TerminalManager {
16
16
  * @returns Whether input was successfully sent
17
17
  */
18
18
  sendInputToProcess(pid: number, input: string): boolean;
19
- executeCommand(command: string, timeoutMs?: number, shell?: string): Promise<CommandExecutionResult>;
19
+ executeCommand(command: string, timeoutMs?: number, shell?: string, collectTiming?: boolean): Promise<CommandExecutionResult>;
20
20
  getNewOutput(pid: number): string | null;
21
21
  /**
22
22
  * Get a session by PID
@@ -33,7 +33,7 @@ export class TerminalManager {
33
33
  return false;
34
34
  }
35
35
  }
36
- async executeCommand(command, timeoutMs = DEFAULT_COMMAND_TIMEOUT, shell) {
36
+ async executeCommand(command, timeoutMs = DEFAULT_COMMAND_TIMEOUT, shell, collectTiming = false) {
37
37
  // Get the shell from config if not specified
38
38
  let shellToUse = shell;
39
39
  if (!shellToUse) {
@@ -81,6 +81,12 @@ export class TerminalManager {
81
81
  startTime: new Date()
82
82
  };
83
83
  this.sessions.set(childProcess.pid, session);
84
+ // Timing telemetry
85
+ const startTime = Date.now();
86
+ let firstOutputTime;
87
+ let lastOutputTime;
88
+ const outputEvents = [];
89
+ let exitReason = 'timeout';
84
90
  return new Promise((resolve) => {
85
91
  let resolved = false;
86
92
  let periodicCheck = null;
@@ -92,15 +98,47 @@ export class TerminalManager {
92
98
  resolved = true;
93
99
  if (periodicCheck)
94
100
  clearInterval(periodicCheck);
101
+ // Add timing info if requested
102
+ if (collectTiming) {
103
+ const endTime = Date.now();
104
+ result.timingInfo = {
105
+ startTime,
106
+ endTime,
107
+ totalDurationMs: endTime - startTime,
108
+ exitReason,
109
+ firstOutputTime,
110
+ lastOutputTime,
111
+ timeToFirstOutputMs: firstOutputTime ? firstOutputTime - startTime : undefined,
112
+ outputEvents: outputEvents.length > 0 ? outputEvents : undefined
113
+ };
114
+ }
95
115
  resolve(result);
96
116
  };
97
117
  childProcess.stdout.on('data', (data) => {
98
118
  const text = data.toString();
119
+ const now = Date.now();
120
+ if (!firstOutputTime)
121
+ firstOutputTime = now;
122
+ lastOutputTime = now;
99
123
  output += text;
100
124
  session.lastOutput += text;
125
+ // Record output event if collecting timing
126
+ if (collectTiming) {
127
+ outputEvents.push({
128
+ timestamp: now,
129
+ deltaMs: now - startTime,
130
+ source: 'stdout',
131
+ length: text.length,
132
+ snippet: text.slice(0, 50).replace(/\n/g, '\\n')
133
+ });
134
+ }
101
135
  // Immediate check for obvious prompts
102
136
  if (quickPromptPatterns.test(text)) {
103
137
  session.isBlocked = true;
138
+ exitReason = 'early_exit_quick_pattern';
139
+ if (collectTiming && outputEvents.length > 0) {
140
+ outputEvents[outputEvents.length - 1].matchedPattern = 'quick_pattern';
141
+ }
104
142
  resolveOnce({
105
143
  pid: childProcess.pid,
106
144
  output,
@@ -110,8 +148,22 @@ export class TerminalManager {
110
148
  });
111
149
  childProcess.stderr.on('data', (data) => {
112
150
  const text = data.toString();
151
+ const now = Date.now();
152
+ if (!firstOutputTime)
153
+ firstOutputTime = now;
154
+ lastOutputTime = now;
113
155
  output += text;
114
156
  session.lastOutput += text;
157
+ // Record output event if collecting timing
158
+ if (collectTiming) {
159
+ outputEvents.push({
160
+ timestamp: now,
161
+ deltaMs: now - startTime,
162
+ source: 'stderr',
163
+ length: text.length,
164
+ snippet: text.slice(0, 50).replace(/\n/g, '\\n')
165
+ });
166
+ }
115
167
  });
116
168
  // Periodic comprehensive check every 100ms
117
169
  periodicCheck = setInterval(() => {
@@ -119,6 +171,7 @@ export class TerminalManager {
119
171
  const processState = analyzeProcessState(output, childProcess.pid);
120
172
  if (processState.isWaitingForInput) {
121
173
  session.isBlocked = true;
174
+ exitReason = 'early_exit_periodic_check';
122
175
  resolveOnce({
123
176
  pid: childProcess.pid,
124
177
  output,
@@ -130,6 +183,7 @@ export class TerminalManager {
130
183
  // Timeout fallback
131
184
  setTimeout(() => {
132
185
  session.isBlocked = true;
186
+ exitReason = 'timeout';
133
187
  resolveOnce({
134
188
  pid: childProcess.pid,
135
189
  output,
@@ -153,6 +207,7 @@ export class TerminalManager {
153
207
  }
154
208
  this.sessions.delete(childProcess.pid);
155
209
  }
210
+ exitReason = 'process_exit';
156
211
  resolveOnce({
157
212
  pid: childProcess.pid,
158
213
  output,
@@ -2,6 +2,7 @@ import { configManager } from '../config-manager.js';
2
2
  import { SetConfigValueArgsSchema } from './schemas.js';
3
3
  import { getSystemInfo } from '../utils/system-info.js';
4
4
  import { currentClient } from '../server.js';
5
+ import { featureFlagManager } from '../utils/feature-flags.js';
5
6
  /**
6
7
  * Get the entire config including system information
7
8
  */
@@ -23,6 +24,7 @@ export async function getConfig() {
23
24
  const configWithSystemInfo = {
24
25
  ...config,
25
26
  currentClient,
27
+ featureFlags: featureFlagManager.getAll(),
26
28
  systemInfo: {
27
29
  ...systemInfo,
28
30
  memory
@@ -73,7 +73,7 @@ export async function giveFeedbackToDesktopCommander(params = {}) {
73
73
  content: [{
74
74
  type: "text",
75
75
  text: `❌ **Error opening feedback form**: ${error instanceof Error ? error.message : String(error)}\n\n` +
76
- `You can still access our feedback form directly at: https://tally.so/r/mYB6av\n\n` +
76
+ `You can still access our feedback form directly at: https://tally.so/r/mKqoKg\n\n` +
77
77
  `We appreciate your willingness to provide feedback!`
78
78
  }],
79
79
  isError: true
@@ -84,7 +84,7 @@ export async function giveFeedbackToDesktopCommander(params = {}) {
84
84
  * Build Tally.so URL with pre-filled parameters
85
85
  */
86
86
  async function buildTallyUrl(params, stats) {
87
- const baseUrl = 'https://tally.so/r/mYB6av';
87
+ const baseUrl = 'https://tally.so/r/mKqoKg';
88
88
  const urlParams = new URLSearchParams();
89
89
  // Only auto-filled hidden fields remain
90
90
  urlParams.set('tool_call_count', stats.totalToolCalls.toString());