@wonderwhy-er/desktop-commander 0.2.23 → 0.2.25
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 +14 -55
- package/dist/config-manager.d.ts +5 -0
- package/dist/config-manager.js +9 -0
- package/dist/custom-stdio.d.ts +1 -0
- package/dist/custom-stdio.js +19 -0
- package/dist/handlers/filesystem-handlers.d.ts +4 -0
- package/dist/handlers/filesystem-handlers.js +120 -14
- package/dist/handlers/node-handlers.d.ts +6 -0
- package/dist/handlers/node-handlers.js +73 -0
- package/dist/index.js +5 -3
- package/dist/search-manager.d.ts +25 -0
- package/dist/search-manager.js +212 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.js +188 -73
- package/dist/terminal-manager.d.ts +56 -2
- package/dist/terminal-manager.js +169 -13
- package/dist/tools/edit.d.ts +28 -4
- package/dist/tools/edit.js +87 -4
- package/dist/tools/filesystem.d.ts +23 -12
- package/dist/tools/filesystem.js +201 -416
- package/dist/tools/improved-process-tools.d.ts +2 -2
- package/dist/tools/improved-process-tools.js +244 -214
- package/dist/tools/mime-types.d.ts +1 -0
- package/dist/tools/mime-types.js +7 -0
- package/dist/tools/pdf/extract-images.d.ts +34 -0
- package/dist/tools/pdf/extract-images.js +132 -0
- package/dist/tools/pdf/index.d.ts +6 -0
- package/dist/tools/pdf/index.js +3 -0
- package/dist/tools/pdf/lib/pdf2md.d.ts +36 -0
- package/dist/tools/pdf/lib/pdf2md.js +76 -0
- package/dist/tools/pdf/manipulations.d.ts +13 -0
- package/dist/tools/pdf/manipulations.js +96 -0
- package/dist/tools/pdf/markdown.d.ts +7 -0
- package/dist/tools/pdf/markdown.js +37 -0
- package/dist/tools/pdf/utils.d.ts +12 -0
- package/dist/tools/pdf/utils.js +34 -0
- package/dist/tools/schemas.d.ts +167 -12
- package/dist/tools/schemas.js +54 -5
- package/dist/types.d.ts +2 -1
- package/dist/utils/ab-test.d.ts +8 -0
- package/dist/utils/ab-test.js +76 -0
- package/dist/utils/capture.js +5 -0
- package/dist/utils/feature-flags.js +7 -4
- package/dist/utils/files/base.d.ts +167 -0
- package/dist/utils/files/base.js +5 -0
- package/dist/utils/files/binary.d.ts +21 -0
- package/dist/utils/files/binary.js +65 -0
- package/dist/utils/files/excel.d.ts +24 -0
- package/dist/utils/files/excel.js +416 -0
- package/dist/utils/files/factory.d.ts +40 -0
- package/dist/utils/files/factory.js +101 -0
- package/dist/utils/files/image.d.ts +21 -0
- package/dist/utils/files/image.js +78 -0
- package/dist/utils/files/index.d.ts +10 -0
- package/dist/utils/files/index.js +13 -0
- package/dist/utils/files/pdf.d.ts +32 -0
- package/dist/utils/files/pdf.js +142 -0
- package/dist/utils/files/text.d.ts +63 -0
- package/dist/utils/files/text.js +357 -0
- package/dist/utils/open-browser.d.ts +9 -0
- package/dist/utils/open-browser.js +43 -0
- package/dist/utils/ripgrep-resolver.js +3 -2
- package/dist/utils/system-info.d.ts +5 -0
- package/dist/utils/system-info.js +71 -3
- package/dist/utils/usageTracker.js +6 -0
- package/dist/utils/welcome-onboarding.d.ts +9 -0
- package/dist/utils/welcome-onboarding.js +37 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +14 -3
package/dist/search-manager.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import fs from 'fs/promises';
|
|
3
4
|
import { validatePath } from './tools/filesystem.js';
|
|
4
5
|
import { capture } from './utils/capture.js';
|
|
5
6
|
import { getRipgrepPath } from './utils/ripgrep-resolver.js';
|
|
7
|
+
import { isExcelFile } from './utils/files/index.js';
|
|
6
8
|
/**
|
|
7
9
|
* Search Session Manager - handles ripgrep processes like terminal sessions
|
|
8
10
|
* Supports both file search and content search with progressive results
|
|
@@ -85,7 +87,26 @@ import { getRipgrepPath } from './utils/ripgrep-resolver.js';
|
|
|
85
87
|
requestedPath: options.rootPath,
|
|
86
88
|
validatedPath: validPath
|
|
87
89
|
});
|
|
90
|
+
// For content searches, only search Excel files when contextually relevant:
|
|
91
|
+
// - filePattern explicitly targets Excel files (*.xlsx, *.xls, etc.)
|
|
92
|
+
// - or rootPath is an Excel file itself
|
|
93
|
+
const shouldSearchExcel = options.searchType === 'content' &&
|
|
94
|
+
this.shouldIncludeExcelSearch(options.filePattern, validPath);
|
|
95
|
+
if (shouldSearchExcel) {
|
|
96
|
+
this.searchExcelFiles(validPath, options.pattern, options.ignoreCase !== false, options.maxResults, options.filePattern // Pass filePattern to filter Excel files too
|
|
97
|
+
).then(excelResults => {
|
|
98
|
+
// Add Excel results to session (merged after initial response)
|
|
99
|
+
for (const result of excelResults) {
|
|
100
|
+
session.results.push(result);
|
|
101
|
+
session.totalMatches++;
|
|
102
|
+
}
|
|
103
|
+
}).catch((err) => {
|
|
104
|
+
// Log Excel search errors but don't fail the whole search
|
|
105
|
+
capture('excel_search_error', { error: err instanceof Error ? err.message : String(err) });
|
|
106
|
+
});
|
|
107
|
+
}
|
|
88
108
|
// Wait for first chunk of data or early completion instead of fixed delay
|
|
109
|
+
// Excel search runs in background and results are merged via readSearchResults
|
|
89
110
|
const firstChunk = new Promise(resolve => {
|
|
90
111
|
const onData = () => {
|
|
91
112
|
session.process.stdout?.off('data', onData);
|
|
@@ -94,6 +115,7 @@ import { getRipgrepPath } from './utils/ripgrep-resolver.js';
|
|
|
94
115
|
session.process.stdout?.once('data', onData);
|
|
95
116
|
setTimeout(resolve, 40); // cap at 40ms instead of 50-100ms
|
|
96
117
|
});
|
|
118
|
+
// Only wait for ripgrep first chunk - Excel results merge asynchronously
|
|
97
119
|
await firstChunk;
|
|
98
120
|
return {
|
|
99
121
|
sessionId,
|
|
@@ -178,6 +200,170 @@ import { getRipgrepPath } from './utils/ripgrep-resolver.js';
|
|
|
178
200
|
totalResults: session.totalMatches + session.totalContextLines
|
|
179
201
|
}));
|
|
180
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Search Excel files for content matches
|
|
205
|
+
* Called during content search to include Excel files alongside text files
|
|
206
|
+
* Searches ALL sheets in each Excel file (row-wise for cross-column matching)
|
|
207
|
+
*
|
|
208
|
+
* TODO: Refactor - Extract Excel search logic to separate module (src/utils/search/excel-search.ts)
|
|
209
|
+
* and inject into SearchManager, similar to how file handlers are structured in src/utils/files/
|
|
210
|
+
* This would allow adding other file type searches (PDF, etc.) without bloating search-manager.ts
|
|
211
|
+
*/
|
|
212
|
+
async searchExcelFiles(rootPath, pattern, ignoreCase, maxResults, filePattern) {
|
|
213
|
+
const results = [];
|
|
214
|
+
// Build regex for matching content
|
|
215
|
+
const flags = ignoreCase ? 'i' : '';
|
|
216
|
+
let regex;
|
|
217
|
+
try {
|
|
218
|
+
regex = new RegExp(pattern, flags);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// If pattern is not valid regex, escape it for literal matching
|
|
222
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
223
|
+
regex = new RegExp(escaped, flags);
|
|
224
|
+
}
|
|
225
|
+
// Find Excel files recursively
|
|
226
|
+
let excelFiles = await this.findExcelFiles(rootPath);
|
|
227
|
+
// Filter by filePattern if provided
|
|
228
|
+
if (filePattern) {
|
|
229
|
+
const patterns = filePattern.split('|').map(p => p.trim()).filter(Boolean);
|
|
230
|
+
excelFiles = excelFiles.filter(filePath => {
|
|
231
|
+
const fileName = path.basename(filePath);
|
|
232
|
+
return patterns.some(pat => {
|
|
233
|
+
// Support glob-like patterns
|
|
234
|
+
if (pat.includes('*')) {
|
|
235
|
+
const regexPat = pat.replace(/\./g, '\\.').replace(/\*/g, '.*');
|
|
236
|
+
return new RegExp(`^${regexPat}$`, 'i').test(fileName);
|
|
237
|
+
}
|
|
238
|
+
// Exact match (case-insensitive)
|
|
239
|
+
return fileName.toLowerCase() === pat.toLowerCase();
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
// Dynamically import ExcelJS to search all sheets
|
|
244
|
+
const ExcelJS = await import('exceljs');
|
|
245
|
+
for (const filePath of excelFiles) {
|
|
246
|
+
if (maxResults && results.length >= maxResults)
|
|
247
|
+
break;
|
|
248
|
+
try {
|
|
249
|
+
const workbook = new ExcelJS.default.Workbook();
|
|
250
|
+
await workbook.xlsx.readFile(filePath);
|
|
251
|
+
// Search ALL sheets in the workbook (row-wise for speed and cross-column matching)
|
|
252
|
+
for (const worksheet of workbook.worksheets) {
|
|
253
|
+
if (maxResults && results.length >= maxResults)
|
|
254
|
+
break;
|
|
255
|
+
const sheetName = worksheet.name;
|
|
256
|
+
// Iterate through rows (faster than cell-by-cell)
|
|
257
|
+
worksheet.eachRow({ includeEmpty: false }, (row, rowNumber) => {
|
|
258
|
+
if (maxResults && results.length >= maxResults)
|
|
259
|
+
return;
|
|
260
|
+
// Build a concatenated string of all cell values in the row
|
|
261
|
+
const rowValues = [];
|
|
262
|
+
row.eachCell({ includeEmpty: false }, (cell) => {
|
|
263
|
+
if (cell.value === null || cell.value === undefined)
|
|
264
|
+
return;
|
|
265
|
+
let cellStr;
|
|
266
|
+
if (typeof cell.value === 'object') {
|
|
267
|
+
if ('result' in cell.value) {
|
|
268
|
+
cellStr = String(cell.value.result ?? '');
|
|
269
|
+
}
|
|
270
|
+
else if ('richText' in cell.value) {
|
|
271
|
+
cellStr = cell.value.richText.map((rt) => rt.text).join('');
|
|
272
|
+
}
|
|
273
|
+
else if ('text' in cell.value) {
|
|
274
|
+
cellStr = String(cell.value.text);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
cellStr = String(cell.value);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
cellStr = String(cell.value);
|
|
282
|
+
}
|
|
283
|
+
if (cellStr.trim()) {
|
|
284
|
+
rowValues.push(cellStr);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
// Join all cell values with space for cross-column matching
|
|
288
|
+
const rowText = rowValues.join(' ');
|
|
289
|
+
if (regex.test(rowText)) {
|
|
290
|
+
// Extract the matching portion for display
|
|
291
|
+
const match = rowText.match(regex);
|
|
292
|
+
const matchContext = match
|
|
293
|
+
? this.getMatchContext(rowText, match.index || 0, match[0].length)
|
|
294
|
+
: rowText.substring(0, 150);
|
|
295
|
+
results.push({
|
|
296
|
+
file: `${filePath}:${sheetName}!Row${rowNumber}`,
|
|
297
|
+
line: rowNumber,
|
|
298
|
+
match: matchContext,
|
|
299
|
+
type: 'content'
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
// Skip files that can't be read (permission issues, corrupted, etc.)
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return results;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Find all Excel files in a directory recursively
|
|
314
|
+
*/
|
|
315
|
+
async findExcelFiles(rootPath) {
|
|
316
|
+
const excelFiles = [];
|
|
317
|
+
async function walk(dir) {
|
|
318
|
+
try {
|
|
319
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
320
|
+
for (const entry of entries) {
|
|
321
|
+
const fullPath = path.join(dir, entry.name);
|
|
322
|
+
if (entry.isDirectory()) {
|
|
323
|
+
// Skip node_modules, .git, etc.
|
|
324
|
+
if (!entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
325
|
+
await walk(fullPath);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
else if (entry.isFile() && isExcelFile(entry.name)) {
|
|
329
|
+
excelFiles.push(fullPath);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
// Skip directories we can't read
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Check if rootPath is a file or directory
|
|
338
|
+
try {
|
|
339
|
+
const stats = await fs.stat(rootPath);
|
|
340
|
+
if (stats.isFile() && isExcelFile(rootPath)) {
|
|
341
|
+
return [rootPath];
|
|
342
|
+
}
|
|
343
|
+
else if (stats.isDirectory()) {
|
|
344
|
+
await walk(rootPath);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
// Path doesn't exist or can't be accessed
|
|
349
|
+
}
|
|
350
|
+
return excelFiles;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Extract context around a match for display (show surrounding text)
|
|
354
|
+
*/
|
|
355
|
+
getMatchContext(text, matchStart, matchLength) {
|
|
356
|
+
const contextChars = 50; // chars before and after match
|
|
357
|
+
const start = Math.max(0, matchStart - contextChars);
|
|
358
|
+
const end = Math.min(text.length, matchStart + matchLength + contextChars);
|
|
359
|
+
let context = text.substring(start, end);
|
|
360
|
+
// Add ellipsis if truncated
|
|
361
|
+
if (start > 0)
|
|
362
|
+
context = '...' + context;
|
|
363
|
+
if (end < text.length)
|
|
364
|
+
context = context + '...';
|
|
365
|
+
return context;
|
|
366
|
+
}
|
|
181
367
|
/**
|
|
182
368
|
* Clean up completed sessions older than specified time
|
|
183
369
|
* Called automatically by cleanup interval
|
|
@@ -215,6 +401,32 @@ import { getRipgrepPath } from './utils/ripgrep-resolver.js';
|
|
|
215
401
|
pattern.includes(']') ||
|
|
216
402
|
pattern.includes('}');
|
|
217
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* Determine if Excel search should be included based on context
|
|
406
|
+
* Only searches Excel files when:
|
|
407
|
+
* - filePattern explicitly targets Excel files (*.xlsx, *.xls, *.xlsm, *.xlsb)
|
|
408
|
+
* - or the rootPath itself is an Excel file
|
|
409
|
+
*/
|
|
410
|
+
shouldIncludeExcelSearch(filePattern, rootPath) {
|
|
411
|
+
const excelExtensions = ['.xlsx', '.xls', '.xlsm', '.xlsb'];
|
|
412
|
+
// Check if rootPath is an Excel file
|
|
413
|
+
if (rootPath) {
|
|
414
|
+
const lowerPath = rootPath.toLowerCase();
|
|
415
|
+
if (excelExtensions.some(ext => lowerPath.endsWith(ext))) {
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
// Check if filePattern targets Excel files
|
|
420
|
+
if (filePattern) {
|
|
421
|
+
const lowerPattern = filePattern.toLowerCase();
|
|
422
|
+
// Check for patterns like *.xlsx, *.xls, or explicit Excel extensions
|
|
423
|
+
if (excelExtensions.some(ext => lowerPattern.includes(`*${ext}`) ||
|
|
424
|
+
lowerPattern.endsWith(ext))) {
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
218
430
|
buildRipgrepArgs(options) {
|
|
219
431
|
const args = [];
|
|
220
432
|
if (options.searchType === 'content') {
|
package/dist/server.d.ts
CHANGED
|
@@ -7,6 +7,9 @@ export declare const server: Server<{
|
|
|
7
7
|
_meta?: {
|
|
8
8
|
[x: string]: unknown;
|
|
9
9
|
progressToken?: string | number | undefined;
|
|
10
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
11
|
+
taskId: string;
|
|
12
|
+
} | undefined;
|
|
10
13
|
} | undefined;
|
|
11
14
|
} | undefined;
|
|
12
15
|
}, {
|
|
@@ -15,12 +18,20 @@ export declare const server: Server<{
|
|
|
15
18
|
[x: string]: unknown;
|
|
16
19
|
_meta?: {
|
|
17
20
|
[x: string]: unknown;
|
|
21
|
+
progressToken?: string | number | undefined;
|
|
22
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
23
|
+
taskId: string;
|
|
24
|
+
} | undefined;
|
|
18
25
|
} | undefined;
|
|
19
26
|
} | undefined;
|
|
20
27
|
}, {
|
|
21
28
|
[x: string]: unknown;
|
|
22
29
|
_meta?: {
|
|
23
30
|
[x: string]: unknown;
|
|
31
|
+
progressToken?: string | number | undefined;
|
|
32
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
33
|
+
taskId: string;
|
|
34
|
+
} | undefined;
|
|
24
35
|
} | undefined;
|
|
25
36
|
}>;
|
|
26
37
|
declare let currentClient: {
|