@wonderwhy-er/desktop-commander 0.2.10 → 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.
@@ -768,6 +768,63 @@ export async function moveFile(sourcePath, destinationPath) {
768
768
  await fs.rename(validSourcePath, validDestPath);
769
769
  }
770
770
  export async function searchFiles(rootPath, pattern) {
771
+ // Use the new search manager for better performance
772
+ // This provides a temporary compatibility layer until we fully migrate to search sessions
773
+ const { searchManager } = await import('../search-manager.js');
774
+ try {
775
+ const result = await searchManager.startSearch({
776
+ rootPath,
777
+ pattern,
778
+ searchType: 'files',
779
+ ignoreCase: true,
780
+ maxResults: 5000, // Higher limit for compatibility
781
+ earlyTermination: true, // Use early termination for better performance
782
+ });
783
+ const sessionId = result.sessionId;
784
+ // Poll for results until complete
785
+ let allResults = [];
786
+ let isComplete = result.isComplete;
787
+ let startTime = Date.now();
788
+ // Add initial results
789
+ for (const searchResult of result.results) {
790
+ if (searchResult.type === 'file') {
791
+ allResults.push(searchResult.file);
792
+ }
793
+ }
794
+ while (!isComplete) {
795
+ await new Promise(resolve => setTimeout(resolve, 100)); // Wait 100ms
796
+ const results = searchManager.readSearchResults(sessionId);
797
+ isComplete = results.isComplete;
798
+ // Add new file paths to results
799
+ for (const searchResult of results.results) {
800
+ if (searchResult.file !== '__LAST_READ_MARKER__' && searchResult.type === 'file') {
801
+ allResults.push(searchResult.file);
802
+ }
803
+ }
804
+ // Safety check to prevent infinite loops (30 second timeout)
805
+ if (Date.now() - startTime > 30000) {
806
+ searchManager.terminateSearch(sessionId);
807
+ break;
808
+ }
809
+ }
810
+ // Log only the count of found files, not their paths
811
+ capture('server_search_files_complete', {
812
+ resultsCount: allResults.length,
813
+ patternLength: pattern.length,
814
+ usedRipgrep: true
815
+ });
816
+ return allResults;
817
+ }
818
+ catch (error) {
819
+ // Fallback to original Node.js implementation if ripgrep fails
820
+ capture('server_search_files_ripgrep_fallback', {
821
+ error: error instanceof Error ? error.message : 'Unknown error'
822
+ });
823
+ return await searchFilesNodeJS(rootPath, pattern);
824
+ }
825
+ }
826
+ // Keep the original Node.js implementation as fallback
827
+ async function searchFilesNodeJS(rootPath, pattern) {
771
828
  const results = [];
772
829
  async function search(currentPath) {
773
830
  let entries;
@@ -800,7 +857,8 @@ export async function searchFiles(rootPath, pattern) {
800
857
  // Log only the count of found files, not their paths
801
858
  capture('server_search_files_complete', {
802
859
  resultsCount: results.length,
803
- patternLength: pattern.length
860
+ patternLength: pattern.length,
861
+ usedRipgrep: false
804
862
  });
805
863
  return results;
806
864
  }
@@ -109,19 +109,6 @@ export declare const MoveFileArgsSchema: z.ZodObject<{
109
109
  source: string;
110
110
  destination: string;
111
111
  }>;
112
- export declare const SearchFilesArgsSchema: z.ZodObject<{
113
- path: z.ZodString;
114
- pattern: z.ZodString;
115
- timeoutMs: z.ZodOptional<z.ZodNumber>;
116
- }, "strip", z.ZodTypeAny, {
117
- path: string;
118
- pattern: string;
119
- timeoutMs?: number | undefined;
120
- }, {
121
- path: string;
122
- pattern: string;
123
- timeoutMs?: number | undefined;
124
- }>;
125
112
  export declare const GetFileInfoArgsSchema: z.ZodObject<{
126
113
  path: z.ZodString;
127
114
  }, "strip", z.ZodTypeAny, {
@@ -129,34 +116,6 @@ export declare const GetFileInfoArgsSchema: z.ZodObject<{
129
116
  }, {
130
117
  path: string;
131
118
  }>;
132
- export declare const SearchCodeArgsSchema: z.ZodObject<{
133
- path: z.ZodString;
134
- pattern: z.ZodString;
135
- filePattern: z.ZodOptional<z.ZodString>;
136
- ignoreCase: z.ZodOptional<z.ZodBoolean>;
137
- maxResults: z.ZodOptional<z.ZodNumber>;
138
- includeHidden: z.ZodOptional<z.ZodBoolean>;
139
- contextLines: z.ZodOptional<z.ZodNumber>;
140
- timeoutMs: z.ZodOptional<z.ZodNumber>;
141
- }, "strip", z.ZodTypeAny, {
142
- path: string;
143
- pattern: string;
144
- timeoutMs?: number | undefined;
145
- filePattern?: string | undefined;
146
- ignoreCase?: boolean | undefined;
147
- maxResults?: number | undefined;
148
- includeHidden?: boolean | undefined;
149
- contextLines?: number | undefined;
150
- }, {
151
- path: string;
152
- pattern: string;
153
- timeoutMs?: number | undefined;
154
- filePattern?: string | undefined;
155
- ignoreCase?: boolean | undefined;
156
- maxResults?: number | undefined;
157
- includeHidden?: boolean | undefined;
158
- contextLines?: number | undefined;
159
- }>;
160
119
  export declare const EditBlockArgsSchema: z.ZodObject<{
161
120
  file_path: z.ZodString;
162
121
  old_string: z.ZodString;
@@ -191,3 +150,58 @@ export declare const InteractWithProcessArgsSchema: z.ZodObject<{
191
150
  }>;
192
151
  export declare const GetUsageStatsArgsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
193
152
  export declare const GiveFeedbackArgsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
153
+ export declare const StartSearchArgsSchema: z.ZodObject<{
154
+ path: z.ZodString;
155
+ pattern: z.ZodString;
156
+ searchType: z.ZodDefault<z.ZodEnum<["files", "content"]>>;
157
+ filePattern: z.ZodOptional<z.ZodString>;
158
+ ignoreCase: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
159
+ maxResults: z.ZodOptional<z.ZodNumber>;
160
+ includeHidden: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
161
+ contextLines: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
162
+ timeout_ms: z.ZodOptional<z.ZodNumber>;
163
+ earlyTermination: z.ZodOptional<z.ZodBoolean>;
164
+ }, "strip", z.ZodTypeAny, {
165
+ path: string;
166
+ pattern: string;
167
+ searchType: "content" | "files";
168
+ ignoreCase: boolean;
169
+ includeHidden: boolean;
170
+ contextLines: number;
171
+ timeout_ms?: number | undefined;
172
+ filePattern?: string | undefined;
173
+ maxResults?: number | undefined;
174
+ earlyTermination?: boolean | undefined;
175
+ }, {
176
+ path: string;
177
+ pattern: string;
178
+ timeout_ms?: number | undefined;
179
+ searchType?: "content" | "files" | undefined;
180
+ filePattern?: string | undefined;
181
+ ignoreCase?: boolean | undefined;
182
+ maxResults?: number | undefined;
183
+ includeHidden?: boolean | undefined;
184
+ contextLines?: number | undefined;
185
+ earlyTermination?: boolean | undefined;
186
+ }>;
187
+ export declare const GetMoreSearchResultsArgsSchema: z.ZodObject<{
188
+ sessionId: z.ZodString;
189
+ offset: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
190
+ length: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
191
+ }, "strip", z.ZodTypeAny, {
192
+ length: number;
193
+ offset: number;
194
+ sessionId: string;
195
+ }, {
196
+ sessionId: string;
197
+ length?: number | undefined;
198
+ offset?: number | undefined;
199
+ }>;
200
+ export declare const StopSearchArgsSchema: z.ZodObject<{
201
+ sessionId: z.ZodString;
202
+ }, "strip", z.ZodTypeAny, {
203
+ sessionId: string;
204
+ }, {
205
+ sessionId: string;
206
+ }>;
207
+ export declare const ListSearchesArgsSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
@@ -49,25 +49,9 @@ export const MoveFileArgsSchema = z.object({
49
49
  source: z.string(),
50
50
  destination: z.string(),
51
51
  });
52
- export const SearchFilesArgsSchema = z.object({
53
- path: z.string(),
54
- pattern: z.string(),
55
- timeoutMs: z.number().optional(),
56
- });
57
52
  export const GetFileInfoArgsSchema = z.object({
58
53
  path: z.string(),
59
54
  });
60
- // Search tools schema
61
- export const SearchCodeArgsSchema = z.object({
62
- path: z.string(),
63
- pattern: z.string(),
64
- filePattern: z.string().optional(),
65
- ignoreCase: z.boolean().optional(),
66
- maxResults: z.number().optional(),
67
- includeHidden: z.boolean().optional(),
68
- contextLines: z.number().optional(),
69
- timeoutMs: z.number().optional(),
70
- });
71
55
  // Edit tools schema
72
56
  export const EditBlockArgsSchema = z.object({
73
57
  file_path: z.string(),
@@ -93,3 +77,25 @@ export const GiveFeedbackArgsSchema = z.object({
93
77
  // - platform (auto)
94
78
  // - client_id (auto)
95
79
  });
80
+ // Search schemas (renamed for natural language)
81
+ export const StartSearchArgsSchema = z.object({
82
+ path: z.string(),
83
+ pattern: z.string(),
84
+ searchType: z.enum(['files', 'content']).default('files'),
85
+ filePattern: z.string().optional(),
86
+ ignoreCase: z.boolean().optional().default(true),
87
+ maxResults: z.number().optional(),
88
+ includeHidden: z.boolean().optional().default(false),
89
+ contextLines: z.number().optional().default(5),
90
+ timeout_ms: z.number().optional(), // Match process naming convention
91
+ earlyTermination: z.boolean().optional(), // Stop search early when exact filename match is found (default: true for files, false for content)
92
+ });
93
+ export const GetMoreSearchResultsArgsSchema = z.object({
94
+ sessionId: z.string(),
95
+ offset: z.number().optional().default(0), // Same as file reading
96
+ length: z.number().optional().default(100), // Same as file reading (but smaller default)
97
+ });
98
+ export const StopSearchArgsSchema = z.object({
99
+ sessionId: z.string(),
100
+ });
101
+ export const ListSearchesArgsSchema = z.object({});
@@ -27,7 +27,15 @@ export async function searchCode(options) {
27
27
  args.push('-C', contextLines.toString());
28
28
  }
29
29
  if (filePattern) {
30
- args.push('-g', filePattern);
30
+ const patterns = filePattern
31
+ .split('|')
32
+ .map(p => p.trim()) // remove surrounding spaces
33
+ .filter(Boolean); // drop empty tokens
34
+ // If all patterns were empty, return no results
35
+ if (patterns.length === 0) {
36
+ return [];
37
+ }
38
+ patterns.forEach(p => args.push('-g', p));
31
39
  }
32
40
  // Add pattern and path
33
41
  args.push(pattern, validPath);
@@ -97,7 +105,21 @@ export async function searchCodeFallback(options) {
97
105
  const validPath = await validatePath(rootPath);
98
106
  const results = [];
99
107
  const regex = new RegExp(pattern, ignoreCase ? 'i' : '');
100
- const fileRegex = filePattern ? new RegExp(filePattern) : null;
108
+ // Handle filePattern similarly to main implementation
109
+ let fileRegex = null;
110
+ if (filePattern) {
111
+ const patterns = filePattern
112
+ .split('|')
113
+ .map(p => p.trim())
114
+ .filter(Boolean);
115
+ // If all patterns were empty, return no results
116
+ if (patterns.length === 0) {
117
+ return [];
118
+ }
119
+ // Create a regex that matches any of the patterns
120
+ const combinedPattern = patterns.map(p => p.replace(/\./g, '\\.').replace(/\*/g, '.*').replace(/\?/g, '.')).join('|');
121
+ fileRegex = new RegExp(`^(${combinedPattern})$`);
122
+ }
101
123
  async function searchDir(dirPath) {
102
124
  if (results.length >= maxResults)
103
125
  return;
@@ -163,12 +185,18 @@ export async function searchCodeFallback(options) {
163
185
  // Main function that tries ripgrep first, falls back to native implementation
164
186
  export async function searchTextInFiles(options) {
165
187
  try {
188
+ // For better performance and consistency, prefer the search session manager
189
+ // when doing content search, but keep the original direct ripgrep approach as primary
166
190
  return await searchCode(options);
167
191
  }
168
192
  catch (error) {
193
+ capture('searchTextInFiles_ripgrep_fallback', {
194
+ error: error instanceof Error ? error.message : 'Unknown error'
195
+ });
196
+ // Use consistent exclusions - remove 'dist' to match ripgrep behavior
169
197
  return searchCodeFallback({
170
198
  ...options,
171
- excludeDirs: ['node_modules', '.git', 'dist']
199
+ excludeDirs: ['node_modules', '.git']
172
200
  });
173
201
  }
174
202
  }
@@ -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 Docker container
127
- let isDocker = 'false';
128
- if (process.env.MCP_CLIENT_DOCKER) {
129
- isDocker = 'true';
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
- isDocker,
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
- if (res.statusCode !== 200 && res.statusCode !== 204) {
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 DockerInfo {
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: DockerInfo;
31
+ docker: ContainerInfo;
26
32
  examplePaths: {
27
33
  home: string;
28
34
  temp: string;