@wonderwhy-er/desktop-commander 0.2.9 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -5
- package/dist/custom-stdio.d.ts +14 -0
- package/dist/custom-stdio.js +140 -13
- package/dist/handlers/edit-search-handlers.d.ts +0 -5
- package/dist/handlers/edit-search-handlers.js +0 -82
- package/dist/handlers/filesystem-handlers.d.ts +0 -4
- package/dist/handlers/filesystem-handlers.js +2 -36
- package/dist/handlers/index.d.ts +1 -0
- package/dist/handlers/index.js +1 -0
- package/dist/handlers/search-handlers.d.ts +17 -0
- package/dist/handlers/search-handlers.js +219 -0
- package/dist/index.js +50 -64
- package/dist/npm-scripts/setup.d.ts +1 -0
- package/dist/npm-scripts/setup.js +40 -0
- package/dist/npm-scripts/uninstall.d.ts +1 -0
- package/dist/npm-scripts/uninstall.js +40 -0
- package/dist/search-manager.d.ts +107 -0
- package/dist/search-manager.js +467 -0
- package/dist/server.js +99 -31
- package/dist/setup.log +63 -0
- package/dist/tools/filesystem.js +59 -1
- package/dist/tools/schemas.d.ts +55 -41
- package/dist/tools/schemas.js +22 -16
- package/dist/tools/search.js +31 -3
- package/dist/track-installation.js +368 -0
- package/dist/utils/capture.js +56 -8
- package/dist/utils/dedent.d.ts +8 -0
- package/dist/utils/dedent.js +38 -0
- package/dist/utils/logger.d.ts +32 -0
- package/dist/utils/logger.js +72 -0
- package/dist/utils/system-info.d.ts +8 -2
- package/dist/utils/system-info.js +247 -30
- package/dist/utils/usageTracker.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Installation Source Tracking Script
|
|
5
|
+
* Runs during npm install to detect how Desktop Commander was installed
|
|
6
|
+
*
|
|
7
|
+
* Debug logging can be enabled with:
|
|
8
|
+
* - DEBUG=desktop-commander npm install
|
|
9
|
+
* - DEBUG=* npm install
|
|
10
|
+
* - NODE_ENV=development npm install
|
|
11
|
+
* - DC_DEBUG=true npm install
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { randomUUID } from 'crypto';
|
|
15
|
+
import * as https from 'https';
|
|
16
|
+
import { platform } from 'os';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
import { fileURLToPath } from 'url';
|
|
19
|
+
|
|
20
|
+
// Get current file directory
|
|
21
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
22
|
+
const __dirname = path.dirname(__filename);
|
|
23
|
+
|
|
24
|
+
// Debug logging utility - configurable via environment variables
|
|
25
|
+
const DEBUG_ENABLED = process.env.DEBUG === 'desktop-commander' ||
|
|
26
|
+
process.env.DEBUG === '*' ||
|
|
27
|
+
process.env.NODE_ENV === 'development' ||
|
|
28
|
+
process.env.DC_DEBUG === 'true';
|
|
29
|
+
|
|
30
|
+
const debug = (...args) => {
|
|
31
|
+
if (DEBUG_ENABLED) {
|
|
32
|
+
console.log('[Desktop Commander Debug]', ...args);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const log = (...args) => {
|
|
37
|
+
// Always show important messages, but prefix differently for debug vs production
|
|
38
|
+
if (DEBUG_ENABLED) {
|
|
39
|
+
console.log('[Desktop Commander]', ...args);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the client ID from the Desktop Commander config file, or generate a new one
|
|
45
|
+
*/
|
|
46
|
+
async function getClientId() {
|
|
47
|
+
try {
|
|
48
|
+
const { homedir } = await import('os');
|
|
49
|
+
const { join } = await import('path');
|
|
50
|
+
const fs = await import('fs');
|
|
51
|
+
|
|
52
|
+
const USER_HOME = homedir();
|
|
53
|
+
const CONFIG_DIR = join(USER_HOME, '.claude-server-commander');
|
|
54
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
55
|
+
|
|
56
|
+
// Try to read existing config
|
|
57
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
58
|
+
const configData = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
59
|
+
const config = JSON.parse(configData);
|
|
60
|
+
if (config.clientId) {
|
|
61
|
+
debug(`Using existing clientId from config: ${config.clientId.substring(0, 8)}...`);
|
|
62
|
+
return config.clientId;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
debug('No existing clientId found, generating new one');
|
|
67
|
+
// Fallback to random UUID if config doesn't exist or lacks clientId
|
|
68
|
+
return randomUUID();
|
|
69
|
+
} catch (error) {
|
|
70
|
+
debug(`Error reading config file: ${error.message}, using random UUID`);
|
|
71
|
+
// If anything goes wrong, fall back to random UUID
|
|
72
|
+
return randomUUID();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Google Analytics configuration (same as setup script)
|
|
77
|
+
const GA_MEASUREMENT_ID = 'G-NGGDNL0K4L';
|
|
78
|
+
const GA_API_SECRET = '5M0mC--2S_6t94m8WrI60A';
|
|
79
|
+
const GA_BASE_URL = `https://www.google-analytics.com/mp/collect?measurement_id=${GA_MEASUREMENT_ID}&api_secret=${GA_API_SECRET}`;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Detect installation source from environment and process context
|
|
83
|
+
*/
|
|
84
|
+
async function detectInstallationSource() {
|
|
85
|
+
// Check npm environment variables for clues
|
|
86
|
+
const npmConfigUserAgent = process.env.npm_config_user_agent || '';
|
|
87
|
+
const npmExecpath = process.env.npm_execpath || '';
|
|
88
|
+
const npmCommand = process.env.npm_command || '';
|
|
89
|
+
const npmLifecycleEvent = process.env.npm_lifecycle_event || '';
|
|
90
|
+
|
|
91
|
+
// Check process arguments and parent commands
|
|
92
|
+
const processArgs = process.argv.join(' ');
|
|
93
|
+
const processTitle = process.title || '';
|
|
94
|
+
|
|
95
|
+
debug('Installation source detection...');
|
|
96
|
+
debug(`npm_config_user_agent: ${npmConfigUserAgent}`);
|
|
97
|
+
debug(`npm_execpath: ${npmExecpath}`);
|
|
98
|
+
debug(`npm_command: ${npmCommand}`);
|
|
99
|
+
debug(`npm_lifecycle_event: ${npmLifecycleEvent}`);
|
|
100
|
+
debug(`process.argv: ${processArgs}`);
|
|
101
|
+
debug(`process.title: ${processTitle}`);
|
|
102
|
+
|
|
103
|
+
// Try to get parent process information
|
|
104
|
+
let parentProcessInfo = null;
|
|
105
|
+
try {
|
|
106
|
+
const { execSync } = await import('child_process');
|
|
107
|
+
const ppid = process.ppid;
|
|
108
|
+
if (ppid && process.platform !== 'win32') {
|
|
109
|
+
// Get parent process command line on Unix systems
|
|
110
|
+
const parentCmd = execSync(`ps -p ${ppid} -o command=`, { encoding: 'utf8' }).trim();
|
|
111
|
+
parentProcessInfo = parentCmd;
|
|
112
|
+
debug(`parent process: ${parentCmd}`);
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
debug(`Could not get parent process info: ${error.message}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Smithery detection - look for smithery in the process chain
|
|
119
|
+
const smitheryIndicators = [
|
|
120
|
+
npmConfigUserAgent.includes('smithery'),
|
|
121
|
+
npmExecpath.includes('smithery'),
|
|
122
|
+
processArgs.includes('smithery'),
|
|
123
|
+
processArgs.includes('@smithery/cli'),
|
|
124
|
+
processTitle.includes('smithery'),
|
|
125
|
+
parentProcessInfo && parentProcessInfo.includes('smithery'),
|
|
126
|
+
parentProcessInfo && parentProcessInfo.includes('@smithery/cli')
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
if (smitheryIndicators.some(indicator => indicator)) {
|
|
130
|
+
return {
|
|
131
|
+
source: 'smithery',
|
|
132
|
+
details: {
|
|
133
|
+
detection_method: 'process_chain',
|
|
134
|
+
user_agent: npmConfigUserAgent,
|
|
135
|
+
exec_path: npmExecpath,
|
|
136
|
+
command: npmCommand,
|
|
137
|
+
parent_process: parentProcessInfo || 'unknown',
|
|
138
|
+
process_args: processArgs
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Direct NPX usage
|
|
144
|
+
if (npmCommand === 'exec' || processArgs.includes('npx')) {
|
|
145
|
+
return {
|
|
146
|
+
source: 'npx-direct',
|
|
147
|
+
details: {
|
|
148
|
+
user_agent: npmConfigUserAgent,
|
|
149
|
+
command: npmCommand,
|
|
150
|
+
lifecycle_event: npmLifecycleEvent
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Regular npm install
|
|
156
|
+
if (npmCommand === 'install' || npmLifecycleEvent === 'postinstall') {
|
|
157
|
+
return {
|
|
158
|
+
source: 'npm-install',
|
|
159
|
+
details: {
|
|
160
|
+
user_agent: npmConfigUserAgent,
|
|
161
|
+
command: npmCommand,
|
|
162
|
+
lifecycle_event: npmLifecycleEvent
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// GitHub Codespaces
|
|
168
|
+
if (process.env.CODESPACES) {
|
|
169
|
+
return {
|
|
170
|
+
source: 'github-codespaces',
|
|
171
|
+
details: {
|
|
172
|
+
codespace: process.env.CODESPACE_NAME || 'unknown'
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// VS Code
|
|
178
|
+
if (process.env.VSCODE_PID || process.env.TERM_PROGRAM === 'vscode') {
|
|
179
|
+
return {
|
|
180
|
+
source: 'vscode',
|
|
181
|
+
details: {
|
|
182
|
+
term_program: process.env.TERM_PROGRAM,
|
|
183
|
+
vscode_pid: process.env.VSCODE_PID
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// GitPod
|
|
189
|
+
if (process.env.GITPOD_WORKSPACE_ID) {
|
|
190
|
+
return {
|
|
191
|
+
source: 'gitpod',
|
|
192
|
+
details: {
|
|
193
|
+
workspace_id: process.env.GITPOD_WORKSPACE_ID.substring(0, 8) + '...' // Truncate for privacy
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// CI/CD environments
|
|
199
|
+
if (process.env.CI) {
|
|
200
|
+
if (process.env.GITHUB_ACTIONS) {
|
|
201
|
+
return {
|
|
202
|
+
source: 'github-actions',
|
|
203
|
+
details: {
|
|
204
|
+
repository: process.env.GITHUB_REPOSITORY,
|
|
205
|
+
workflow: process.env.GITHUB_WORKFLOW
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (process.env.GITLAB_CI) {
|
|
210
|
+
return {
|
|
211
|
+
source: 'gitlab-ci',
|
|
212
|
+
details: {
|
|
213
|
+
project: process.env.CI_PROJECT_NAME
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
if (process.env.JENKINS_URL) {
|
|
218
|
+
return {
|
|
219
|
+
source: 'jenkins',
|
|
220
|
+
details: {
|
|
221
|
+
job: process.env.JOB_NAME
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
source: 'ci-cd-other',
|
|
227
|
+
details: {
|
|
228
|
+
ci_env: 'unknown'
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Docker detection
|
|
234
|
+
if (process.env.DOCKER_CONTAINER) {
|
|
235
|
+
return {
|
|
236
|
+
source: 'docker',
|
|
237
|
+
details: {
|
|
238
|
+
container_id: process.env.HOSTNAME?.substring(0, 8) + '...' || 'unknown'
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Check for .dockerenv file (need to use fs import)
|
|
244
|
+
try {
|
|
245
|
+
const fs = await import('fs');
|
|
246
|
+
if (fs.existsSync('/.dockerenv')) {
|
|
247
|
+
return {
|
|
248
|
+
source: 'docker',
|
|
249
|
+
details: {
|
|
250
|
+
container_id: process.env.HOSTNAME?.substring(0, 8) + '...' || 'unknown'
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
} catch (error) {
|
|
255
|
+
// Ignore fs errors
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Default fallback
|
|
259
|
+
return {
|
|
260
|
+
source: 'unknown',
|
|
261
|
+
details: {
|
|
262
|
+
user_agent: npmConfigUserAgent || 'none',
|
|
263
|
+
command: npmCommand || 'none',
|
|
264
|
+
lifecycle: npmLifecycleEvent || 'none'
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Send installation tracking to analytics
|
|
271
|
+
*/
|
|
272
|
+
async function trackInstallation(installationData) {
|
|
273
|
+
if (!GA_MEASUREMENT_ID || !GA_API_SECRET) {
|
|
274
|
+
debug('Analytics not configured, skipping tracking');
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
const uniqueUserId = await getClientId();
|
|
280
|
+
log("user id", uniqueUserId)
|
|
281
|
+
// Prepare GA4 payload
|
|
282
|
+
const payload = {
|
|
283
|
+
client_id: uniqueUserId,
|
|
284
|
+
non_personalized_ads: false,
|
|
285
|
+
timestamp_micros: Date.now() * 1000,
|
|
286
|
+
events: [{
|
|
287
|
+
name: 'package_installed',
|
|
288
|
+
params: {
|
|
289
|
+
timestamp: new Date().toISOString(),
|
|
290
|
+
platform: platform(),
|
|
291
|
+
installation_source: installationData.source,
|
|
292
|
+
installation_details: JSON.stringify(installationData.details),
|
|
293
|
+
package_name: '@wonderwhy-er/desktop-commander',
|
|
294
|
+
install_method: 'npm-lifecycle',
|
|
295
|
+
node_version: process.version,
|
|
296
|
+
npm_version: process.env.npm_version || 'unknown'
|
|
297
|
+
}
|
|
298
|
+
}]
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const postData = JSON.stringify(payload);
|
|
302
|
+
|
|
303
|
+
const options = {
|
|
304
|
+
method: 'POST',
|
|
305
|
+
headers: {
|
|
306
|
+
'Content-Type': 'application/json',
|
|
307
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
await new Promise((resolve, reject) => {
|
|
312
|
+
const req = https.request(GA_BASE_URL, options);
|
|
313
|
+
|
|
314
|
+
const timeoutId = setTimeout(() => {
|
|
315
|
+
req.destroy();
|
|
316
|
+
reject(new Error('Request timeout'));
|
|
317
|
+
}, 5000);
|
|
318
|
+
|
|
319
|
+
req.on('error', (error) => {
|
|
320
|
+
clearTimeout(timeoutId);
|
|
321
|
+
debug(`Analytics error: ${error.message}`);
|
|
322
|
+
resolve(); // Don't fail installation on analytics error
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
req.on('response', (res) => {
|
|
326
|
+
clearTimeout(timeoutId);
|
|
327
|
+
// Consume the response data to complete the request
|
|
328
|
+
res.on('data', () => {}); // Ignore response data
|
|
329
|
+
res.on('end', () => {
|
|
330
|
+
log(`Installation tracked: ${installationData.source}`);
|
|
331
|
+
resolve();
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
req.write(postData);
|
|
336
|
+
req.end();
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
} catch (error) {
|
|
340
|
+
debug(`Failed to track installation: ${error.message}`);
|
|
341
|
+
// Don't fail the installation process
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Main execution
|
|
347
|
+
*/
|
|
348
|
+
async function main() {
|
|
349
|
+
try {
|
|
350
|
+
log('Package installation detected');
|
|
351
|
+
|
|
352
|
+
const installationData = await detectInstallationSource();
|
|
353
|
+
log(`Installation source: ${installationData.source}`);
|
|
354
|
+
|
|
355
|
+
await trackInstallation(installationData);
|
|
356
|
+
|
|
357
|
+
} catch (error) {
|
|
358
|
+
debug(`Installation tracking error: ${error.message}`);
|
|
359
|
+
// Don't fail the installation
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Only run if this script is executed directly (not imported)
|
|
364
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
365
|
+
main();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export { detectInstallationSource, trackInstallation };
|
package/dist/utils/capture.js
CHANGED
|
@@ -123,16 +123,63 @@ export const captureBase = async (captureURL, event, properties) => {
|
|
|
123
123
|
if (process.env.MCP_DXT) {
|
|
124
124
|
isDXT = 'true';
|
|
125
125
|
}
|
|
126
|
-
// Is MCP running in a
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
// Is MCP running in a container - use robust detection
|
|
127
|
+
const { getSystemInfo } = await import('./system-info.js');
|
|
128
|
+
const systemInfo = getSystemInfo();
|
|
129
|
+
const isContainer = systemInfo.docker.isContainer ? 'true' : 'false';
|
|
130
|
+
const containerType = systemInfo.docker.containerType || 'none';
|
|
131
|
+
const orchestrator = systemInfo.docker.orchestrator || 'none';
|
|
132
|
+
// Add container metadata (with privacy considerations)
|
|
133
|
+
let containerName = 'none';
|
|
134
|
+
let containerImage = 'none';
|
|
135
|
+
if (systemInfo.docker.isContainer && systemInfo.docker.containerEnvironment) {
|
|
136
|
+
const env = systemInfo.docker.containerEnvironment;
|
|
137
|
+
// Container name - sanitize to remove potentially sensitive info
|
|
138
|
+
if (env.containerName) {
|
|
139
|
+
// Keep only alphanumeric chars, dashes, and underscores
|
|
140
|
+
// Remove random IDs and UUIDs for privacy
|
|
141
|
+
containerName = env.containerName
|
|
142
|
+
.replace(/[0-9a-f]{8,}/gi, 'ID') // Replace long hex strings with 'ID'
|
|
143
|
+
.replace(/[0-9]{8,}/g, 'ID') // Replace long numeric IDs with 'ID'
|
|
144
|
+
.substring(0, 50); // Limit length
|
|
145
|
+
}
|
|
146
|
+
// Docker image - sanitize registry info for privacy
|
|
147
|
+
if (env.dockerImage) {
|
|
148
|
+
// Remove registry URLs and keep just image:tag format
|
|
149
|
+
containerImage = env.dockerImage
|
|
150
|
+
.replace(/^[^/]+\/[^/]+\//, '') // Remove registry.com/namespace/ prefix
|
|
151
|
+
.replace(/^[^/]+\//, '') // Remove simple registry.com/ prefix
|
|
152
|
+
.replace(/@sha256:.*$/, '') // Remove digest hashes
|
|
153
|
+
.substring(0, 100); // Limit length
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Detect if we're running through Smithery at runtime
|
|
157
|
+
let runtimeSource = 'unknown';
|
|
158
|
+
const processArgs = process.argv.join(' ');
|
|
159
|
+
try {
|
|
160
|
+
if (processArgs.includes('@smithery/cli') || processArgs.includes('smithery')) {
|
|
161
|
+
runtimeSource = 'smithery-runtime';
|
|
162
|
+
}
|
|
163
|
+
else if (processArgs.includes('npx')) {
|
|
164
|
+
runtimeSource = 'npx-runtime';
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
runtimeSource = 'direct-runtime';
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
// Ignore detection errors
|
|
130
172
|
}
|
|
131
173
|
// Prepare standard properties
|
|
132
174
|
const baseProperties = {
|
|
133
175
|
timestamp: new Date().toISOString(),
|
|
134
176
|
platform: platform(),
|
|
135
|
-
|
|
177
|
+
isContainer,
|
|
178
|
+
containerType,
|
|
179
|
+
orchestrator,
|
|
180
|
+
containerName,
|
|
181
|
+
containerImage,
|
|
182
|
+
runtimeSource,
|
|
136
183
|
isDXT,
|
|
137
184
|
app_version: VERSION,
|
|
138
185
|
engagement_time_msec: "100"
|
|
@@ -169,13 +216,14 @@ export const captureBase = async (captureURL, event, properties) => {
|
|
|
169
216
|
data += chunk;
|
|
170
217
|
});
|
|
171
218
|
res.on('end', () => {
|
|
172
|
-
|
|
219
|
+
const success = res.statusCode === 200 || res.statusCode === 204;
|
|
220
|
+
if (!success) {
|
|
173
221
|
// Optional debug logging
|
|
174
222
|
// console.debug(`GA tracking error: ${res.statusCode} ${data}`);
|
|
175
223
|
}
|
|
176
224
|
});
|
|
177
225
|
});
|
|
178
|
-
req.on('error', () => {
|
|
226
|
+
req.on('error', (error) => {
|
|
179
227
|
// Silently fail - we don't want analytics issues to break functionality
|
|
180
228
|
});
|
|
181
229
|
// Set timeout to prevent blocking the app
|
|
@@ -186,7 +234,7 @@ export const captureBase = async (captureURL, event, properties) => {
|
|
|
186
234
|
req.write(postData);
|
|
187
235
|
req.end();
|
|
188
236
|
}
|
|
189
|
-
catch {
|
|
237
|
+
catch (error) {
|
|
190
238
|
// Silently fail - we don't want analytics issues to break functionality
|
|
191
239
|
}
|
|
192
240
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes common leading whitespace from all lines in a template literal.
|
|
3
|
+
* This function helps clean up indented template literals used for tool descriptions.
|
|
4
|
+
*
|
|
5
|
+
* @param text - The template literal string with potential leading whitespace
|
|
6
|
+
* @returns The dedented string with leading whitespace removed
|
|
7
|
+
*/
|
|
8
|
+
export declare function dedent(text: string): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes common leading whitespace from all lines in a template literal.
|
|
3
|
+
* This function helps clean up indented template literals used for tool descriptions.
|
|
4
|
+
*
|
|
5
|
+
* @param text - The template literal string with potential leading whitespace
|
|
6
|
+
* @returns The dedented string with leading whitespace removed
|
|
7
|
+
*/
|
|
8
|
+
export function dedent(text) {
|
|
9
|
+
// Split into lines
|
|
10
|
+
const lines = text.split('\n');
|
|
11
|
+
// Remove first and last lines if they're empty
|
|
12
|
+
if (lines[0] === '')
|
|
13
|
+
lines.shift();
|
|
14
|
+
if (lines[lines.length - 1] === '')
|
|
15
|
+
lines.pop();
|
|
16
|
+
// If no lines remain, return empty string
|
|
17
|
+
if (lines.length === 0)
|
|
18
|
+
return '';
|
|
19
|
+
// Find the minimum indentation (excluding empty lines)
|
|
20
|
+
let minIndent = Infinity;
|
|
21
|
+
for (const line of lines) {
|
|
22
|
+
if (line.trim() === '')
|
|
23
|
+
continue; // Skip empty lines
|
|
24
|
+
const indent = line.match(/^(\s*)/)?.[1]?.length || 0;
|
|
25
|
+
minIndent = Math.min(minIndent, indent);
|
|
26
|
+
}
|
|
27
|
+
// If no indentation found, return as-is
|
|
28
|
+
if (minIndent === 0 || minIndent === Infinity) {
|
|
29
|
+
return lines.join('\n');
|
|
30
|
+
}
|
|
31
|
+
// Remove the common indentation from all lines
|
|
32
|
+
const dedentedLines = lines.map(line => {
|
|
33
|
+
if (line.trim() === '')
|
|
34
|
+
return ''; // Keep empty lines empty
|
|
35
|
+
return line.slice(minIndent);
|
|
36
|
+
});
|
|
37
|
+
return dedentedLines.join('\n');
|
|
38
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized logging utility for Desktop Commander
|
|
3
|
+
* Ensures all logging goes through proper channels based on initialization state
|
|
4
|
+
*/
|
|
5
|
+
import type { FilteredStdioServerTransport } from '../custom-stdio.js';
|
|
6
|
+
declare global {
|
|
7
|
+
var mcpTransport: FilteredStdioServerTransport | undefined;
|
|
8
|
+
}
|
|
9
|
+
export type LogLevel = 'emergency' | 'alert' | 'critical' | 'error' | 'warning' | 'notice' | 'info' | 'debug';
|
|
10
|
+
/**
|
|
11
|
+
* Log a message using the appropriate method based on MCP initialization state
|
|
12
|
+
*/
|
|
13
|
+
export declare function log(level: LogLevel, message: string, data?: any): void;
|
|
14
|
+
/**
|
|
15
|
+
* Convenience functions for different log levels
|
|
16
|
+
*/
|
|
17
|
+
export declare const logger: {
|
|
18
|
+
emergency: (message: string, data?: any) => void;
|
|
19
|
+
alert: (message: string, data?: any) => void;
|
|
20
|
+
critical: (message: string, data?: any) => void;
|
|
21
|
+
error: (message: string, data?: any) => void;
|
|
22
|
+
warning: (message: string, data?: any) => void;
|
|
23
|
+
notice: (message: string, data?: any) => void;
|
|
24
|
+
info: (message: string, data?: any) => void;
|
|
25
|
+
debug: (message: string, data?: any) => void;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Log to stderr during early initialization (before MCP is ready)
|
|
29
|
+
* Use this for critical startup messages that must be visible
|
|
30
|
+
* NOTE: This should also be JSON-RPC format
|
|
31
|
+
*/
|
|
32
|
+
export declare function logToStderr(level: LogLevel, message: string): void;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized logging utility for Desktop Commander
|
|
3
|
+
* Ensures all logging goes through proper channels based on initialization state
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Log a message using the appropriate method based on MCP initialization state
|
|
7
|
+
*/
|
|
8
|
+
export function log(level, message, data) {
|
|
9
|
+
try {
|
|
10
|
+
// Check if MCP transport is available
|
|
11
|
+
if (global.mcpTransport) {
|
|
12
|
+
// Always use MCP logging (will buffer if not initialized yet)
|
|
13
|
+
global.mcpTransport.sendLog(level, message, data);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
// This should rarely happen, but fallback to create a JSON-RPC notification manually
|
|
17
|
+
const notification = {
|
|
18
|
+
jsonrpc: "2.0",
|
|
19
|
+
method: "notifications/message",
|
|
20
|
+
params: {
|
|
21
|
+
level: level,
|
|
22
|
+
logger: "desktop-commander",
|
|
23
|
+
data: data ? { message, ...data } : message
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
process.stdout.write(JSON.stringify(notification) + '\n');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
// Ultimate fallback - but this should be JSON-RPC too
|
|
31
|
+
const notification = {
|
|
32
|
+
jsonrpc: "2.0",
|
|
33
|
+
method: "notifications/message",
|
|
34
|
+
params: {
|
|
35
|
+
level: "error",
|
|
36
|
+
logger: "desktop-commander",
|
|
37
|
+
data: `[LOG-ERROR] Failed to log message: ${message}`
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
process.stdout.write(JSON.stringify(notification) + '\n');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Convenience functions for different log levels
|
|
45
|
+
*/
|
|
46
|
+
export const logger = {
|
|
47
|
+
emergency: (message, data) => log('emergency', message, data),
|
|
48
|
+
alert: (message, data) => log('alert', message, data),
|
|
49
|
+
critical: (message, data) => log('critical', message, data),
|
|
50
|
+
error: (message, data) => log('error', message, data),
|
|
51
|
+
warning: (message, data) => log('warning', message, data),
|
|
52
|
+
notice: (message, data) => log('notice', message, data),
|
|
53
|
+
info: (message, data) => log('info', message, data),
|
|
54
|
+
debug: (message, data) => log('debug', message, data),
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Log to stderr during early initialization (before MCP is ready)
|
|
58
|
+
* Use this for critical startup messages that must be visible
|
|
59
|
+
* NOTE: This should also be JSON-RPC format
|
|
60
|
+
*/
|
|
61
|
+
export function logToStderr(level, message) {
|
|
62
|
+
const notification = {
|
|
63
|
+
jsonrpc: "2.0",
|
|
64
|
+
method: "notifications/message",
|
|
65
|
+
params: {
|
|
66
|
+
level: level,
|
|
67
|
+
logger: "desktop-commander",
|
|
68
|
+
data: message
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
process.stdout.write(JSON.stringify(notification) + '\n');
|
|
72
|
+
}
|
|
@@ -5,13 +5,19 @@ export interface DockerMount {
|
|
|
5
5
|
readOnly: boolean;
|
|
6
6
|
description: string;
|
|
7
7
|
}
|
|
8
|
-
export interface
|
|
8
|
+
export interface ContainerInfo {
|
|
9
|
+
isContainer: boolean;
|
|
10
|
+
containerType: 'docker' | 'podman' | 'kubernetes' | 'lxc' | 'systemd-nspawn' | 'other' | null;
|
|
11
|
+
orchestrator: 'kubernetes' | 'docker-compose' | 'docker-swarm' | 'podman-compose' | null;
|
|
9
12
|
isDocker: boolean;
|
|
10
13
|
mountPoints: DockerMount[];
|
|
11
14
|
containerEnvironment?: {
|
|
12
15
|
dockerImage?: string;
|
|
13
16
|
containerName?: string;
|
|
14
17
|
hostPlatform?: string;
|
|
18
|
+
kubernetesNamespace?: string;
|
|
19
|
+
kubernetesPod?: string;
|
|
20
|
+
kubernetesNode?: string;
|
|
15
21
|
};
|
|
16
22
|
}
|
|
17
23
|
export interface SystemInfo {
|
|
@@ -22,7 +28,7 @@ export interface SystemInfo {
|
|
|
22
28
|
isWindows: boolean;
|
|
23
29
|
isMacOS: boolean;
|
|
24
30
|
isLinux: boolean;
|
|
25
|
-
docker:
|
|
31
|
+
docker: ContainerInfo;
|
|
26
32
|
examplePaths: {
|
|
27
33
|
home: string;
|
|
28
34
|
temp: string;
|