@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.
@@ -2,37 +2,111 @@ import os from 'os';
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
  /**
5
- * Detect if running inside Docker container
5
+ * Detect container environment and type
6
6
  */
7
- function isRunningInDocker() {
8
- // Method 1: MCP_CLIENT_DOCKER environment variable (set in Dockerfile)
7
+ function detectContainerEnvironment() {
8
+ // Method 1: Check environment variables first (most reliable when set)
9
+ // Docker-specific
9
10
  if (process.env.MCP_CLIENT_DOCKER === 'true') {
10
- return true;
11
+ return { isContainer: true, containerType: 'docker', orchestrator: null };
11
12
  }
12
- // Method 2: Check for .dockerenv file
13
+ // Kubernetes detection
14
+ if (process.env.KUBERNETES_SERVICE_HOST || process.env.KUBERNETES_PORT) {
15
+ return { isContainer: true, containerType: 'kubernetes', orchestrator: 'kubernetes' };
16
+ }
17
+ // Podman detection
18
+ if (process.env.PODMAN_CONTAINER || process.env.CONTAINER_HOST?.includes('podman')) {
19
+ return { isContainer: true, containerType: 'podman', orchestrator: null };
20
+ }
21
+ // Method 2: Check for container indicator files
13
22
  if (fs.existsSync('/.dockerenv')) {
14
- return true;
23
+ return { isContainer: true, containerType: 'docker', orchestrator: null };
15
24
  }
16
25
  // Method 3: Check /proc/1/cgroup for container indicators (Linux only)
17
26
  if (os.platform() === 'linux') {
18
27
  try {
19
28
  const cgroup = fs.readFileSync('/proc/1/cgroup', 'utf8');
20
- if (cgroup.includes('docker') || cgroup.includes('containerd')) {
21
- return true;
29
+ // Docker detection
30
+ if (cgroup.includes('docker')) {
31
+ return { isContainer: true, containerType: 'docker', orchestrator: null };
32
+ }
33
+ // Kubernetes detection (pods run in containerd/cri-o)
34
+ if (cgroup.includes('kubepods') || cgroup.includes('pod')) {
35
+ return { isContainer: true, containerType: 'kubernetes', orchestrator: 'kubernetes' };
36
+ }
37
+ // Podman detection
38
+ if (cgroup.includes('podman') || cgroup.includes('libpod')) {
39
+ return { isContainer: true, containerType: 'podman', orchestrator: null };
40
+ }
41
+ // LXC detection
42
+ if (cgroup.includes('lxc')) {
43
+ return { isContainer: true, containerType: 'lxc', orchestrator: null };
44
+ }
45
+ // systemd-nspawn detection
46
+ if (cgroup.includes('machine.slice')) {
47
+ return { isContainer: true, containerType: 'systemd-nspawn', orchestrator: null };
48
+ }
49
+ // Generic containerd detection
50
+ if (cgroup.includes('containerd')) {
51
+ return { isContainer: true, containerType: 'other', orchestrator: null };
22
52
  }
23
53
  }
24
54
  catch (error) {
25
55
  // /proc/1/cgroup might not exist
26
56
  }
27
57
  }
28
- return false;
58
+ // Method 4: Check /proc/1/environ for container indicators
59
+ if (os.platform() === 'linux') {
60
+ try {
61
+ const environ = fs.readFileSync('/proc/1/environ', 'utf8');
62
+ if (environ.includes('container=')) {
63
+ // systemd-nspawn sets container=systemd-nspawn
64
+ if (environ.includes('container=systemd-nspawn')) {
65
+ return { isContainer: true, containerType: 'systemd-nspawn', orchestrator: null };
66
+ }
67
+ // LXC sets container=lxc
68
+ if (environ.includes('container=lxc')) {
69
+ return { isContainer: true, containerType: 'lxc', orchestrator: null };
70
+ }
71
+ // Generic container detection
72
+ return { isContainer: true, containerType: 'other', orchestrator: null };
73
+ }
74
+ }
75
+ catch (error) {
76
+ // /proc/1/environ might not exist or be accessible
77
+ }
78
+ }
79
+ // Method 5: Check hostname for Kubernetes patterns
80
+ try {
81
+ const hostname = os.hostname();
82
+ // Kubernetes pods often have hostnames like: podname-deploymentid-randomid
83
+ if (hostname && (hostname.includes('-') && hostname.split('-').length >= 3)) {
84
+ // Additional check for Kubernetes service account
85
+ if (fs.existsSync('/var/run/secrets/kubernetes.io')) {
86
+ return { isContainer: true, containerType: 'kubernetes', orchestrator: 'kubernetes' };
87
+ }
88
+ }
89
+ }
90
+ catch (error) {
91
+ // Hostname check failed
92
+ }
93
+ // Method 6: Check for orchestrator-specific indicators
94
+ // Docker Compose detection
95
+ if (process.env.COMPOSE_PROJECT_NAME || process.env.COMPOSE_SERVICE) {
96
+ return { isContainer: true, containerType: 'docker', orchestrator: 'docker-compose' };
97
+ }
98
+ // Docker Swarm detection
99
+ if (process.env.DOCKER_SWARM_MODE || process.env.DOCKER_NODE_ID) {
100
+ return { isContainer: true, containerType: 'docker', orchestrator: 'docker-swarm' };
101
+ }
102
+ return { isContainer: false, containerType: null, orchestrator: null };
29
103
  }
30
104
  /**
31
- * Discover Docker mount points
105
+ * Discover container mount points
32
106
  */
33
- function discoverDockerMounts() {
107
+ function discoverContainerMounts(isContainer) {
34
108
  const mounts = [];
35
- if (!isRunningInDocker()) {
109
+ if (!isContainer) {
36
110
  return mounts;
37
111
  }
38
112
  // Method 1: Parse /proc/mounts (Linux only)
@@ -140,7 +214,7 @@ function discoverDockerMounts() {
140
214
  /**
141
215
  * Get container environment information
142
216
  */
143
- function getContainerEnvironment() {
217
+ function getContainerEnvironment(containerType) {
144
218
  const env = {};
145
219
  // Try to get container name from hostname (often set to container ID/name)
146
220
  try {
@@ -152,9 +226,78 @@ function getContainerEnvironment() {
152
226
  catch (error) {
153
227
  // Hostname not available
154
228
  }
155
- // Try to get Docker image from environment variables
156
- if (process.env.DOCKER_IMAGE) {
157
- env.dockerImage = process.env.DOCKER_IMAGE;
229
+ // Docker-specific environment
230
+ if (containerType === 'docker') {
231
+ // Try multiple sources for Docker image name
232
+ if (process.env.DOCKER_IMAGE) {
233
+ env.dockerImage = process.env.DOCKER_IMAGE;
234
+ }
235
+ else if (process.env.IMAGE_NAME) {
236
+ env.dockerImage = process.env.IMAGE_NAME;
237
+ }
238
+ else if (process.env.CONTAINER_IMAGE) {
239
+ env.dockerImage = process.env.CONTAINER_IMAGE;
240
+ }
241
+ // Try to read from Docker labels if available (less common but possible)
242
+ try {
243
+ if (fs.existsSync('/proc/self/cgroup')) {
244
+ const cgroup = fs.readFileSync('/proc/self/cgroup', 'utf8');
245
+ // Extract container ID from cgroup path
246
+ const containerIdMatch = cgroup.match(/docker\/([a-f0-9]{64})/);
247
+ if (containerIdMatch && !env.containerName) {
248
+ // Use short container ID as fallback name
249
+ env.containerName = containerIdMatch[1].substring(0, 12);
250
+ }
251
+ }
252
+ }
253
+ catch (error) {
254
+ // Ignore errors reading cgroup
255
+ }
256
+ }
257
+ // Kubernetes-specific environment
258
+ if (containerType === 'kubernetes') {
259
+ if (process.env.KUBERNETES_NAMESPACE || process.env.POD_NAMESPACE) {
260
+ env.kubernetesNamespace = process.env.KUBERNETES_NAMESPACE || process.env.POD_NAMESPACE;
261
+ }
262
+ if (process.env.POD_NAME || process.env.HOSTNAME) {
263
+ env.kubernetesPod = process.env.POD_NAME || process.env.HOSTNAME;
264
+ }
265
+ if (process.env.NODE_NAME || process.env.KUBERNETES_NODE_NAME) {
266
+ env.kubernetesNode = process.env.NODE_NAME || process.env.KUBERNETES_NODE_NAME;
267
+ }
268
+ // Try to get container image from common Kubernetes environment variables
269
+ if (process.env.CONTAINER_IMAGE) {
270
+ env.dockerImage = process.env.CONTAINER_IMAGE;
271
+ }
272
+ else if (process.env.IMAGE_NAME) {
273
+ env.dockerImage = process.env.IMAGE_NAME;
274
+ }
275
+ // Try to read Kubernetes service account info
276
+ try {
277
+ if (fs.existsSync('/var/run/secrets/kubernetes.io/serviceaccount/namespace')) {
278
+ const namespace = fs.readFileSync('/var/run/secrets/kubernetes.io/serviceaccount/namespace', 'utf8').trim();
279
+ if (namespace && !env.kubernetesNamespace) {
280
+ env.kubernetesNamespace = namespace;
281
+ }
282
+ }
283
+ }
284
+ catch (error) {
285
+ // Service account info not available
286
+ }
287
+ }
288
+ // Podman-specific environment
289
+ if (containerType === 'podman') {
290
+ // Podman uses similar environment variables to Docker
291
+ if (process.env.CONTAINER_IMAGE || process.env.PODMAN_IMAGE) {
292
+ env.dockerImage = process.env.CONTAINER_IMAGE || process.env.PODMAN_IMAGE;
293
+ }
294
+ }
295
+ // LXC-specific environment
296
+ if (containerType === 'lxc') {
297
+ // LXC containers might have different naming conventions
298
+ if (process.env.LXC_NAME) {
299
+ env.containerName = process.env.LXC_NAME;
300
+ }
158
301
  }
159
302
  // Try to detect host platform
160
303
  if (process.env.HOST_PLATFORM) {
@@ -170,9 +313,9 @@ export function getSystemInfo() {
170
313
  const isWindows = platform === 'win32';
171
314
  const isMacOS = platform === 'darwin';
172
315
  const isLinux = platform === 'linux';
173
- // Docker detection
174
- const dockerDetected = isRunningInDocker();
175
- const mountPoints = dockerDetected ? discoverDockerMounts() : [];
316
+ // Container detection
317
+ const containerDetection = detectContainerEnvironment();
318
+ const mountPoints = containerDetection.isContainer ? discoverContainerMounts(containerDetection.isContainer) : [];
176
319
  let platformName;
177
320
  let defaultShell;
178
321
  let pathSeparator;
@@ -218,9 +361,37 @@ export function getSystemInfo() {
218
361
  absolute: '/path/to/file.txt'
219
362
  };
220
363
  }
221
- // Adjust platform name for Docker
222
- if (dockerDetected) {
223
- platformName = `${platformName} (Docker)`;
364
+ // Adjust platform name for containers
365
+ if (containerDetection.isContainer) {
366
+ let containerLabel = '';
367
+ if (containerDetection.containerType === 'kubernetes') {
368
+ containerLabel = 'Kubernetes';
369
+ if (containerDetection.orchestrator === 'kubernetes') {
370
+ containerLabel += ' Pod';
371
+ }
372
+ }
373
+ else if (containerDetection.containerType === 'docker') {
374
+ containerLabel = 'Docker';
375
+ if (containerDetection.orchestrator === 'docker-compose') {
376
+ containerLabel += ' Compose';
377
+ }
378
+ else if (containerDetection.orchestrator === 'docker-swarm') {
379
+ containerLabel += ' Swarm';
380
+ }
381
+ }
382
+ else if (containerDetection.containerType === 'podman') {
383
+ containerLabel = 'Podman';
384
+ }
385
+ else if (containerDetection.containerType === 'lxc') {
386
+ containerLabel = 'LXC';
387
+ }
388
+ else if (containerDetection.containerType === 'systemd-nspawn') {
389
+ containerLabel = 'systemd-nspawn';
390
+ }
391
+ else {
392
+ containerLabel = 'Container';
393
+ }
394
+ platformName = `${platformName} (${containerLabel})`;
224
395
  // Add accessible paths from mounts
225
396
  if (mountPoints.length > 0) {
226
397
  examplePaths.accessible = mountPoints.map(mount => mount.containerPath);
@@ -235,9 +406,14 @@ export function getSystemInfo() {
235
406
  isMacOS,
236
407
  isLinux,
237
408
  docker: {
238
- isDocker: dockerDetected,
409
+ // New container detection fields
410
+ isContainer: containerDetection.isContainer,
411
+ containerType: containerDetection.containerType,
412
+ orchestrator: containerDetection.orchestrator,
413
+ // Backward compatibility - keep old field
414
+ isDocker: containerDetection.isContainer && containerDetection.containerType === 'docker',
239
415
  mountPoints,
240
- containerEnvironment: getContainerEnvironment()
416
+ containerEnvironment: getContainerEnvironment(containerDetection.containerType)
241
417
  },
242
418
  examplePaths
243
419
  };
@@ -248,12 +424,48 @@ export function getSystemInfo() {
248
424
  export function getOSSpecificGuidance(systemInfo) {
249
425
  const { platformName, defaultShell, isWindows, docker } = systemInfo;
250
426
  let guidance = `Running on ${platformName}. Default shell: ${defaultShell}.`;
251
- // Docker-specific guidance
252
- if (docker.isDocker) {
427
+ // Container-specific guidance
428
+ if (docker.isContainer) {
429
+ const containerTypeLabel = docker.containerType === 'kubernetes' ? 'KUBERNETES POD' :
430
+ docker.containerType === 'docker' ? 'DOCKER CONTAINER' :
431
+ docker.containerType === 'podman' ? 'PODMAN CONTAINER' :
432
+ docker.containerType === 'lxc' ? 'LXC CONTAINER' :
433
+ docker.containerType === 'systemd-nspawn' ? 'SYSTEMD-NSPAWN CONTAINER' :
434
+ 'CONTAINER';
253
435
  guidance += `
254
436
 
255
- 🐳 DOCKER ENVIRONMENT DETECTED:
437
+ 🐳 ${containerTypeLabel} ENVIRONMENT DETECTED:`;
438
+ if (docker.containerType === 'kubernetes') {
439
+ guidance += `
440
+ This Desktop Commander instance is running inside a Kubernetes pod.`;
441
+ // Add Kubernetes-specific info
442
+ if (docker.containerEnvironment?.kubernetesNamespace) {
443
+ guidance += `
444
+ Namespace: ${docker.containerEnvironment.kubernetesNamespace}`;
445
+ }
446
+ if (docker.containerEnvironment?.kubernetesPod) {
447
+ guidance += `
448
+ Pod: ${docker.containerEnvironment.kubernetesPod}`;
449
+ }
450
+ if (docker.containerEnvironment?.kubernetesNode) {
451
+ guidance += `
452
+ Node: ${docker.containerEnvironment.kubernetesNode}`;
453
+ }
454
+ }
455
+ else if (docker.containerType === 'docker') {
456
+ guidance += `
256
457
  This Desktop Commander instance is running inside a Docker container.`;
458
+ if (docker.orchestrator === 'docker-compose') {
459
+ guidance += ` (Docker Compose)`;
460
+ }
461
+ else if (docker.orchestrator === 'docker-swarm') {
462
+ guidance += ` (Docker Swarm)`;
463
+ }
464
+ }
465
+ else {
466
+ guidance += `
467
+ This Desktop Commander instance is running inside a ${docker.containerType || 'container'} environment.`;
468
+ }
257
469
  if (docker.mountPoints.length > 0) {
258
470
  guidance += `
259
471
 
@@ -314,7 +526,8 @@ MACOS-SPECIFIC NOTES:
314
526
  - Python 3 might be 'python3' command, not 'python'
315
527
  - Some GNU tools have different names (e.g., gsed instead of sed)
316
528
  - System Integrity Protection (SIP) may block certain operations
317
- - Use 'open' command to open files/applications from terminal`;
529
+ - Use 'open' command to open files/applications from terminal
530
+ - For file search: Use mdfind (Spotlight) for fastest exact filename searches`;
318
531
  }
319
532
  else {
320
533
  guidance += `
@@ -366,10 +579,14 @@ COMMON LINUX DEVELOPMENT TOOLS:
366
579
  */
367
580
  export function getPathGuidance(systemInfo) {
368
581
  let guidance = `Always use absolute paths for reliability. Paths are automatically normalized regardless of slash direction.`;
369
- if (systemInfo.docker.isDocker && systemInfo.docker.mountPoints.length > 0) {
582
+ if (systemInfo.docker.isContainer && systemInfo.docker.mountPoints.length > 0) {
583
+ const containerLabel = systemInfo.docker.containerType === 'kubernetes' ? 'KUBERNETES' :
584
+ systemInfo.docker.containerType === 'docker' ? 'DOCKER' :
585
+ systemInfo.docker.containerType === 'podman' ? 'PODMAN' :
586
+ 'CONTAINER';
370
587
  guidance += `
371
588
 
372
- 🐳 DOCKER: Prefer paths within mounted directories: ${systemInfo.docker.mountPoints.map(m => m.containerPath).join(', ')}.
589
+ 🐳 ${containerLabel}: Prefer paths within mounted directories: ${systemInfo.docker.mountPoints.map(m => m.containerPath).join(', ')}.
373
590
  When users ask about file locations, check these mounted paths first.`;
374
591
  }
375
592
  return guidance;
@@ -5,7 +5,7 @@ const TOOL_CATEGORIES = {
5
5
  filesystem: ['read_file', 'read_multiple_files', 'write_file', 'create_directory', 'list_directory', 'move_file', 'get_file_info'],
6
6
  terminal: ['execute_command', 'read_output', 'force_terminate', 'list_sessions'],
7
7
  edit: ['edit_block'],
8
- search: ['search_files', 'search_code'],
8
+ search: ['start_search', 'get_more_search_results', 'stop_search', 'list_searches'],
9
9
  config: ['get_config', 'set_config_value'],
10
10
  process: ['list_processes', 'kill_process']
11
11
  };
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.2.9";
1
+ export declare const VERSION = "0.2.11";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.2.9';
1
+ export const VERSION = '0.2.11';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wonderwhy-er/desktop-commander",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "MCP server for terminal operations and file editing",
5
5
  "license": "MIT",
6
6
  "author": "Eduards Ruzga",
@@ -21,12 +21,13 @@
21
21
  "testemonials"
22
22
  ],
23
23
  "scripts": {
24
+ "postinstall": "node dist/track-installation.js",
24
25
  "open-chat": "open -n /Applications/Claude.app",
25
26
  "sync-version": "node scripts/sync-version.js",
26
27
  "bump": "node scripts/sync-version.js --bump",
27
28
  "bump:minor": "node scripts/sync-version.js --bump --minor",
28
29
  "bump:major": "node scripts/sync-version.js --bump --major",
29
- "build": "tsc && shx cp setup-claude-server.js uninstall-claude-server.js dist/ && shx chmod +x dist/*.js",
30
+ "build": "tsc && shx cp setup-claude-server.js uninstall-claude-server.js track-installation.js dist/ && shx chmod +x dist/*.js",
30
31
  "watch": "tsc --watch",
31
32
  "start": "node dist/index.js",
32
33
  "start:debug": "node --inspect-brk=9229 dist/index.js",