@wonderwhy-er/desktop-commander 0.2.3 → 0.2.5

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/README.md +72 -4
  2. package/dist/config-manager.d.ts +6 -0
  3. package/dist/config-manager.js +1 -1
  4. package/dist/custom-stdio.d.ts +23 -2
  5. package/dist/custom-stdio.js +167 -12
  6. package/dist/handlers/edit-search-handlers.js +25 -6
  7. package/dist/handlers/terminal-handlers.d.ts +8 -4
  8. package/dist/handlers/terminal-handlers.js +16 -10
  9. package/dist/index-dxt.d.ts +2 -0
  10. package/dist/index-dxt.js +76 -0
  11. package/dist/index-with-startup-detection.d.ts +5 -0
  12. package/dist/index-with-startup-detection.js +180 -0
  13. package/dist/index.js +2 -0
  14. package/dist/server.d.ts +5 -0
  15. package/dist/server.js +345 -42
  16. package/dist/terminal-manager.d.ts +7 -0
  17. package/dist/terminal-manager.js +93 -18
  18. package/dist/tools/client.d.ts +10 -0
  19. package/dist/tools/client.js +13 -0
  20. package/dist/tools/config.d.ts +1 -1
  21. package/dist/tools/config.js +21 -3
  22. package/dist/tools/edit.js +4 -3
  23. package/dist/tools/environment.d.ts +55 -0
  24. package/dist/tools/environment.js +65 -0
  25. package/dist/tools/feedback.d.ts +8 -0
  26. package/dist/tools/feedback.js +132 -0
  27. package/dist/tools/filesystem.js +152 -57
  28. package/dist/tools/improved-process-tools.js +170 -29
  29. package/dist/tools/schemas.d.ts +20 -2
  30. package/dist/tools/schemas.js +20 -2
  31. package/dist/tools/usage.d.ts +5 -0
  32. package/dist/tools/usage.js +24 -0
  33. package/dist/types.d.ts +4 -0
  34. package/dist/utils/capture.js +23 -1
  35. package/dist/utils/early-logger.d.ts +4 -0
  36. package/dist/utils/early-logger.js +35 -0
  37. package/dist/utils/mcp-logger.d.ts +30 -0
  38. package/dist/utils/mcp-logger.js +59 -0
  39. package/dist/utils/smithery-detector.d.ts +94 -0
  40. package/dist/utils/smithery-detector.js +292 -0
  41. package/dist/utils/startup-detector.d.ts +65 -0
  42. package/dist/utils/startup-detector.js +390 -0
  43. package/dist/utils/usageTracker.d.ts +85 -0
  44. package/dist/utils/usageTracker.js +280 -0
  45. package/dist/version.d.ts +1 -1
  46. package/dist/version.js +1 -1
  47. package/package.json +3 -1
@@ -0,0 +1,390 @@
1
+ /**
2
+ * Production-Ready Startup Detection Module
3
+ *
4
+ * This module provides a lightweight, production-ready way to detect
5
+ * how a Node.js application was started. Perfect for logging, analytics,
6
+ * or conditional behavior based on startup method.
7
+ */
8
+ import { readFileSync } from 'fs';
9
+ import { platform } from 'os';
10
+ export class StartupDetector {
11
+ constructor() {
12
+ this._startupInfo = null;
13
+ }
14
+ static getInstance() {
15
+ if (!StartupDetector.instance) {
16
+ StartupDetector.instance = new StartupDetector();
17
+ }
18
+ return StartupDetector.instance;
19
+ }
20
+ /**
21
+ * Get startup information (cached after first call)
22
+ */
23
+ getStartupInfo() {
24
+ if (!this._startupInfo) {
25
+ this._startupInfo = this.detectStartupMethod();
26
+ }
27
+ return this._startupInfo;
28
+ }
29
+ /**
30
+ * Force re-detection (useful for testing)
31
+ */
32
+ forceRedetect() {
33
+ this._startupInfo = this.detectStartupMethod();
34
+ return this._startupInfo;
35
+ }
36
+ detectStartupMethod() {
37
+ const detectors = [
38
+ this.detectSmithery.bind(this),
39
+ this.detectNpmRun.bind(this),
40
+ this.detectNpx.bind(this),
41
+ this.detectDocker.bind(this),
42
+ this.detectCiCd.bind(this),
43
+ this.detectDirectNode.bind(this)
44
+ ];
45
+ const results = detectors.map(detector => detector()).filter(result => result !== null);
46
+ // Sort by confidence, highest first
47
+ results.sort((a, b) => b.confidence - a.confidence);
48
+ if (results.length === 0) {
49
+ return {
50
+ method: 'unknown',
51
+ confidence: 0,
52
+ details: { evidence: ['No detection method succeeded'] },
53
+ environment: 'unknown'
54
+ };
55
+ }
56
+ const topResult = results[0];
57
+ topResult.environment = this.detectEnvironment(topResult);
58
+ return topResult;
59
+ }
60
+ detectNpmRun() {
61
+ const evidence = [];
62
+ let confidence = 0;
63
+ // Primary indicator - npm_lifecycle_event is only set during npm run
64
+ if (process.env.npm_lifecycle_event) {
65
+ confidence += 50;
66
+ evidence.push(`npm script: ${process.env.npm_lifecycle_event}`);
67
+ // Additional confidence for npm script context
68
+ if (process.env.npm_lifecycle_script) {
69
+ confidence += 20;
70
+ }
71
+ return {
72
+ method: 'npm-run',
73
+ confidence,
74
+ details: {
75
+ npmScript: process.env.npm_lifecycle_event,
76
+ evidence
77
+ },
78
+ environment: 'unknown' // Will be set later
79
+ };
80
+ }
81
+ return null;
82
+ }
83
+ detectNpx() {
84
+ const evidence = [];
85
+ let confidence = 0;
86
+ // Check user agent for npx
87
+ const userAgent = process.env.npm_config_user_agent;
88
+ if (userAgent?.includes('npx')) {
89
+ confidence += 40;
90
+ evidence.push(`User agent indicates npx: ${userAgent}`);
91
+ }
92
+ // NPX specific environment variables
93
+ if (process.env.NPM_CLI_JS?.includes('npx')) {
94
+ confidence += 30;
95
+ evidence.push('NPM_CLI_JS indicates npx execution');
96
+ }
97
+ if (confidence > 0) {
98
+ return {
99
+ method: 'npx',
100
+ confidence,
101
+ details: { evidence },
102
+ environment: 'unknown'
103
+ };
104
+ }
105
+ return null;
106
+ }
107
+ detectDocker() {
108
+ const evidence = [];
109
+ let confidence = 0;
110
+ // Most reliable: Check for .dockerenv file
111
+ try {
112
+ readFileSync('/.dockerenv');
113
+ confidence += 70;
114
+ evidence.push('/.dockerenv file exists');
115
+ }
116
+ catch {
117
+ // Not in Docker
118
+ }
119
+ // Check container environment variable
120
+ if (process.env.container === 'docker' || process.env.container === 'podman') {
121
+ confidence += 50;
122
+ evidence.push(`Container type: ${process.env.container}`);
123
+ }
124
+ // Check for Docker-like hostname pattern
125
+ const hostname = process.env.HOSTNAME;
126
+ if (hostname && hostname.length === 12 && /^[a-f0-9]{12}$/.test(hostname)) {
127
+ confidence += 25;
128
+ evidence.push('Docker-style hostname detected');
129
+ }
130
+ // Linux-specific: Check cgroup
131
+ if (platform() === 'linux') {
132
+ try {
133
+ const cgroup = readFileSync('/proc/1/cgroup', 'utf8');
134
+ if (cgroup.includes('docker') || cgroup.includes('containerd')) {
135
+ confidence += 60;
136
+ evidence.push('Docker detected in cgroup');
137
+ }
138
+ }
139
+ catch {
140
+ // Ignore errors
141
+ }
142
+ }
143
+ if (confidence > 0) {
144
+ return {
145
+ method: 'docker',
146
+ confidence,
147
+ details: {
148
+ dockerContainer: true,
149
+ evidence
150
+ },
151
+ environment: 'unknown'
152
+ };
153
+ }
154
+ return null;
155
+ }
156
+ detectCiCd() {
157
+ const evidence = [];
158
+ let confidence = 0;
159
+ let ciPlatform;
160
+ const ciDetectors = {
161
+ 'GitHub Actions': () => process.env.GITHUB_ACTIONS === 'true',
162
+ 'GitLab CI': () => process.env.GITLAB_CI === 'true',
163
+ 'Jenkins': () => !!process.env.JENKINS_URL,
164
+ 'CircleCI': () => process.env.CIRCLECI === 'true',
165
+ 'Travis CI': () => process.env.TRAVIS === 'true',
166
+ 'Azure DevOps': () => process.env.TF_BUILD === 'True',
167
+ 'Bamboo': () => !!process.env.bamboo_buildKey,
168
+ 'TeamCity': () => !!process.env.TEAMCITY_VERSION
169
+ };
170
+ for (const [platform, detector] of Object.entries(ciDetectors)) {
171
+ if (detector()) {
172
+ ciPlatform = platform;
173
+ confidence += 60;
174
+ evidence.push(`${platform} environment detected`);
175
+ break;
176
+ }
177
+ }
178
+ // Generic CI detection
179
+ if (!ciPlatform && (process.env.CI === 'true' || process.env.CI === '1')) {
180
+ confidence += 40;
181
+ evidence.push('Generic CI environment');
182
+ ciPlatform = 'Generic CI';
183
+ }
184
+ if (confidence > 0) {
185
+ return {
186
+ method: 'ci-cd',
187
+ confidence,
188
+ details: {
189
+ ciPlatform,
190
+ evidence
191
+ },
192
+ environment: 'unknown'
193
+ };
194
+ }
195
+ return null;
196
+ }
197
+ detectDirectNode() {
198
+ const evidence = [];
199
+ let confidence = 0;
200
+ // If no npm/npx indicators are present, likely direct node
201
+ if (!process.env.npm_lifecycle_event &&
202
+ !process.env.npm_config_user_agent &&
203
+ !process.env.npm_execpath) {
204
+ confidence += 30;
205
+ evidence.push('No package manager environment variables');
206
+ }
207
+ // This is more of a fallback, so lower confidence
208
+ if (confidence > 0) {
209
+ return {
210
+ method: 'node-direct',
211
+ confidence,
212
+ details: { evidence },
213
+ environment: 'unknown'
214
+ };
215
+ }
216
+ return null;
217
+ }
218
+ detectSmithery() {
219
+ const evidence = [];
220
+ let confidence = 0;
221
+ let smitheryClient;
222
+ let smitheryConnection;
223
+ let smitherySession;
224
+ // Check for Smithery-specific environment variables
225
+ const smitheryEnvVars = [
226
+ 'SMITHERY_SESSION_ID',
227
+ 'SMITHERY_CLIENT',
228
+ 'SMITHERY_PROFILE',
229
+ 'SMITHERY_ANALYTICS',
230
+ 'SMITHERY_CONNECTION_TYPE',
231
+ 'SMITHERY_QUALIFIED_NAME'
232
+ ];
233
+ for (const envVar of smitheryEnvVars) {
234
+ if (process.env[envVar]) {
235
+ confidence += 40;
236
+ evidence.push(`Smithery environment variable: ${envVar}`);
237
+ // Extract specific details
238
+ switch (envVar) {
239
+ case 'SMITHERY_SESSION_ID':
240
+ smitherySession = process.env[envVar];
241
+ break;
242
+ case 'SMITHERY_CLIENT':
243
+ smitheryClient = process.env[envVar];
244
+ break;
245
+ case 'SMITHERY_CONNECTION_TYPE':
246
+ smitheryConnection = process.env[envVar];
247
+ break;
248
+ }
249
+ }
250
+ }
251
+ // Check for Smithery endpoints
252
+ if (process.env.REGISTRY_ENDPOINT?.includes('smithery')) {
253
+ confidence += 30;
254
+ evidence.push('Smithery registry endpoint detected');
255
+ }
256
+ if (process.env.ANALYTICS_ENDPOINT?.includes('smithery')) {
257
+ confidence += 25;
258
+ evidence.push('Smithery analytics endpoint detected');
259
+ }
260
+ // Check process arguments for Smithery patterns
261
+ const args = process.argv.join(' ');
262
+ const smitheryPatterns = [
263
+ /smithery[\s\/\\]cli/i,
264
+ /@smithery[\s\/\\]cli/i,
265
+ /npx.*@smithery/i,
266
+ /smithery.*run/i
267
+ ];
268
+ for (const pattern of smitheryPatterns) {
269
+ if (pattern.test(args)) {
270
+ confidence += 35;
271
+ evidence.push('Smithery CLI pattern in arguments');
272
+ break;
273
+ }
274
+ }
275
+ // Check for UUID v7 session pattern (Smithery uses uuidv7)
276
+ const envVars = Object.keys(process.env);
277
+ for (const key of envVars) {
278
+ const value = process.env[key] || '';
279
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)) {
280
+ confidence += 25;
281
+ evidence.push('UUID v7 session ID pattern detected');
282
+ if (!smitherySession)
283
+ smitherySession = value;
284
+ break;
285
+ }
286
+ }
287
+ if (confidence > 0) {
288
+ return {
289
+ method: 'smithery',
290
+ confidence,
291
+ details: {
292
+ smitheryClient,
293
+ smitheryConnection,
294
+ smitherySession,
295
+ evidence
296
+ },
297
+ environment: 'unknown'
298
+ };
299
+ }
300
+ return null;
301
+ }
302
+ detectEnvironment(startupInfo) {
303
+ // Smithery is always considered smithery environment
304
+ if (startupInfo.method === 'smithery') {
305
+ return 'smithery';
306
+ }
307
+ // Docker containers are usually production or CI
308
+ if (startupInfo.method === 'docker') {
309
+ // Check if it's also CI
310
+ if (process.env.CI === 'true' || process.env.CI === '1') {
311
+ return 'ci';
312
+ }
313
+ return 'container';
314
+ }
315
+ // Check NODE_ENV
316
+ const nodeEnv = process.env.NODE_ENV?.toLowerCase();
317
+ if (nodeEnv === 'production')
318
+ return 'production';
319
+ if (nodeEnv === 'development')
320
+ return 'development';
321
+ // NPM scripts often indicate development
322
+ if (startupInfo.method === 'npm-run') {
323
+ const script = startupInfo.details.npmScript;
324
+ if (script?.includes('dev') || script?.includes('start')) {
325
+ return 'development';
326
+ }
327
+ if (script?.includes('prod') || script?.includes('build')) {
328
+ return 'production';
329
+ }
330
+ return 'development'; // Default for npm scripts
331
+ }
332
+ // NPX is often used for one-off tools in development
333
+ if (startupInfo.method === 'npx') {
334
+ return 'development';
335
+ }
336
+ return 'unknown';
337
+ }
338
+ /**
339
+ * Get a simple string representation of how the app was started
340
+ */
341
+ getStartupMethodString() {
342
+ const info = this.getStartupInfo();
343
+ switch (info.method) {
344
+ case 'npm-run':
345
+ return `npm run ${info.details.npmScript || 'script'}`;
346
+ case 'npx':
347
+ return 'npx';
348
+ case 'docker':
349
+ return 'Docker container';
350
+ case 'ci-cd':
351
+ return info.details.ciPlatform || 'CI/CD';
352
+ case 'smithery':
353
+ return `Smithery CLI${info.details.smitheryClient ? ` (${info.details.smitheryClient})` : ''}`;
354
+ case 'node-direct':
355
+ return 'node (direct)';
356
+ default:
357
+ return 'unknown';
358
+ }
359
+ }
360
+ /**
361
+ * Check if running in a specific environment
362
+ */
363
+ isEnvironment(env) {
364
+ return this.getStartupInfo().environment === env;
365
+ }
366
+ /**
367
+ * Check if running via a specific method
368
+ */
369
+ isMethod(method) {
370
+ return this.getStartupInfo().method === method;
371
+ }
372
+ }
373
+ // Singleton instance
374
+ export const startupDetector = StartupDetector.getInstance();
375
+ // Convenience functions
376
+ export const getStartupInfo = () => startupDetector.getStartupInfo();
377
+ export const getStartupMethod = () => startupDetector.getStartupMethodString();
378
+ export const isProduction = () => startupDetector.isEnvironment('production');
379
+ export const isDevelopment = () => startupDetector.isEnvironment('development');
380
+ export const isDocker = () => startupDetector.isMethod('docker');
381
+ export const isCi = () => startupDetector.isEnvironment('ci');
382
+ export const isSmithery = () => startupDetector.isMethod('smithery');
383
+ export const getSmitheryClient = () => {
384
+ const info = startupDetector.getStartupInfo();
385
+ return info.method === 'smithery' ? info.details.smitheryClient : undefined;
386
+ };
387
+ export const getSmitheryConnection = () => {
388
+ const info = startupDetector.getStartupInfo();
389
+ return info.method === 'smithery' ? info.details.smitheryConnection : undefined;
390
+ };
@@ -0,0 +1,85 @@
1
+ export interface ToolUsageStats {
2
+ filesystemOperations: number;
3
+ terminalOperations: number;
4
+ editOperations: number;
5
+ searchOperations: number;
6
+ configOperations: number;
7
+ processOperations: number;
8
+ totalToolCalls: number;
9
+ successfulCalls: number;
10
+ failedCalls: number;
11
+ toolCounts: Record<string, number>;
12
+ firstUsed: number;
13
+ lastUsed: number;
14
+ totalSessions: number;
15
+ lastFeedbackPrompt: number;
16
+ }
17
+ export interface UsageSession {
18
+ sessionStart: number;
19
+ lastActivity: number;
20
+ commandsInSession: number;
21
+ }
22
+ declare class UsageTracker {
23
+ private currentSession;
24
+ /**
25
+ * Get default usage stats
26
+ */
27
+ private getDefaultStats;
28
+ /**
29
+ * Get current usage stats from config
30
+ */
31
+ getStats(): Promise<ToolUsageStats>;
32
+ /**
33
+ * Save usage stats to config
34
+ */
35
+ private saveStats;
36
+ /**
37
+ * Determine which category a tool belongs to
38
+ */
39
+ private getToolCategory;
40
+ /**
41
+ * Check if we're in a new session
42
+ */
43
+ private isNewSession;
44
+ /**
45
+ * Update session tracking
46
+ */
47
+ private updateSession;
48
+ /**
49
+ * Track a successful tool call
50
+ */
51
+ trackSuccess(toolName: string): Promise<ToolUsageStats>;
52
+ /**
53
+ * Track a failed tool call
54
+ */
55
+ trackFailure(toolName: string): Promise<ToolUsageStats>;
56
+ /**
57
+ * Check if user should be prompted for feedback based on usage patterns
58
+ */
59
+ shouldPromptForFeedback(): Promise<boolean>;
60
+ /**
61
+ * Get a random feedback prompt message with strong CTAs and clear actions
62
+ */
63
+ getFeedbackPromptMessage(): Promise<{
64
+ variant: string;
65
+ message: string;
66
+ }>;
67
+ /**
68
+ * Check if user should be prompted for error feedback
69
+ */
70
+ shouldPromptForErrorFeedback(): Promise<boolean>;
71
+ /**
72
+ * Mark that user was prompted for feedback
73
+ */
74
+ markFeedbackPrompted(): Promise<void>;
75
+ /**
76
+ * Mark that user has given feedback
77
+ */
78
+ markFeedbackGiven(): Promise<void>;
79
+ /**
80
+ * Get usage summary for debugging/admin purposes
81
+ */
82
+ getUsageSummary(): Promise<string>;
83
+ }
84
+ export declare const usageTracker: UsageTracker;
85
+ export {};