starmux 0.11.0 → 0.13.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.
Files changed (136) hide show
  1. package/CONTRIBUTING.md +1 -1
  2. package/README.md +169 -97
  3. package/SECURITY.md +3 -3
  4. package/dist/src/access-control.js +23 -20
  5. package/dist/src/access-control.js.map +1 -1
  6. package/dist/src/access-dashboard.js +8 -2
  7. package/dist/src/access-dashboard.js.map +1 -1
  8. package/dist/src/cli/instances.js +87 -14
  9. package/dist/src/cli/instances.js.map +1 -1
  10. package/dist/src/cli/mac-mcp-gateway.js +4 -3
  11. package/dist/src/cli/mac-mcp-gateway.js.map +1 -1
  12. package/dist/src/cli/starmux-status.js +122 -0
  13. package/dist/src/cli/starmux-status.js.map +1 -0
  14. package/dist/src/cli/starmux.js +222 -49
  15. package/dist/src/cli/starmux.js.map +1 -1
  16. package/dist/src/deny-dashboard.js +2 -2
  17. package/dist/src/deny-dashboard.js.map +1 -1
  18. package/dist/src/instance-runtime.js +11 -2
  19. package/dist/src/instance-runtime.js.map +1 -1
  20. package/dist/src/instance-store.js +3 -1
  21. package/dist/src/instance-store.js.map +1 -1
  22. package/dist/src/ops-dashboard.js +4 -4
  23. package/dist/src/ops-dashboard.js.map +1 -1
  24. package/dist/src/permissions-dashboard.js +2 -2
  25. package/dist/src/permissions-dashboard.js.map +1 -1
  26. package/dist/src/runtime-paths.js +3 -3
  27. package/dist/src/runtime-paths.js.map +1 -1
  28. package/dist/src/starmux-migration.js +1 -1
  29. package/dist/src/starmux-migration.js.map +1 -1
  30. package/dist/vendor/desktop-commander/LICENSE +21 -0
  31. package/dist/vendor/desktop-commander/dist/bootstrap.js +22 -0
  32. package/dist/vendor/desktop-commander/dist/command-manager.js +246 -0
  33. package/dist/vendor/desktop-commander/dist/config-field-definitions.js +37 -0
  34. package/dist/vendor/desktop-commander/dist/config-manager.js +634 -0
  35. package/dist/vendor/desktop-commander/dist/config-recovery.js +81 -0
  36. package/dist/vendor/desktop-commander/dist/config.js +19 -0
  37. package/dist/vendor/desktop-commander/dist/custom-stdio.js +358 -0
  38. package/dist/vendor/desktop-commander/dist/error-handlers.js +15 -0
  39. package/dist/vendor/desktop-commander/dist/handlers/edit-search-handlers.js +6 -0
  40. package/dist/vendor/desktop-commander/dist/handlers/filesystem-handlers.js +450 -0
  41. package/dist/vendor/desktop-commander/dist/handlers/history-handlers.js +67 -0
  42. package/dist/vendor/desktop-commander/dist/handlers/index.js +7 -0
  43. package/dist/vendor/desktop-commander/dist/handlers/process-handlers.js +15 -0
  44. package/dist/vendor/desktop-commander/dist/handlers/search-answers.js +172 -0
  45. package/dist/vendor/desktop-commander/dist/handlers/search-handlers.js +136 -0
  46. package/dist/vendor/desktop-commander/dist/handlers/terminal-handlers.js +35 -0
  47. package/dist/vendor/desktop-commander/dist/index.js +88 -0
  48. package/dist/vendor/desktop-commander/dist/search-manager.js +1262 -0
  49. package/dist/vendor/desktop-commander/dist/server.js +1414 -0
  50. package/dist/vendor/desktop-commander/dist/terminal-manager.js +757 -0
  51. package/dist/vendor/desktop-commander/dist/tools/config.js +211 -0
  52. package/dist/vendor/desktop-commander/dist/tools/edit.js +455 -0
  53. package/dist/vendor/desktop-commander/dist/tools/feedback.js +10 -0
  54. package/dist/vendor/desktop-commander/dist/tools/filesystem.js +1047 -0
  55. package/dist/vendor/desktop-commander/dist/tools/fuzzySearch.js +74 -0
  56. package/dist/vendor/desktop-commander/dist/tools/fuzzySearchCore.js +125 -0
  57. package/dist/vendor/desktop-commander/dist/tools/improved-process-tools.js +726 -0
  58. package/dist/vendor/desktop-commander/dist/tools/mime-types.js +28 -0
  59. package/dist/vendor/desktop-commander/dist/tools/pdf/extract-images.js +131 -0
  60. package/dist/vendor/desktop-commander/dist/tools/pdf/index.js +4 -0
  61. package/dist/vendor/desktop-commander/dist/tools/pdf/lib/pdf2md.js +78 -0
  62. package/dist/vendor/desktop-commander/dist/tools/pdf/manipulations.js +112 -0
  63. package/dist/vendor/desktop-commander/dist/tools/pdf/markdown.js +30 -0
  64. package/dist/vendor/desktop-commander/dist/tools/pdf/utils.js +45 -0
  65. package/dist/vendor/desktop-commander/dist/tools/process.js +90 -0
  66. package/dist/vendor/desktop-commander/dist/tools/prompts.js +259 -0
  67. package/dist/vendor/desktop-commander/dist/tools/schemas.js +242 -0
  68. package/dist/vendor/desktop-commander/dist/tools/usage.js +24 -0
  69. package/dist/vendor/desktop-commander/dist/types.js +1 -0
  70. package/dist/vendor/desktop-commander/dist/ui/config-editor/config-editor-runtime.js +150 -0
  71. package/dist/vendor/desktop-commander/dist/ui/config-editor/index.html +13 -0
  72. package/dist/vendor/desktop-commander/dist/ui/config-editor/styles.css +587 -0
  73. package/dist/vendor/desktop-commander/dist/ui/contracts.js +57 -0
  74. package/dist/vendor/desktop-commander/dist/ui/file-preview/index.html +16 -0
  75. package/dist/vendor/desktop-commander/dist/ui/file-preview/preview-runtime.js +471 -0
  76. package/dist/vendor/desktop-commander/dist/ui/file-preview/shared/preview-file-types.js +59 -0
  77. package/dist/vendor/desktop-commander/dist/ui/file-preview/styles.css +1631 -0
  78. package/dist/vendor/desktop-commander/dist/ui/resources.js +93 -0
  79. package/dist/vendor/desktop-commander/dist/utils/ab-test.js +122 -0
  80. package/dist/vendor/desktop-commander/dist/utils/atomic-write.js +80 -0
  81. package/dist/vendor/desktop-commander/dist/utils/capture.js +40 -0
  82. package/dist/vendor/desktop-commander/dist/utils/dockerPrompt.js +9 -0
  83. package/dist/vendor/desktop-commander/dist/utils/exit-process.js +31 -0
  84. package/dist/vendor/desktop-commander/dist/utils/feature-flags.js +18 -0
  85. package/dist/vendor/desktop-commander/dist/utils/files/base.js +5 -0
  86. package/dist/vendor/desktop-commander/dist/utils/files/binary.js +65 -0
  87. package/dist/vendor/desktop-commander/dist/utils/files/docx.js +694 -0
  88. package/dist/vendor/desktop-commander/dist/utils/files/excel.js +497 -0
  89. package/dist/vendor/desktop-commander/dist/utils/files/factory.js +168 -0
  90. package/dist/vendor/desktop-commander/dist/utils/files/image.js +89 -0
  91. package/dist/vendor/desktop-commander/dist/utils/files/index.js +14 -0
  92. package/dist/vendor/desktop-commander/dist/utils/files/pdf.js +124 -0
  93. package/dist/vendor/desktop-commander/dist/utils/files/text.js +423 -0
  94. package/dist/vendor/desktop-commander/dist/utils/fuzzySearchLogger.js +126 -0
  95. package/dist/vendor/desktop-commander/dist/utils/internal-facts.js +23 -0
  96. package/dist/vendor/desktop-commander/dist/utils/lazy-package.js +44 -0
  97. package/dist/vendor/desktop-commander/dist/utils/lineEndingHandler.js +77 -0
  98. package/dist/vendor/desktop-commander/dist/utils/logger.js +72 -0
  99. package/dist/vendor/desktop-commander/dist/utils/mcp-ui-ab-test.js +62 -0
  100. package/dist/vendor/desktop-commander/dist/utils/open-browser.js +2 -0
  101. package/dist/vendor/desktop-commander/dist/utils/process-detection.js +260 -0
  102. package/dist/vendor/desktop-commander/dist/utils/process-tree.js +223 -0
  103. package/dist/vendor/desktop-commander/dist/utils/rename.js +74 -0
  104. package/dist/vendor/desktop-commander/dist/utils/ripgrep-resolver.js +69 -0
  105. package/dist/vendor/desktop-commander/dist/utils/shell.js +187 -0
  106. package/dist/vendor/desktop-commander/dist/utils/system-info.js +727 -0
  107. package/dist/vendor/desktop-commander/dist/utils/terminal-timestamps.js +18 -0
  108. package/dist/vendor/desktop-commander/dist/utils/toolHistory.js +309 -0
  109. package/dist/vendor/desktop-commander/dist/utils/trackTools.js +55 -0
  110. package/dist/vendor/desktop-commander/dist/utils/unsupportedParams.js +66 -0
  111. package/dist/vendor/desktop-commander/dist/utils/usageTracker.js +225 -0
  112. package/dist/vendor/desktop-commander/dist/utils/welcome-onboarding.js +2 -0
  113. package/dist/vendor/desktop-commander/dist/utils/withTimeout.js +85 -0
  114. package/dist/vendor/desktop-commander/dist/version.js +1 -0
  115. package/dist/vendor/desktop-commander/package.json +15 -0
  116. package/docs/ACCESS_CONTROL.md +5 -5
  117. package/docs/CLIENTS.md +11 -19
  118. package/docs/CONNECT_CHATGPT.md +4 -6
  119. package/docs/INSTALL.md +9 -5
  120. package/docs/INSTANCES.md +5 -2
  121. package/docs/OAUTH.md +6 -14
  122. package/docs/PLATFORMS.md +4 -4
  123. package/docs/README.md +2 -11
  124. package/docs/REMOTE_SETUP.md +13 -13
  125. package/docs/SECURITY.md +5 -5
  126. package/docs/assets/starmux-architecture.svg +315 -0
  127. package/package.json +14 -2
  128. package/docs/ARCHITECTURE.md +0 -51
  129. package/docs/MIGRATION.md +0 -37
  130. package/docs/RELEASE_STATUS.md +0 -31
  131. package/docs/TYPESCRIPT_MIGRATION.md +0 -56
  132. package/docs/VALIDATION_OAUTH.md +0 -79
  133. package/docs/VALIDATION_TUNNEL.md +0 -39
  134. package/docs/VALIDATION_WORKBUDDY.md +0 -59
  135. package/docs/assets/mcp-connection-route.svg +0 -104
  136. package/docs/assets/mcp-gateway-simple.svg +0 -62
@@ -0,0 +1,727 @@
1
+ import os from 'os';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { execFileSync } from 'child_process';
5
+ import { getDefaultShell, resolveShellPath } from './shell.js';
6
+ /**
7
+ * Detect container environment and type
8
+ */
9
+ function detectContainerEnvironment() {
10
+ // Method 1: Check environment variables first (most reliable when set)
11
+ // Docker-specific
12
+ if (process.env.MCP_CLIENT_DOCKER === 'true') {
13
+ return { isContainer: true, containerType: 'docker', orchestrator: null };
14
+ }
15
+ // Kubernetes detection
16
+ if (process.env.KUBERNETES_SERVICE_HOST || process.env.KUBERNETES_PORT) {
17
+ return { isContainer: true, containerType: 'kubernetes', orchestrator: 'kubernetes' };
18
+ }
19
+ // Podman detection
20
+ if (process.env.PODMAN_CONTAINER || process.env.CONTAINER_HOST?.includes('podman')) {
21
+ return { isContainer: true, containerType: 'podman', orchestrator: null };
22
+ }
23
+ // Method 2: Check for container indicator files
24
+ if (fs.existsSync('/.dockerenv')) {
25
+ return { isContainer: true, containerType: 'docker', orchestrator: null };
26
+ }
27
+ // Method 3: Check /proc/1/cgroup for container indicators (Linux only)
28
+ if (os.platform() === 'linux') {
29
+ try {
30
+ const cgroup = fs.readFileSync('/proc/1/cgroup', 'utf8');
31
+ // Docker detection
32
+ if (cgroup.includes('docker')) {
33
+ return { isContainer: true, containerType: 'docker', orchestrator: null };
34
+ }
35
+ // Kubernetes detection (pods run in containerd/cri-o)
36
+ if (cgroup.includes('kubepods') || cgroup.includes('pod')) {
37
+ return { isContainer: true, containerType: 'kubernetes', orchestrator: 'kubernetes' };
38
+ }
39
+ // Podman detection
40
+ if (cgroup.includes('podman') || cgroup.includes('libpod')) {
41
+ return { isContainer: true, containerType: 'podman', orchestrator: null };
42
+ }
43
+ // LXC detection
44
+ if (cgroup.includes('lxc')) {
45
+ return { isContainer: true, containerType: 'lxc', orchestrator: null };
46
+ }
47
+ // systemd-nspawn detection
48
+ if (cgroup.includes('machine.slice')) {
49
+ return { isContainer: true, containerType: 'systemd-nspawn', orchestrator: null };
50
+ }
51
+ // Generic containerd detection
52
+ if (cgroup.includes('containerd')) {
53
+ return { isContainer: true, containerType: 'other', orchestrator: null };
54
+ }
55
+ }
56
+ catch (error) {
57
+ // /proc/1/cgroup might not exist
58
+ }
59
+ }
60
+ // Method 4: Check /proc/1/environ for container indicators
61
+ if (os.platform() === 'linux') {
62
+ try {
63
+ const environ = fs.readFileSync('/proc/1/environ', 'utf8');
64
+ if (environ.includes('container=')) {
65
+ // systemd-nspawn sets container=systemd-nspawn
66
+ if (environ.includes('container=systemd-nspawn')) {
67
+ return { isContainer: true, containerType: 'systemd-nspawn', orchestrator: null };
68
+ }
69
+ // LXC sets container=lxc
70
+ if (environ.includes('container=lxc')) {
71
+ return { isContainer: true, containerType: 'lxc', orchestrator: null };
72
+ }
73
+ // Generic container detection
74
+ return { isContainer: true, containerType: 'other', orchestrator: null };
75
+ }
76
+ }
77
+ catch (error) {
78
+ // /proc/1/environ might not exist or be accessible
79
+ }
80
+ }
81
+ // Method 5: Check hostname for Kubernetes patterns
82
+ try {
83
+ const hostname = os.hostname();
84
+ // Kubernetes pods often have hostnames like: podname-deploymentid-randomid
85
+ if (hostname && (hostname.includes('-') && hostname.split('-').length >= 3)) {
86
+ // Additional check for Kubernetes service account
87
+ if (fs.existsSync('/var/run/secrets/kubernetes.io')) {
88
+ return { isContainer: true, containerType: 'kubernetes', orchestrator: 'kubernetes' };
89
+ }
90
+ }
91
+ }
92
+ catch (error) {
93
+ // Hostname check failed
94
+ }
95
+ // Method 6: Check for orchestrator-specific indicators
96
+ // Docker Compose detection
97
+ if (process.env.COMPOSE_PROJECT_NAME || process.env.COMPOSE_SERVICE) {
98
+ return { isContainer: true, containerType: 'docker', orchestrator: 'docker-compose' };
99
+ }
100
+ // Docker Swarm detection
101
+ if (process.env.DOCKER_SWARM_MODE || process.env.DOCKER_NODE_ID) {
102
+ return { isContainer: true, containerType: 'docker', orchestrator: 'docker-swarm' };
103
+ }
104
+ return { isContainer: false, containerType: null, orchestrator: null };
105
+ }
106
+ /**
107
+ * Discover container mount points
108
+ */
109
+ function discoverContainerMounts(isContainer) {
110
+ const mounts = [];
111
+ if (!isContainer) {
112
+ return mounts;
113
+ }
114
+ // Method 1: Parse /proc/mounts (Linux only)
115
+ if (os.platform() === 'linux') {
116
+ try {
117
+ const mountsContent = fs.readFileSync('/proc/mounts', 'utf8');
118
+ const mountLines = mountsContent.split('\n');
119
+ // System filesystem types that are never user mounts
120
+ const systemFsTypes = new Set([
121
+ 'overlay', 'tmpfs', 'proc', 'sysfs', 'devpts', 'cgroup', 'cgroup2',
122
+ 'mqueue', 'debugfs', 'securityfs', 'pstore', 'configfs', 'fusectl',
123
+ 'hugetlbfs', 'autofs', 'devtmpfs', 'bpf', 'tracefs', 'shm'
124
+ ]);
125
+ // Filesystem types that indicate host mounts
126
+ const hostMountFsTypes = new Set(['fakeowner', '9p', 'virtiofs', 'fuse.sshfs']);
127
+ for (const line of mountLines) {
128
+ const parts = line.split(' ');
129
+ if (parts.length >= 4) {
130
+ const device = parts[0];
131
+ const mountPoint = parts[1];
132
+ const fsType = parts[2];
133
+ const options = parts[3].split(',');
134
+ // Skip system mount points
135
+ const isSystemMountPoint = mountPoint === '/' ||
136
+ mountPoint.startsWith('/dev') ||
137
+ mountPoint.startsWith('/sys') ||
138
+ mountPoint.startsWith('/proc') ||
139
+ mountPoint.startsWith('/run') ||
140
+ mountPoint.startsWith('/sbin') ||
141
+ mountPoint === '/etc/resolv.conf' ||
142
+ mountPoint === '/etc/hostname' ||
143
+ mountPoint === '/etc/hosts';
144
+ if (isSystemMountPoint) {
145
+ continue;
146
+ }
147
+ // Detect user mounts by:
148
+ // 1. Known host-mount filesystem types (fakeowner, 9p, virtiofs)
149
+ // 2. Device from /run/host_mark/ (docker-mcp-gateway pattern)
150
+ // 3. Non-system filesystem type with user-like mount point
151
+ const isHostMountFs = hostMountFsTypes.has(fsType);
152
+ const isHostMarkDevice = device.startsWith('/run/host_mark/');
153
+ const isNonSystemFs = !systemFsTypes.has(fsType);
154
+ const isUserLikePath = mountPoint.startsWith('/mnt/') ||
155
+ mountPoint.startsWith('/workspace') ||
156
+ mountPoint.startsWith('/data/') ||
157
+ mountPoint.startsWith('/home/') ||
158
+ mountPoint.startsWith('/Users/') ||
159
+ mountPoint.startsWith('/app/') ||
160
+ mountPoint.startsWith('/project/') ||
161
+ mountPoint.startsWith('/src/') ||
162
+ mountPoint.startsWith('/code/');
163
+ if (isHostMountFs || isHostMarkDevice || (isNonSystemFs && isUserLikePath)) {
164
+ const isReadOnly = options.includes('ro');
165
+ mounts.push({
166
+ hostPath: device,
167
+ containerPath: mountPoint,
168
+ type: 'bind',
169
+ readOnly: isReadOnly,
170
+ verifiedMount: true,
171
+ description: `Mounted directory: ${path.basename(mountPoint)}`
172
+ });
173
+ }
174
+ }
175
+ }
176
+ }
177
+ catch (error) {
178
+ // /proc/mounts might not be available
179
+ }
180
+ }
181
+ // Method 2: Check /mnt directory contents
182
+ try {
183
+ if (fs.existsSync('/mnt')) {
184
+ const contents = fs.readdirSync('/mnt');
185
+ for (const item of contents) {
186
+ const itemPath = `/mnt/${item}`;
187
+ try {
188
+ const stats = fs.statSync(itemPath);
189
+ if (stats.isDirectory()) {
190
+ // Check if we already have this mount
191
+ const exists = mounts.some(m => m.containerPath === itemPath);
192
+ if (!exists) {
193
+ mounts.push({
194
+ hostPath: `<host>/${item}`,
195
+ containerPath: itemPath,
196
+ type: 'bind',
197
+ readOnly: false,
198
+ verifiedMount: false,
199
+ description: `Candidate folder under /mnt: ${item}`
200
+ });
201
+ }
202
+ }
203
+ }
204
+ catch (itemError) {
205
+ // Skip items we can't stat
206
+ }
207
+ }
208
+ }
209
+ }
210
+ catch (error) {
211
+ // /mnt directory doesn't exist or not accessible
212
+ }
213
+ // Method 3: Check /home directory for user-mounted folders (Desktop Commander Docker installer pattern)
214
+ try {
215
+ if (fs.existsSync('/home')) {
216
+ const contents = fs.readdirSync('/home');
217
+ for (const item of contents) {
218
+ // Skip the root user directory and common system directories
219
+ if (item === 'root' || item === 'node' || item === 'bin' || item === 'sbin' ||
220
+ item === 'usr' || item === 'lib' || item === 'lib64' || item === 'var' ||
221
+ item === 'tmp' || item === 'opt' || item === 'sys' || item === 'proc') {
222
+ continue;
223
+ }
224
+ const itemPath = `/home/${item}`;
225
+ try {
226
+ const stats = fs.statSync(itemPath);
227
+ if (stats.isDirectory()) {
228
+ // Check if we already have this mount
229
+ const exists = mounts.some(m => m.containerPath === itemPath);
230
+ if (!exists) {
231
+ mounts.push({
232
+ hostPath: `<host>/${item}`,
233
+ containerPath: itemPath,
234
+ type: 'bind',
235
+ readOnly: false,
236
+ verifiedMount: false,
237
+ description: `Candidate folder under /home: ${item}`
238
+ });
239
+ }
240
+ }
241
+ }
242
+ catch (itemError) {
243
+ // Skip items we can't stat
244
+ }
245
+ }
246
+ }
247
+ }
248
+ catch (error) {
249
+ // /home directory doesn't exist or not accessible
250
+ }
251
+ return mounts;
252
+ }
253
+ /**
254
+ * Get container environment information
255
+ */
256
+ function getContainerEnvironment(containerType) {
257
+ const env = {};
258
+ // Try to get container name from hostname (often set to container ID/name)
259
+ try {
260
+ const hostname = os.hostname();
261
+ if (hostname && hostname !== 'localhost') {
262
+ env.containerName = hostname;
263
+ }
264
+ }
265
+ catch (error) {
266
+ // Hostname not available
267
+ }
268
+ // Docker-specific environment
269
+ if (containerType === 'docker') {
270
+ // Try multiple sources for Docker image name
271
+ if (process.env.DOCKER_IMAGE) {
272
+ env.dockerImage = process.env.DOCKER_IMAGE;
273
+ }
274
+ else if (process.env.IMAGE_NAME) {
275
+ env.dockerImage = process.env.IMAGE_NAME;
276
+ }
277
+ else if (process.env.CONTAINER_IMAGE) {
278
+ env.dockerImage = process.env.CONTAINER_IMAGE;
279
+ }
280
+ // Try to read from Docker labels if available (less common but possible)
281
+ try {
282
+ if (fs.existsSync('/proc/self/cgroup')) {
283
+ const cgroup = fs.readFileSync('/proc/self/cgroup', 'utf8');
284
+ // Extract container ID from cgroup path
285
+ const containerIdMatch = cgroup.match(/docker\/([a-f0-9]{64})/);
286
+ if (containerIdMatch && !env.containerName) {
287
+ // Use short container ID as fallback name
288
+ env.containerName = containerIdMatch[1].substring(0, 12);
289
+ }
290
+ }
291
+ }
292
+ catch (error) {
293
+ // Ignore errors reading cgroup
294
+ }
295
+ }
296
+ // Kubernetes-specific environment
297
+ if (containerType === 'kubernetes') {
298
+ if (process.env.KUBERNETES_NAMESPACE || process.env.POD_NAMESPACE) {
299
+ env.kubernetesNamespace = process.env.KUBERNETES_NAMESPACE || process.env.POD_NAMESPACE;
300
+ }
301
+ if (process.env.POD_NAME || process.env.HOSTNAME) {
302
+ env.kubernetesPod = process.env.POD_NAME || process.env.HOSTNAME;
303
+ }
304
+ if (process.env.NODE_NAME || process.env.KUBERNETES_NODE_NAME) {
305
+ env.kubernetesNode = process.env.NODE_NAME || process.env.KUBERNETES_NODE_NAME;
306
+ }
307
+ // Try to get container image from common Kubernetes environment variables
308
+ if (process.env.CONTAINER_IMAGE) {
309
+ env.dockerImage = process.env.CONTAINER_IMAGE;
310
+ }
311
+ else if (process.env.IMAGE_NAME) {
312
+ env.dockerImage = process.env.IMAGE_NAME;
313
+ }
314
+ // Try to read Kubernetes service account info
315
+ try {
316
+ if (fs.existsSync('/var/run/secrets/kubernetes.io/serviceaccount/namespace')) {
317
+ const namespace = fs.readFileSync('/var/run/secrets/kubernetes.io/serviceaccount/namespace', 'utf8').trim();
318
+ if (namespace && !env.kubernetesNamespace) {
319
+ env.kubernetesNamespace = namespace;
320
+ }
321
+ }
322
+ }
323
+ catch (error) {
324
+ // Service account info not available
325
+ }
326
+ }
327
+ // Podman-specific environment
328
+ if (containerType === 'podman') {
329
+ // Podman uses similar environment variables to Docker
330
+ if (process.env.CONTAINER_IMAGE || process.env.PODMAN_IMAGE) {
331
+ env.dockerImage = process.env.CONTAINER_IMAGE || process.env.PODMAN_IMAGE;
332
+ }
333
+ }
334
+ // LXC-specific environment
335
+ if (containerType === 'lxc') {
336
+ // LXC containers might have different naming conventions
337
+ if (process.env.LXC_NAME) {
338
+ env.containerName = process.env.LXC_NAME;
339
+ }
340
+ }
341
+ // Try to detect host platform
342
+ if (process.env.HOST_PLATFORM) {
343
+ env.hostPlatform = process.env.HOST_PLATFORM;
344
+ }
345
+ return Object.keys(env).length > 0 ? env : undefined;
346
+ }
347
+ /**
348
+ * Detect Node.js installation and version from current process
349
+ */
350
+ function detectNodeInfo() {
351
+ try {
352
+ // Get Node.js version from current process
353
+ const version = process.version.replace('v', ''); // Remove 'v' prefix
354
+ // Get Node.js executable path from current process
355
+ const path = process.execPath;
356
+ // Get npm version from environment if available
357
+ const npmVersion = process.env.npm_version;
358
+ return {
359
+ version,
360
+ path,
361
+ ...(npmVersion && { npmVersion })
362
+ };
363
+ }
364
+ catch (error) {
365
+ return undefined;
366
+ }
367
+ }
368
+ /**
369
+ * Detect Python installation and version and put on systeminfo.pythonInfo
370
+ */
371
+ function detectPythonInfo() {
372
+ // Try python commands in order of preference
373
+ const pythonCommands = process.platform === 'win32'
374
+ ? ['python', 'python3', 'py'] // Windows: 'python' is common, 'py' launcher
375
+ : ['python3', 'python']; // Unix: prefer python3
376
+ for (const cmd of pythonCommands) {
377
+ // The python on PATH, never one in the working folder (which Windows
378
+ // searches first for a bare name); not on PATH means not available
379
+ const program = resolveShellPath(cmd);
380
+ if (!program)
381
+ continue;
382
+ try {
383
+ const version = execFileSync(program, ['--version'], {
384
+ encoding: 'utf8',
385
+ timeout: 5000,
386
+ stdio: ['pipe', 'pipe', 'pipe']
387
+ }).trim();
388
+ // Verify it's Python 3.x
389
+ if (version.includes('Python 3')) {
390
+ return {
391
+ available: true,
392
+ command: cmd,
393
+ version: version.replace('Python ', '')
394
+ };
395
+ }
396
+ }
397
+ catch {
398
+ // Command not found or failed, try next
399
+ }
400
+ }
401
+ return { available: false, command: '' };
402
+ }
403
+ /**
404
+ * Get comprehensive system information for tool prompts
405
+ */
406
+ export function getSystemInfo() {
407
+ const platform = os.platform();
408
+ const isWindows = platform === 'win32';
409
+ const isMacOS = platform === 'darwin';
410
+ const isLinux = platform === 'linux';
411
+ // Container detection
412
+ const containerDetection = detectContainerEnvironment();
413
+ const mountPoints = containerDetection.isContainer ? discoverContainerMounts(containerDetection.isContainer) : [];
414
+ let platformName;
415
+ const defaultShell = getDefaultShell();
416
+ let pathSeparator;
417
+ let examplePaths;
418
+ if (isWindows) {
419
+ platformName = 'Windows';
420
+ pathSeparator = '\\';
421
+ examplePaths = {
422
+ home: 'C:\\Users\\username',
423
+ temp: 'C:\\Temp',
424
+ absolute: 'C:\\path\\to\\file.txt'
425
+ };
426
+ }
427
+ else if (isMacOS) {
428
+ platformName = 'macOS';
429
+ pathSeparator = '/';
430
+ examplePaths = {
431
+ home: '/Users/username',
432
+ temp: '/tmp',
433
+ absolute: '/path/to/file.txt'
434
+ };
435
+ }
436
+ else if (isLinux) {
437
+ platformName = 'Linux';
438
+ pathSeparator = '/';
439
+ examplePaths = {
440
+ home: '/home/username',
441
+ temp: '/tmp',
442
+ absolute: '/path/to/file.txt'
443
+ };
444
+ }
445
+ else {
446
+ // Fallback for other Unix-like systems
447
+ platformName = 'Unix';
448
+ pathSeparator = '/';
449
+ examplePaths = {
450
+ home: '/home/username',
451
+ temp: '/tmp',
452
+ absolute: '/path/to/file.txt'
453
+ };
454
+ }
455
+ // Adjust platform name for containers
456
+ if (containerDetection.isContainer) {
457
+ let containerLabel = '';
458
+ if (containerDetection.containerType === 'kubernetes') {
459
+ containerLabel = 'Kubernetes';
460
+ if (containerDetection.orchestrator === 'kubernetes') {
461
+ containerLabel += ' Pod';
462
+ }
463
+ }
464
+ else if (containerDetection.containerType === 'docker') {
465
+ containerLabel = 'Docker';
466
+ if (containerDetection.orchestrator === 'docker-compose') {
467
+ containerLabel += ' Compose';
468
+ }
469
+ else if (containerDetection.orchestrator === 'docker-swarm') {
470
+ containerLabel += ' Swarm';
471
+ }
472
+ }
473
+ else if (containerDetection.containerType === 'podman') {
474
+ containerLabel = 'Podman';
475
+ }
476
+ else if (containerDetection.containerType === 'lxc') {
477
+ containerLabel = 'LXC';
478
+ }
479
+ else if (containerDetection.containerType === 'systemd-nspawn') {
480
+ containerLabel = 'systemd-nspawn';
481
+ }
482
+ else {
483
+ containerLabel = 'Container';
484
+ }
485
+ platformName = `${platformName} (${containerLabel})`;
486
+ // Add accessible paths from mounts
487
+ if (mountPoints.length > 0) {
488
+ examplePaths.accessible = mountPoints.map(mount => mount.containerPath);
489
+ }
490
+ }
491
+ // Detect Node.js installation from current process
492
+ const nodeInfo = detectNodeInfo();
493
+ // Detect Python installation
494
+ const pythonInfo = detectPythonInfo();
495
+ // Get process information
496
+ const processInfo = {
497
+ pid: process.pid,
498
+ arch: process.arch,
499
+ platform: process.platform,
500
+ versions: process.versions
501
+ };
502
+ return {
503
+ platform,
504
+ platformName,
505
+ defaultShell,
506
+ pathSeparator,
507
+ isWindows,
508
+ isMacOS,
509
+ isLinux,
510
+ docker: {
511
+ // New container detection fields
512
+ isContainer: containerDetection.isContainer,
513
+ containerType: containerDetection.containerType,
514
+ orchestrator: containerDetection.orchestrator,
515
+ // Backward compatibility - keep old field
516
+ isDocker: containerDetection.isContainer && containerDetection.containerType === 'docker',
517
+ mountPoints,
518
+ containerEnvironment: getContainerEnvironment(containerDetection.containerType)
519
+ },
520
+ isDXT: !!process.env.MCP_DXT,
521
+ nodeInfo,
522
+ pythonInfo,
523
+ processInfo,
524
+ examplePaths
525
+ };
526
+ }
527
+ /**
528
+ * Generate OS-specific guidance for tool prompts
529
+ */
530
+ export function getOSSpecificGuidance(systemInfo) {
531
+ const { platformName, defaultShell, isWindows, docker } = systemInfo;
532
+ let guidance = `Running on ${platformName}. Default shell: ${defaultShell}.`;
533
+ // Container-specific guidance
534
+ if (docker.isContainer) {
535
+ const containerTypeLabel = docker.containerType === 'kubernetes' ? 'KUBERNETES POD' :
536
+ docker.containerType === 'docker' ? 'DOCKER CONTAINER' :
537
+ docker.containerType === 'podman' ? 'PODMAN CONTAINER' :
538
+ docker.containerType === 'lxc' ? 'LXC CONTAINER' :
539
+ docker.containerType === 'systemd-nspawn' ? 'SYSTEMD-NSPAWN CONTAINER' :
540
+ 'CONTAINER';
541
+ guidance += `
542
+
543
+ 🐳 ${containerTypeLabel} ENVIRONMENT DETECTED:`;
544
+ if (docker.containerType === 'kubernetes') {
545
+ guidance += `
546
+ This Desktop Commander instance is running inside a Kubernetes pod.`;
547
+ // Add Kubernetes-specific info
548
+ if (docker.containerEnvironment?.kubernetesNamespace) {
549
+ guidance += `
550
+ Namespace: ${docker.containerEnvironment.kubernetesNamespace}`;
551
+ }
552
+ if (docker.containerEnvironment?.kubernetesPod) {
553
+ guidance += `
554
+ Pod: ${docker.containerEnvironment.kubernetesPod}`;
555
+ }
556
+ if (docker.containerEnvironment?.kubernetesNode) {
557
+ guidance += `
558
+ Node: ${docker.containerEnvironment.kubernetesNode}`;
559
+ }
560
+ }
561
+ else if (docker.containerType === 'docker') {
562
+ guidance += `
563
+ This Desktop Commander instance is running inside a Docker container.`;
564
+ if (docker.orchestrator === 'docker-compose') {
565
+ guidance += ` (Docker Compose)`;
566
+ }
567
+ else if (docker.orchestrator === 'docker-swarm') {
568
+ guidance += ` (Docker Swarm)`;
569
+ }
570
+ }
571
+ else {
572
+ guidance += `
573
+ This Desktop Commander instance is running inside a ${docker.containerType || 'container'} environment.`;
574
+ }
575
+ if (docker.mountPoints.length > 0) {
576
+ guidance += `
577
+
578
+ AVAILABLE MOUNT LOCATIONS:`;
579
+ for (const mount of docker.mountPoints) {
580
+ const access = mount.readOnly ? '(read-only)' : '(read-write)';
581
+ const verification = mount.verifiedMount ? '[verified mount]' : '[unverified candidate]';
582
+ guidance += `
583
+ - ${mount.containerPath} ${access} ${verification} - ${mount.description}`;
584
+ }
585
+ guidance += `
586
+
587
+ Entries marked [verified mount] are suitable for persistent file work.
588
+ Entries marked [unverified candidate] are inferred from common container paths and may not persist.
589
+ Files outside verified mounts may be lost when the container stops.
590
+
591
+ PATH TRANSLATION IN DOCKER:
592
+ Host paths may require translation to their corresponding container paths:
593
+
594
+ Windows: "C:\\projects\\data\\file.txt" → "/home/projects/data/file.txt"
595
+ Linux/Mac: "/Users/john/projects/data/file.txt" → "/home/projects/data/file.txt"
596
+
597
+ Typical translation removes the drive letter or user prefix, keeps the full folder structure, and maps it under /home/.
598
+
599
+ NOTE: Desktop Commander Docker installer mounts host folders to /home/[folder-name].`;
600
+ }
601
+ else {
602
+ guidance += `
603
+
604
+ ⚠️ WARNING: No mounted directories detected.
605
+ Files created outside mounted volumes will be lost when the container stops.
606
+ Persistent file work requires a mounted directory, available through the Docker installer or a -v mount.
607
+ Desktop Commander Docker installer typically mounts folders to /home/[folder-name].`;
608
+ }
609
+ if (docker.containerEnvironment?.containerName) {
610
+ guidance += `
611
+ Container: ${docker.containerEnvironment.containerName}`;
612
+ }
613
+ }
614
+ if (isWindows) {
615
+ guidance += `
616
+
617
+ WINDOWS-SPECIFIC TROUBLESHOOTING:
618
+ - If Node.js/Python commands fail with "not recognized" errors:
619
+ * Alternative shells include "cmd" and "powershell.exe"
620
+ * PowerShell may have execution policy restrictions for some tools
621
+ * CMD typically has better compatibility with development tools
622
+ * defaultShell can be changed with set_config_value if needed
623
+ - Windows services and processes use different commands (Get-Process vs ps)
624
+ - Package managers: choco, winget, scoop instead of apt/brew
625
+ - Environment variables: $env:VAR instead of $VAR
626
+ - File permissions work differently than Unix systems`;
627
+ }
628
+ else if (systemInfo.isMacOS) {
629
+ guidance += `
630
+
631
+ MACOS-SPECIFIC NOTES:
632
+ - Package manager: brew (Homebrew) is commonly used
633
+ - Python 3 might be 'python3' command, not 'python'
634
+ - Some GNU tools have different names (e.g., gsed instead of sed)
635
+ - System Integrity Protection (SIP) may block certain operations
636
+ - The 'open' command opens files and applications from the terminal
637
+ - mdfind (Spotlight) provides fast exact filename searches`;
638
+ }
639
+ else {
640
+ guidance += `
641
+
642
+ LINUX-SPECIFIC NOTES:
643
+ - Package managers vary by distro: apt, yum, dnf, pacman, zypper
644
+ - Python 3 might be 'python3' command, not 'python'
645
+ - Standard Unix shell tools available (grep, awk, sed, etc.)
646
+ - File permissions and ownership important for many operations
647
+ - Systemd services common on modern distributions`;
648
+ }
649
+ return guidance;
650
+ }
651
+ /**
652
+ * Get common development tool guidance based on OS
653
+ */
654
+ export function getDevelopmentToolGuidance(systemInfo) {
655
+ const { isWindows, isMacOS, isLinux, platformName, nodeInfo, processInfo } = systemInfo;
656
+ // Add detected Node.js info to guidance
657
+ const nodeGuidance = nodeInfo
658
+ ? `Node.js: v${nodeInfo.version} (${nodeInfo.path})${nodeInfo.npmVersion ? ` | npm: v${nodeInfo.npmVersion}` : ''}`
659
+ : 'Node.js: Not detected';
660
+ // Add process environment info
661
+ const envInfo = `
662
+ Current Process Environment:
663
+ - Node: v${processInfo.versions.node}
664
+ - V8: v${processInfo.versions.v8}
665
+ - Architecture: ${processInfo.arch}
666
+ - Platform: ${processInfo.platform}
667
+ - Process ID: ${processInfo.pid}`;
668
+ if (isWindows) {
669
+ return `
670
+ COMMON WINDOWS DEVELOPMENT TOOLS:
671
+ - ${nodeGuidance}
672
+ - Python: May be 'python' or 'py' command, check both
673
+ - Git: Git Bash provides Unix-like environment
674
+ - WSL: Windows Subsystem for Linux available for Unix tools
675
+ - Visual Studio tools: cl, msbuild for C++ compilation
676
+
677
+ ${envInfo}`;
678
+ }
679
+ else if (isMacOS) {
680
+ return `
681
+ COMMON MACOS DEVELOPMENT TOOLS:
682
+ - Xcode Command Line Tools: Required for many development tools
683
+ - Homebrew: Primary package manager for development tools
684
+ - ${nodeGuidance}
685
+ - Python: Usually python3, check if python points to Python 2
686
+ - Ruby: System Ruby available, rbenv/rvm for version management
687
+
688
+ ${envInfo}`;
689
+ }
690
+ else {
691
+ return `
692
+ COMMON LINUX DEVELOPMENT TOOLS:
693
+ - Package managers: Install tools via distribution package manager
694
+ - Python: Usually python3, python may point to Python 2
695
+ - ${nodeGuidance}
696
+ - Build tools: gcc, make typically available or easily installed
697
+ - Container tools: docker, podman common for development
698
+
699
+ ${envInfo}`;
700
+ }
701
+ }
702
+ /**
703
+ * Get path guidance (simplified since paths are normalized)
704
+ */
705
+ export function getPathGuidance(systemInfo) {
706
+ let guidance = `Absolute paths are the most reliable because they do not depend on the current working directory. Paths are automatically normalized regardless of slash direction.`;
707
+ if (systemInfo.docker.isContainer && systemInfo.docker.mountPoints.length > 0) {
708
+ const containerLabel = systemInfo.docker.containerType === 'kubernetes' ? 'KUBERNETES' :
709
+ systemInfo.docker.containerType === 'docker' ? 'DOCKER' :
710
+ systemInfo.docker.containerType === 'podman' ? 'PODMAN' :
711
+ 'CONTAINER';
712
+ const verifiedMounts = systemInfo.docker.mountPoints.filter(m => m.verifiedMount).map(m => m.containerPath);
713
+ const unverifiedCandidates = systemInfo.docker.mountPoints.filter(m => !m.verifiedMount).map(m => m.containerPath);
714
+ if (verifiedMounts.length > 0) {
715
+ guidance += `
716
+
717
+ 🐳 ${containerLabel}: Verified mounts for persistent file operations: ${verifiedMounts.join(', ')}.
718
+ Files outside verified mounts may not persist after the container stops.`;
719
+ }
720
+ if (unverifiedCandidates.length > 0) {
721
+ guidance += `
722
+
723
+ 🐳 ${containerLabel}: Unverified mount candidates: ${unverifiedCandidates.join(', ')}. These paths are inferred from common container directories and may not persist.`;
724
+ }
725
+ }
726
+ return guidance;
727
+ }