@loxia-labs/loxia-autopilot-one 1.0.1

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 (80) hide show
  1. package/LICENSE +267 -0
  2. package/README.md +509 -0
  3. package/bin/cli.js +117 -0
  4. package/package.json +94 -0
  5. package/scripts/install-scanners.js +236 -0
  6. package/src/analyzers/CSSAnalyzer.js +297 -0
  7. package/src/analyzers/ConfigValidator.js +690 -0
  8. package/src/analyzers/ESLintAnalyzer.js +320 -0
  9. package/src/analyzers/JavaScriptAnalyzer.js +261 -0
  10. package/src/analyzers/PrettierFormatter.js +247 -0
  11. package/src/analyzers/PythonAnalyzer.js +266 -0
  12. package/src/analyzers/SecurityAnalyzer.js +729 -0
  13. package/src/analyzers/TypeScriptAnalyzer.js +247 -0
  14. package/src/analyzers/codeCloneDetector/analyzer.js +344 -0
  15. package/src/analyzers/codeCloneDetector/detector.js +203 -0
  16. package/src/analyzers/codeCloneDetector/index.js +160 -0
  17. package/src/analyzers/codeCloneDetector/parser.js +199 -0
  18. package/src/analyzers/codeCloneDetector/reporter.js +148 -0
  19. package/src/analyzers/codeCloneDetector/scanner.js +59 -0
  20. package/src/core/agentPool.js +1474 -0
  21. package/src/core/agentScheduler.js +2147 -0
  22. package/src/core/contextManager.js +709 -0
  23. package/src/core/messageProcessor.js +732 -0
  24. package/src/core/orchestrator.js +548 -0
  25. package/src/core/stateManager.js +877 -0
  26. package/src/index.js +631 -0
  27. package/src/interfaces/cli.js +549 -0
  28. package/src/interfaces/webServer.js +2162 -0
  29. package/src/modules/fileExplorer/controller.js +280 -0
  30. package/src/modules/fileExplorer/index.js +37 -0
  31. package/src/modules/fileExplorer/middleware.js +92 -0
  32. package/src/modules/fileExplorer/routes.js +125 -0
  33. package/src/modules/fileExplorer/types.js +44 -0
  34. package/src/services/aiService.js +1232 -0
  35. package/src/services/apiKeyManager.js +164 -0
  36. package/src/services/benchmarkService.js +366 -0
  37. package/src/services/budgetService.js +539 -0
  38. package/src/services/contextInjectionService.js +247 -0
  39. package/src/services/conversationCompactionService.js +637 -0
  40. package/src/services/errorHandler.js +810 -0
  41. package/src/services/fileAttachmentService.js +544 -0
  42. package/src/services/modelRouterService.js +366 -0
  43. package/src/services/modelsService.js +322 -0
  44. package/src/services/qualityInspector.js +796 -0
  45. package/src/services/tokenCountingService.js +536 -0
  46. package/src/tools/agentCommunicationTool.js +1344 -0
  47. package/src/tools/agentDelayTool.js +485 -0
  48. package/src/tools/asyncToolManager.js +604 -0
  49. package/src/tools/baseTool.js +800 -0
  50. package/src/tools/browserTool.js +920 -0
  51. package/src/tools/cloneDetectionTool.js +621 -0
  52. package/src/tools/dependencyResolverTool.js +1215 -0
  53. package/src/tools/fileContentReplaceTool.js +875 -0
  54. package/src/tools/fileSystemTool.js +1107 -0
  55. package/src/tools/fileTreeTool.js +853 -0
  56. package/src/tools/imageTool.js +901 -0
  57. package/src/tools/importAnalyzerTool.js +1060 -0
  58. package/src/tools/jobDoneTool.js +248 -0
  59. package/src/tools/seekTool.js +956 -0
  60. package/src/tools/staticAnalysisTool.js +1778 -0
  61. package/src/tools/taskManagerTool.js +2873 -0
  62. package/src/tools/terminalTool.js +2304 -0
  63. package/src/tools/webTool.js +1430 -0
  64. package/src/types/agent.js +519 -0
  65. package/src/types/contextReference.js +972 -0
  66. package/src/types/conversation.js +730 -0
  67. package/src/types/toolCommand.js +747 -0
  68. package/src/utilities/attachmentValidator.js +292 -0
  69. package/src/utilities/configManager.js +582 -0
  70. package/src/utilities/constants.js +722 -0
  71. package/src/utilities/directoryAccessManager.js +535 -0
  72. package/src/utilities/fileProcessor.js +307 -0
  73. package/src/utilities/logger.js +436 -0
  74. package/src/utilities/tagParser.js +1246 -0
  75. package/src/utilities/toolConstants.js +317 -0
  76. package/web-ui/build/index.html +15 -0
  77. package/web-ui/build/logo.png +0 -0
  78. package/web-ui/build/logo2.png +0 -0
  79. package/web-ui/build/static/index-CjkkcnFA.js +344 -0
  80. package/web-ui/build/static/index-Dy2bYbOa.css +1 -0
@@ -0,0 +1,853 @@
1
+ /**
2
+ * FileTreeTool - Generate hierarchical file tree representations
3
+ *
4
+ * Purpose:
5
+ * - Generate ASCII tree of project directory structure
6
+ * - Help agents understand project organization
7
+ * - Support filtering by file types (whitelist/blacklist)
8
+ * - Respect directory access permissions
9
+ * - Provide focused views of codebases
10
+ */
11
+
12
+ import { BaseTool } from './baseTool.js';
13
+ import { promises as fs } from 'fs';
14
+ import path from 'path';
15
+
16
+ // Configuration constants
17
+ const TREE_CONFIG = {
18
+ // Depth limits
19
+ DEFAULT_MAX_DEPTH: 3,
20
+ MAX_DEPTH_LIMIT: 10, // Absolute maximum to prevent performance issues
21
+
22
+ // Size limits
23
+ MAX_FILES_IN_TREE: 10000, // Maximum files to include
24
+ MAX_DIRECTORIES: 1000, // Maximum directories to scan
25
+
26
+ // Display settings
27
+ SHOW_FILES_DEFAULT: true,
28
+ SHOW_HIDDEN_DEFAULT: false,
29
+ SHOW_SIZES_DEFAULT: false,
30
+
31
+ // Tree symbols
32
+ SYMBOLS: {
33
+ BRANCH: '├── ',
34
+ LAST_BRANCH: '└── ',
35
+ VERTICAL: '│ ',
36
+ INDENT: ' '
37
+ }
38
+ };
39
+
40
+ // Default directories to ignore
41
+ const DEFAULT_IGNORE_DIRECTORIES = [
42
+ 'node_modules',
43
+ '.git',
44
+ 'dist',
45
+ 'build',
46
+ 'coverage',
47
+ '.next',
48
+ '.nuxt',
49
+ '.cache',
50
+ 'out',
51
+ 'target',
52
+ 'vendor',
53
+ 'bower_components',
54
+ '.vscode',
55
+ '.idea',
56
+ '.vs',
57
+ '__pycache__',
58
+ 'venv',
59
+ 'env',
60
+ '.env',
61
+ '.pytest_cache',
62
+ '.mypy_cache',
63
+ 'eggs',
64
+ '.eggs',
65
+ 'lib',
66
+ 'lib64',
67
+ 'parts',
68
+ 'sdist',
69
+ 'wheels',
70
+ '.tox',
71
+ '.nox',
72
+ 'htmlcov',
73
+ '.hypothesis',
74
+ 'tmp',
75
+ 'temp',
76
+ '.tmp',
77
+ '.temp'
78
+ ];
79
+
80
+ // Default file extensions to ignore (blacklist)
81
+ const DEFAULT_IGNORE_EXTENSIONS = [
82
+ '.map',
83
+ '.min.js',
84
+ '.min.css',
85
+ '.lock',
86
+ '.log',
87
+ '.tmp',
88
+ '.temp',
89
+ '.pyc',
90
+ '.pyo',
91
+ '.pyd',
92
+ '.so',
93
+ '.dll',
94
+ '.dylib',
95
+ '.exe',
96
+ '.obj',
97
+ '.o',
98
+ '.a',
99
+ '.class',
100
+ '.jar',
101
+ '.war'
102
+ ];
103
+
104
+ // Common binary/media file extensions to optionally ignore
105
+ const BINARY_EXTENSIONS = [
106
+ '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.svg',
107
+ '.pdf', '.zip', '.tar', '.gz', '.rar', '.7z',
108
+ '.mp3', '.mp4', '.avi', '.mov', '.wmv',
109
+ '.woff', '.woff2', '.ttf', '.eot',
110
+ '.bin', '.dat', '.db', '.sqlite'
111
+ ];
112
+
113
+ class FileTreeTool extends BaseTool {
114
+ constructor(config = {}, logger = null) {
115
+ super(config, logger);
116
+
117
+ // Tool metadata
118
+ this.requiresProject = true;
119
+ this.isAsync = true;
120
+ this.timeout = config.timeout || 120000; // 2 minutes default
121
+
122
+ // Merge config with defaults
123
+ this.treeConfig = {
124
+ ...TREE_CONFIG,
125
+ ...config.treeConfig
126
+ };
127
+
128
+ // Track statistics during tree generation
129
+ this.stats = {
130
+ filesCount: 0,
131
+ directoriesCount: 0,
132
+ skippedCount: 0
133
+ };
134
+ }
135
+
136
+ /**
137
+ * Get tool description for LLM consumption
138
+ * @returns {string} Tool description
139
+ */
140
+ getDescription() {
141
+ return `
142
+ File Tree Tool: Generate hierarchical tree representation of project directory structure.
143
+
144
+ XML USAGE:
145
+ <file-tree>
146
+ <directory>src</directory>
147
+ <max-depth>4</max-depth>
148
+ <include-extensions>.js,.jsx,.ts,.tsx</include-extensions>
149
+ <exclude-extensions>.test.js,.spec.js</exclude-extensions>
150
+ <show-files>true</show-files>
151
+ <show-hidden>false</show-hidden>
152
+ </file-tree>
153
+
154
+ JSON USAGE:
155
+ \`\`\`json
156
+ {
157
+ "toolId": "file-tree",
158
+ "directory": "src",
159
+ "maxDepth": 4,
160
+ "includeExtensions": [".js", ".jsx", ".ts", ".tsx"],
161
+ "excludeExtensions": [".test.js", ".spec.js"],
162
+ "showFiles": true,
163
+ "showHidden": false
164
+ }
165
+ \`\`\`
166
+
167
+ PARAMETERS:
168
+
169
+ directory (optional):
170
+ - Directory to scan (default: working directory)
171
+ - Can be relative or absolute path
172
+ - Examples: "src", "src/components", "/home/user/project"
173
+
174
+ maxDepth (optional):
175
+ - Maximum depth to scan (default: ${TREE_CONFIG.DEFAULT_MAX_DEPTH})
176
+ - Range: 1-${TREE_CONFIG.MAX_DEPTH_LIMIT}
177
+ - Deeper = more detail, slower performance
178
+
179
+ includeExtensions (optional - WHITELIST):
180
+ - Include ONLY these file extensions
181
+ - Comma-separated list
182
+ - Examples: ".js,.jsx,.ts,.tsx" or ".py,.pyw"
183
+ - Takes precedence over excludeExtensions
184
+
185
+ excludeExtensions (optional - BLACKLIST):
186
+ - Exclude these file extensions
187
+ - Comma-separated list
188
+ - Examples: ".test.js,.spec.js,.min.js"
189
+ - Only applies if includeExtensions is not set
190
+
191
+ excludeDirectories (optional):
192
+ - Additional directories to ignore
193
+ - Already ignores: node_modules, .git, dist, build, etc.
194
+ - Examples: "tests,fixtures,mocks"
195
+
196
+ showFiles (optional):
197
+ - Whether to show files (default: true)
198
+ - Set to false to show only directory structure
199
+
200
+ showHidden (optional):
201
+ - Whether to show hidden files/folders (default: false)
202
+ - Hidden = starts with '.'
203
+
204
+ showSizes (optional):
205
+ - Whether to show file sizes (default: false)
206
+ - Format: KB, MB
207
+
208
+ ignoreBinaryFiles (optional):
209
+ - Whether to ignore binary/media files (default: false)
210
+ - Ignores: images, videos, archives, fonts, etc.
211
+
212
+ FILTERING STRATEGIES:
213
+
214
+ Strategy 1: WHITELIST (Include Only)
215
+ <file-tree>
216
+ <directory>src</directory>
217
+ <include-extensions>.js,.jsx</include-extensions>
218
+ </file-tree>
219
+ Result: Shows ONLY .js and .jsx files
220
+
221
+ Strategy 2: BLACKLIST (Exclude)
222
+ <file-tree>
223
+ <directory>src</directory>
224
+ <exclude-extensions>.test.js,.spec.js</exclude-extensions>
225
+ </file-tree>
226
+ Result: Shows all files EXCEPT .test.js and .spec.js
227
+
228
+ Strategy 3: COMBINED
229
+ <file-tree>
230
+ <directory>src</directory>
231
+ <include-extensions>.js,.jsx,.ts,.tsx</include-extensions>
232
+ <exclude-extensions>.test.js,.spec.ts</exclude-extensions>
233
+ </file-tree>
234
+ Result: Shows JS/JSX/TS/TSX files, but excludes test files
235
+
236
+ EXAMPLES:
237
+
238
+ Example 1 - Basic tree:
239
+ <file-tree>
240
+ <directory>.</directory>
241
+ </file-tree>
242
+
243
+ Example 2 - JavaScript/TypeScript only:
244
+ <file-tree>
245
+ <directory>src</directory>
246
+ <include-extensions>.js,.jsx,.ts,.tsx</include-extensions>
247
+ <max-depth>5</max-depth>
248
+ </file-tree>
249
+
250
+ Example 3 - Python project structure:
251
+ <file-tree>
252
+ <directory>.</directory>
253
+ <include-extensions>.py</include-extensions>
254
+ <exclude-extensions>.pyc</exclude-extensions>
255
+ <show-files>true</show-files>
256
+ </file-tree>
257
+
258
+ Example 4 - Directory structure only:
259
+ <file-tree>
260
+ <directory>src</directory>
261
+ <show-files>false</show-files>
262
+ <max-depth>3</max-depth>
263
+ </file-tree>
264
+
265
+ Example 5 - Exclude test files:
266
+ <file-tree>
267
+ <directory>src</directory>
268
+ <exclude-extensions>.test.js,.spec.js,.test.ts,.spec.ts</exclude-extensions>
269
+ <exclude-directories>__tests__,tests,specs</exclude-directories>
270
+ </file-tree>
271
+
272
+ OUTPUT FORMAT:
273
+ src/
274
+ ├── components/
275
+ │ ├── Header.jsx
276
+ │ ├── Footer.jsx
277
+ │ └── Layout.jsx
278
+ ├── utils/
279
+ │ ├── helpers.js
280
+ │ └── config.js
281
+ └── index.js
282
+
283
+ AUTOMATIC IGNORES:
284
+ The tool automatically ignores common directories:
285
+ - node_modules, .git, dist, build, coverage
286
+ - .next, .nuxt, .cache, out, target
287
+ - .vscode, .idea, __pycache__, venv
288
+ And more (see full list in DEFAULT_IGNORE_DIRECTORIES)
289
+
290
+ LIMITATIONS:
291
+ - Maximum depth: ${TREE_CONFIG.MAX_DEPTH_LIMIT}
292
+ - Maximum files: ${TREE_CONFIG.MAX_FILES_IN_TREE}
293
+ - Maximum directories: ${TREE_CONFIG.MAX_DIRECTORIES}
294
+ - Binary files can be filtered with ignoreBinaryFiles option
295
+
296
+ MULTI-DIRECTORY SUPPORT:
297
+ Works with agent's configured accessible directories.
298
+ Validates paths against directoryAccess configuration.
299
+ `;
300
+ }
301
+
302
+ /**
303
+ * Parse parameters from tool command content
304
+ * @param {string} content - Raw tool command content
305
+ * @returns {Object} Parsed parameters
306
+ */
307
+ parseParameters(content) {
308
+ try {
309
+ // Try JSON first
310
+ if (content.trim().startsWith('{')) {
311
+ const parsed = JSON.parse(content);
312
+
313
+ return {
314
+ directory: parsed.directory || '.',
315
+ maxDepth: parsed.maxDepth || TREE_CONFIG.DEFAULT_MAX_DEPTH,
316
+ includeExtensions: parsed.includeExtensions || [],
317
+ excludeExtensions: parsed.excludeExtensions || [],
318
+ excludeDirectories: parsed.excludeDirectories || [],
319
+ showFiles: parsed.showFiles !== false,
320
+ showHidden: parsed.showHidden === true,
321
+ showSizes: parsed.showSizes === true,
322
+ ignoreBinaryFiles: parsed.ignoreBinaryFiles === true
323
+ };
324
+ }
325
+
326
+ // XML parsing
327
+ const params = {
328
+ directory: '.',
329
+ maxDepth: TREE_CONFIG.DEFAULT_MAX_DEPTH,
330
+ includeExtensions: [],
331
+ excludeExtensions: [],
332
+ excludeDirectories: [],
333
+ showFiles: true,
334
+ showHidden: false,
335
+ showSizes: false,
336
+ ignoreBinaryFiles: false
337
+ };
338
+
339
+ // Extract directory
340
+ const dirPattern = /<directory>(.*?)<\/directory>/i;
341
+ const dirMatch = dirPattern.exec(content);
342
+ if (dirMatch) {
343
+ params.directory = dirMatch[1].trim();
344
+ }
345
+
346
+ // Extract max-depth
347
+ const depthPattern = /<max-depth>(\d+)<\/max-depth>/i;
348
+ const depthMatch = depthPattern.exec(content);
349
+ if (depthMatch) {
350
+ params.maxDepth = parseInt(depthMatch[1], 10);
351
+ }
352
+
353
+ // Extract include-extensions (whitelist)
354
+ const includePattern = /<include-extensions>(.*?)<\/include-extensions>/i;
355
+ const includeMatch = includePattern.exec(content);
356
+ if (includeMatch) {
357
+ params.includeExtensions = includeMatch[1]
358
+ .split(',')
359
+ .map(ext => ext.trim())
360
+ .filter(ext => ext.length > 0);
361
+ }
362
+
363
+ // Extract exclude-extensions (blacklist)
364
+ const excludePattern = /<exclude-extensions>(.*?)<\/exclude-extensions>/i;
365
+ const excludeMatch = excludePattern.exec(content);
366
+ if (excludeMatch) {
367
+ params.excludeExtensions = excludeMatch[1]
368
+ .split(',')
369
+ .map(ext => ext.trim())
370
+ .filter(ext => ext.length > 0);
371
+ }
372
+
373
+ // Extract exclude-directories
374
+ const excludeDirsPattern = /<exclude-directories>(.*?)<\/exclude-directories>/i;
375
+ const excludeDirsMatch = excludeDirsPattern.exec(content);
376
+ if (excludeDirsMatch) {
377
+ params.excludeDirectories = excludeDirsMatch[1]
378
+ .split(',')
379
+ .map(dir => dir.trim())
380
+ .filter(dir => dir.length > 0);
381
+ }
382
+
383
+ // Extract boolean flags
384
+ const showFilesPattern = /<show-files>(.*?)<\/show-files>/i;
385
+ const showFilesMatch = showFilesPattern.exec(content);
386
+ if (showFilesMatch) {
387
+ params.showFiles = showFilesMatch[1].trim().toLowerCase() !== 'false';
388
+ }
389
+
390
+ const showHiddenPattern = /<show-hidden>(.*?)<\/show-hidden>/i;
391
+ const showHiddenMatch = showHiddenPattern.exec(content);
392
+ if (showHiddenMatch) {
393
+ params.showHidden = showHiddenMatch[1].trim().toLowerCase() === 'true';
394
+ }
395
+
396
+ const showSizesPattern = /<show-sizes>(.*?)<\/show-sizes>/i;
397
+ const showSizesMatch = showSizesPattern.exec(content);
398
+ if (showSizesMatch) {
399
+ params.showSizes = showSizesMatch[1].trim().toLowerCase() === 'true';
400
+ }
401
+
402
+ const ignoreBinaryPattern = /<ignore-binary-files>(.*?)<\/ignore-binary-files>/i;
403
+ const ignoreBinaryMatch = ignoreBinaryPattern.exec(content);
404
+ if (ignoreBinaryMatch) {
405
+ params.ignoreBinaryFiles = ignoreBinaryMatch[1].trim().toLowerCase() === 'true';
406
+ }
407
+
408
+ return params;
409
+
410
+ } catch (error) {
411
+ this.logger?.error('Failed to parse file-tree parameters', { error: error.message });
412
+ return {
413
+ directory: '.',
414
+ maxDepth: TREE_CONFIG.DEFAULT_MAX_DEPTH,
415
+ includeExtensions: [],
416
+ excludeExtensions: [],
417
+ excludeDirectories: [],
418
+ showFiles: true,
419
+ showHidden: false,
420
+ showSizes: false,
421
+ ignoreBinaryFiles: false,
422
+ parseError: error.message
423
+ };
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Get required parameters
429
+ * @returns {Array<string>} Array of required parameter names
430
+ */
431
+ getRequiredParameters() {
432
+ return []; // All parameters are optional
433
+ }
434
+
435
+ /**
436
+ * Custom parameter validation
437
+ * @param {Object} params - Parameters to validate
438
+ * @returns {Object} Validation result
439
+ */
440
+ customValidateParameters(params) {
441
+ const errors = [];
442
+
443
+ // Validate maxDepth
444
+ if (params.maxDepth !== undefined) {
445
+ if (typeof params.maxDepth !== 'number' || params.maxDepth < 1) {
446
+ errors.push('max-depth must be a positive number');
447
+ } else if (params.maxDepth > this.treeConfig.MAX_DEPTH_LIMIT) {
448
+ errors.push(`max-depth cannot exceed ${this.treeConfig.MAX_DEPTH_LIMIT}`);
449
+ }
450
+ }
451
+
452
+ // Validate directory path
453
+ if (params.directory && params.directory.includes('..')) {
454
+ errors.push('Path traversal (..) not allowed for security');
455
+ }
456
+
457
+ // Validate includeExtensions format
458
+ if (params.includeExtensions !== undefined && params.includeExtensions !== null) {
459
+ if (!Array.isArray(params.includeExtensions)) {
460
+ errors.push('includeExtensions must be an array');
461
+ } else {
462
+ for (const ext of params.includeExtensions) {
463
+ if (!ext.startsWith('.')) {
464
+ errors.push(`Extension "${ext}" must start with a dot (e.g., ".js")`);
465
+ }
466
+ }
467
+ }
468
+ }
469
+
470
+ // Validate excludeExtensions format
471
+ if (params.excludeExtensions !== undefined && params.excludeExtensions !== null) {
472
+ if (!Array.isArray(params.excludeExtensions)) {
473
+ errors.push('excludeExtensions must be an array');
474
+ } else {
475
+ for (const ext of params.excludeExtensions) {
476
+ if (!ext.startsWith('.')) {
477
+ errors.push(`Extension "${ext}" must start with a dot (e.g., ".js")`);
478
+ }
479
+ }
480
+ }
481
+ }
482
+
483
+ // Validate excludeDirectories format
484
+ if (params.excludeDirectories !== undefined && params.excludeDirectories !== null) {
485
+ if (!Array.isArray(params.excludeDirectories)) {
486
+ errors.push('excludeDirectories must be an array');
487
+ }
488
+ }
489
+
490
+ // Throw error if validation fails
491
+ if (errors.length > 0) {
492
+ throw new Error(`Parameter validation failed: ${errors.join(', ')}`);
493
+ }
494
+
495
+ return {
496
+ valid: true,
497
+ errors: []
498
+ };
499
+ }
500
+
501
+ /**
502
+ * Execute tool with parsed parameters
503
+ * @param {Object} params - Parsed parameters
504
+ * @param {Object} context - Execution context
505
+ * @returns {Promise<Object>} Execution result
506
+ */
507
+ async execute(params, context) {
508
+ // Extract params with defaults
509
+ const {
510
+ directory = '.',
511
+ maxDepth = TREE_CONFIG.DEFAULT_MAX_DEPTH,
512
+ includeExtensions = [],
513
+ excludeExtensions = [],
514
+ excludeDirectories = [],
515
+ showFiles = true,
516
+ showHidden = false,
517
+ showSizes = false,
518
+ ignoreBinaryFiles = false
519
+ } = params;
520
+ const { projectDir, agentId, directoryAccess } = context;
521
+
522
+ // Determine working directory (respect multi-directory access)
523
+ let workingDirectory = projectDir || process.cwd();
524
+
525
+ if (directoryAccess && directoryAccess.workingDirectory) {
526
+ workingDirectory = directoryAccess.workingDirectory;
527
+ this.logger?.info('Using agent configured working directory', {
528
+ workingDirectory: directoryAccess.workingDirectory,
529
+ agentId
530
+ });
531
+ }
532
+
533
+ // Resolve target directory
534
+ const targetDir = path.isAbsolute(directory)
535
+ ? directory
536
+ : path.resolve(workingDirectory, directory);
537
+
538
+ // Validate directory exists
539
+ try {
540
+ const stats = await fs.stat(targetDir);
541
+ if (!stats.isDirectory()) {
542
+ throw new Error('Path is not a directory');
543
+ }
544
+ } catch (error) {
545
+ throw new Error(`Directory does not exist or is inaccessible: ${directory}`);
546
+ }
547
+
548
+ this.logger?.info('Generating file tree', {
549
+ directory,
550
+ targetDir,
551
+ maxDepth,
552
+ includeExtensions: includeExtensions?.length || 0,
553
+ excludeExtensions: excludeExtensions?.length || 0,
554
+ agentId
555
+ });
556
+
557
+ // Reset statistics
558
+ this.stats = {
559
+ filesCount: 0,
560
+ directoriesCount: 0,
561
+ skippedCount: 0
562
+ };
563
+
564
+ // Build ignore lists (ensure arrays exist)
565
+ const ignoreDirs = [...DEFAULT_IGNORE_DIRECTORIES, ...(excludeDirectories || [])];
566
+ const ignoreExts = [...DEFAULT_IGNORE_EXTENSIONS];
567
+
568
+ if (ignoreBinaryFiles) {
569
+ ignoreExts.push(...BINARY_EXTENSIONS);
570
+ }
571
+
572
+ // Generate tree (ensure arrays exist)
573
+ const tree = await this.buildTree(
574
+ targetDir,
575
+ targetDir,
576
+ 0,
577
+ maxDepth,
578
+ ignoreDirs,
579
+ includeExtensions || [],
580
+ [...(excludeExtensions || []), ...ignoreExts],
581
+ showFiles,
582
+ showHidden,
583
+ showSizes
584
+ );
585
+
586
+ // Format tree as string
587
+ const treeString = this.formatTree(tree);
588
+
589
+ // Generate summary
590
+ const summary = this.generateSummary(directory, this.stats, maxDepth);
591
+
592
+ return {
593
+ success: true,
594
+ directory: targetDir,
595
+ tree: treeString,
596
+ summary,
597
+ maxDepth,
598
+ totalFiles: this.stats.filesCount,
599
+ totalDirectories: this.stats.directoriesCount,
600
+ skippedCount: this.stats.skippedCount,
601
+ statistics: { ...this.stats },
602
+ toolUsed: 'file-tree'
603
+ };
604
+ }
605
+
606
+ /**
607
+ * Build tree data structure recursively
608
+ * @param {string} basePath - Base path for relative calculations
609
+ * @param {string} currentPath - Current directory being processed
610
+ * @param {number} currentDepth - Current depth in tree
611
+ * @param {number} maxDepth - Maximum depth to scan
612
+ * @param {Array<string>} ignoreDirs - Directories to ignore
613
+ * @param {Array<string>} includeExts - Extensions to include (whitelist)
614
+ * @param {Array<string>} excludeExts - Extensions to exclude (blacklist)
615
+ * @param {boolean} showFiles - Whether to show files
616
+ * @param {boolean} showHidden - Whether to show hidden files/folders
617
+ * @param {boolean} showSizes - Whether to show file sizes
618
+ * @returns {Promise<Object>} Tree node
619
+ * @private
620
+ */
621
+ async buildTree(
622
+ basePath,
623
+ currentPath,
624
+ currentDepth,
625
+ maxDepth,
626
+ ignoreDirs,
627
+ includeExts,
628
+ excludeExts,
629
+ showFiles,
630
+ showHidden,
631
+ showSizes
632
+ ) {
633
+ // Check depth limit
634
+ if (currentDepth > maxDepth) {
635
+ return null;
636
+ }
637
+
638
+ // Check directory count limit
639
+ if (this.stats.directoriesCount >= this.treeConfig.MAX_DIRECTORIES) {
640
+ this.logger?.warn('Maximum directory count reached', {
641
+ maxDirectories: this.treeConfig.MAX_DIRECTORIES
642
+ });
643
+ return null;
644
+ }
645
+
646
+ try {
647
+ const entries = await fs.readdir(currentPath, { withFileTypes: true });
648
+
649
+ const node = {
650
+ name: currentDepth === 0 ? path.basename(currentPath) + '/' : path.basename(currentPath),
651
+ path: path.relative(basePath, currentPath),
652
+ type: 'directory',
653
+ children: []
654
+ };
655
+
656
+ this.stats.directoriesCount++;
657
+
658
+ for (const entry of entries) {
659
+ // Check file count limit
660
+ if (this.stats.filesCount >= this.treeConfig.MAX_FILES_IN_TREE) {
661
+ this.logger?.warn('Maximum file count reached', {
662
+ maxFiles: this.treeConfig.MAX_FILES_IN_TREE
663
+ });
664
+ break;
665
+ }
666
+
667
+ // Skip hidden files/folders if not showing hidden
668
+ if (!showHidden && entry.name.startsWith('.')) {
669
+ this.stats.skippedCount++;
670
+ continue;
671
+ }
672
+
673
+ const entryPath = path.join(currentPath, entry.name);
674
+
675
+ if (entry.isDirectory()) {
676
+ // Skip ignored directories
677
+ if (ignoreDirs.includes(entry.name)) {
678
+ this.stats.skippedCount++;
679
+ continue;
680
+ }
681
+
682
+ // Recursively build subtree
683
+ const subTree = await this.buildTree(
684
+ basePath,
685
+ entryPath,
686
+ currentDepth + 1,
687
+ maxDepth,
688
+ ignoreDirs,
689
+ includeExts,
690
+ excludeExts,
691
+ showFiles,
692
+ showHidden,
693
+ showSizes
694
+ );
695
+
696
+ if (subTree && (subTree.children.length > 0 || currentDepth === 0)) {
697
+ node.children.push(subTree);
698
+ }
699
+ } else if (entry.isFile() && showFiles) {
700
+ // Files are children of current directory, so they're at currentDepth + 1
701
+ // Don't show files if they would exceed maxDepth
702
+ if (currentDepth + 1 > maxDepth) {
703
+ continue;
704
+ }
705
+
706
+ const ext = path.extname(entry.name);
707
+
708
+ // WHITELIST: If includeExtensions is specified, ONLY include those
709
+ if (includeExts.length > 0) {
710
+ if (!includeExts.includes(ext)) {
711
+ this.stats.skippedCount++;
712
+ continue;
713
+ }
714
+ }
715
+ // BLACKLIST: Otherwise, exclude based on excludeExtensions
716
+ else if (excludeExts.includes(ext)) {
717
+ this.stats.skippedCount++;
718
+ continue;
719
+ }
720
+
721
+ // Get file size if requested
722
+ let sizeInfo = '';
723
+ if (showSizes) {
724
+ try {
725
+ const stats = await fs.stat(entryPath);
726
+ sizeInfo = ` (${this.formatFileSize(stats.size)})`;
727
+ } catch (error) {
728
+ // Ignore size errors
729
+ }
730
+ }
731
+
732
+ node.children.push({
733
+ name: entry.name + sizeInfo,
734
+ path: path.relative(basePath, entryPath),
735
+ type: 'file',
736
+ ext
737
+ });
738
+
739
+ this.stats.filesCount++;
740
+ }
741
+ }
742
+
743
+ // Sort children: directories first, then files, both alphabetically
744
+ node.children.sort((a, b) => {
745
+ if (a.type === b.type) {
746
+ return a.name.localeCompare(b.name);
747
+ }
748
+ return a.type === 'directory' ? -1 : 1;
749
+ });
750
+
751
+ return node;
752
+
753
+ } catch (error) {
754
+ this.logger?.error('Error building tree', {
755
+ currentPath,
756
+ error: error.message
757
+ });
758
+ return {
759
+ name: path.basename(currentPath),
760
+ path: path.relative(basePath, currentPath),
761
+ type: 'directory',
762
+ error: error.message,
763
+ children: []
764
+ };
765
+ }
766
+ }
767
+
768
+ /**
769
+ * Format tree as ASCII string
770
+ * @param {Object} node - Tree node
771
+ * @param {string} prefix - Prefix for current line
772
+ * @param {boolean} isLast - Whether this is the last child
773
+ * @param {boolean} isRoot - Whether this is the root node
774
+ * @returns {string} Formatted tree string
775
+ * @private
776
+ */
777
+ formatTree(node, prefix = '', isLast = true, isRoot = true) {
778
+ if (!node) return '';
779
+
780
+ const symbols = this.treeConfig.SYMBOLS;
781
+ let result = '';
782
+
783
+ // Root node special case
784
+ if (isRoot) {
785
+ result = `${node.name}\n`;
786
+ } else {
787
+ result = `${prefix}${isLast ? symbols.LAST_BRANCH : symbols.BRANCH}${node.name}\n`;
788
+ }
789
+
790
+ // Add children
791
+ if (node.children && node.children.length > 0) {
792
+ const childPrefix = isRoot ? '' : prefix + (isLast ? symbols.INDENT : symbols.VERTICAL);
793
+
794
+ node.children.forEach((child, index) => {
795
+ const isLastChild = index === node.children.length - 1;
796
+ result += this.formatTree(child, childPrefix, isLastChild, false);
797
+ });
798
+ }
799
+
800
+ return result;
801
+ }
802
+
803
+ /**
804
+ * Format file size in human-readable format
805
+ * @param {number} bytes - File size in bytes
806
+ * @returns {string} Formatted size
807
+ * @private
808
+ */
809
+ formatFileSize(bytes) {
810
+ const KB = 1024;
811
+ const MB = KB * 1024;
812
+ const GB = MB * 1024;
813
+
814
+ if (bytes >= GB) {
815
+ return `${(bytes / GB).toFixed(2)} GB`;
816
+ } else if (bytes >= MB) {
817
+ return `${(bytes / MB).toFixed(2)} MB`;
818
+ } else if (bytes >= KB) {
819
+ return `${(bytes / KB).toFixed(2)} KB`;
820
+ } else {
821
+ return `${bytes} B`;
822
+ }
823
+ }
824
+
825
+ /**
826
+ * Generate summary text
827
+ * @param {string} directory - Directory scanned
828
+ * @param {Object} stats - Statistics object
829
+ * @param {number} maxDepth - Maximum depth used
830
+ * @returns {string} Summary text
831
+ * @private
832
+ */
833
+ generateSummary(directory, stats, maxDepth) {
834
+ return `
835
+ Directory: ${directory}
836
+ Max Depth: ${maxDepth}
837
+ Files: ${stats.filesCount}
838
+ Directories: ${stats.directoriesCount}
839
+ Skipped: ${stats.skippedCount}
840
+ `.trim();
841
+ }
842
+
843
+ /**
844
+ * Resource cleanup
845
+ * @param {string} operationId - Operation identifier
846
+ */
847
+ async cleanup(operationId) {
848
+ // No persistent resources to clean up
849
+ this.logger?.info('File tree tool cleanup completed', { operationId });
850
+ }
851
+ }
852
+
853
+ export default FileTreeTool;