@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.
- 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 +43 -24
- package/dist/search-manager.d.ts +107 -0
- package/dist/search-manager.js +467 -0
- package/dist/server.js +99 -31
- 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/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
package/dist/tools/filesystem.js
CHANGED
|
@@ -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
|
}
|
package/dist/tools/schemas.d.ts
CHANGED
|
@@ -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, {}, {}>;
|
package/dist/tools/schemas.js
CHANGED
|
@@ -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({});
|
package/dist/tools/search.js
CHANGED
|
@@ -27,7 +27,15 @@ export async function searchCode(options) {
|
|
|
27
27
|
args.push('-C', contextLines.toString());
|
|
28
28
|
}
|
|
29
29
|
if (filePattern) {
|
|
30
|
-
|
|
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
|
-
|
|
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'
|
|
199
|
+
excludeDirs: ['node_modules', '.git']
|
|
172
200
|
});
|
|
173
201
|
}
|
|
174
202
|
}
|
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;
|