@wonderwhy-er/desktop-commander 0.2.22 → 0.2.24

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 (60) hide show
  1. package/README.md +14 -55
  2. package/dist/custom-stdio.d.ts +1 -0
  3. package/dist/custom-stdio.js +19 -0
  4. package/dist/handlers/filesystem-handlers.d.ts +4 -0
  5. package/dist/handlers/filesystem-handlers.js +120 -14
  6. package/dist/handlers/node-handlers.d.ts +6 -0
  7. package/dist/handlers/node-handlers.js +73 -0
  8. package/dist/index.js +5 -3
  9. package/dist/search-manager.d.ts +25 -0
  10. package/dist/search-manager.js +212 -0
  11. package/dist/server.js +161 -107
  12. package/dist/terminal-manager.d.ts +56 -2
  13. package/dist/terminal-manager.js +169 -13
  14. package/dist/tools/edit.d.ts +28 -4
  15. package/dist/tools/edit.js +87 -4
  16. package/dist/tools/filesystem.d.ts +23 -12
  17. package/dist/tools/filesystem.js +201 -416
  18. package/dist/tools/improved-process-tools.d.ts +2 -2
  19. package/dist/tools/improved-process-tools.js +244 -214
  20. package/dist/tools/mime-types.d.ts +1 -0
  21. package/dist/tools/mime-types.js +7 -0
  22. package/dist/tools/pdf/extract-images.d.ts +34 -0
  23. package/dist/tools/pdf/extract-images.js +132 -0
  24. package/dist/tools/pdf/index.d.ts +6 -0
  25. package/dist/tools/pdf/index.js +3 -0
  26. package/dist/tools/pdf/lib/pdf2md.d.ts +36 -0
  27. package/dist/tools/pdf/lib/pdf2md.js +76 -0
  28. package/dist/tools/pdf/manipulations.d.ts +13 -0
  29. package/dist/tools/pdf/manipulations.js +96 -0
  30. package/dist/tools/pdf/markdown.d.ts +7 -0
  31. package/dist/tools/pdf/markdown.js +37 -0
  32. package/dist/tools/pdf/utils.d.ts +12 -0
  33. package/dist/tools/pdf/utils.js +34 -0
  34. package/dist/tools/prompts.js +0 -10
  35. package/dist/tools/schemas.d.ts +167 -12
  36. package/dist/tools/schemas.js +54 -5
  37. package/dist/types.d.ts +2 -1
  38. package/dist/utils/feature-flags.js +7 -4
  39. package/dist/utils/files/base.d.ts +167 -0
  40. package/dist/utils/files/base.js +5 -0
  41. package/dist/utils/files/binary.d.ts +21 -0
  42. package/dist/utils/files/binary.js +65 -0
  43. package/dist/utils/files/excel.d.ts +24 -0
  44. package/dist/utils/files/excel.js +416 -0
  45. package/dist/utils/files/factory.d.ts +40 -0
  46. package/dist/utils/files/factory.js +101 -0
  47. package/dist/utils/files/image.d.ts +21 -0
  48. package/dist/utils/files/image.js +78 -0
  49. package/dist/utils/files/index.d.ts +10 -0
  50. package/dist/utils/files/index.js +13 -0
  51. package/dist/utils/files/pdf.d.ts +32 -0
  52. package/dist/utils/files/pdf.js +142 -0
  53. package/dist/utils/files/text.d.ts +63 -0
  54. package/dist/utils/files/text.js +357 -0
  55. package/dist/utils/ripgrep-resolver.js +3 -2
  56. package/dist/utils/system-info.d.ts +5 -0
  57. package/dist/utils/system-info.js +71 -3
  58. package/dist/version.d.ts +1 -1
  59. package/dist/version.js +1 -1
  60. package/package.json +14 -3
@@ -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') {