faf_list
List directories and discover projects containing project.faf files to facilitate FAF discovery workflows.
Instructions
List directories and discover projects with project.faf files - Essential for FAF discovery workflow
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Directory path to list (e.g., ~/Projects, /Users/username/Projects) | |
| filter | No | Filter: "faf" (only dirs with project.faf), "dirs" (all directories), "all" (dirs and files). Default: "dirs" | |
| depth | No | Directory depth to scan: 1 (immediate children) or 2 (one level deeper). Default: 1 | |
| showHidden | No | Show hidden files/directories (starting with .). Default: false |
Implementation Reference
- src/handlers/tools.ts:1090-1208 (handler)The handleFafList method implements the logic for listing directories and identifying 'project.faf' files. It scans the provided path recursively up to a specified depth, filters results, and formats the output.
private async handleFafList(args: any): Promise<CallToolResult> { try { const fs = await import('fs'); const path = await import('path'); // Parse arguments const targetPath = args?.path || this.engineAdapter.getWorkingDirectory(); const filter = args?.filter || 'dirs'; const depth = args?.depth || 1; const showHidden = args?.showHidden || false; // Expand tilde const expandedPath = targetPath.startsWith('~') ? path.join(require('os').homedir(), targetPath.slice(1)) : targetPath; const resolvedPath = path.resolve(expandedPath); // Check if directory exists if (!fs.existsSync(resolvedPath)) { return { content: [{ type: 'text', text: `❌ Directory not found: ${resolvedPath}` }], isError: true }; } // Check if it's actually a directory const stats = fs.statSync(resolvedPath); if (!stats.isDirectory()) { return { content: [{ type: 'text', text: `❌ Not a directory: ${resolvedPath}` }], isError: true }; } // Scan directory const results: Array<{name: string; path: string; hasFaf: boolean; isDir: boolean}> = []; const scanDir = (dirPath: string, currentDepth: number) => { if (currentDepth > depth) return; const entries = fs.readdirSync(dirPath); for (const entry of entries) { // Skip hidden files unless requested if (!showHidden && entry.startsWith('.')) continue; const fullPath = path.join(dirPath, entry); const entryStats = fs.statSync(fullPath); const isDir = entryStats.isDirectory(); // Check for project.faf const hasFaf = isDir && fs.existsSync(path.join(fullPath, 'project.faf')); // Apply filter if (filter === 'faf' && !hasFaf) continue; if (filter === 'dirs' && !isDir) continue; results.push({ name: entry, path: fullPath, hasFaf, isDir }); // Recurse if needed if (isDir && currentDepth < depth) { scanDir(fullPath, currentDepth + 1); } } }; scanDir(resolvedPath, 1); // Sort: FAF projects first, then alphabetically results.sort((a, b) => { if (a.hasFaf && !b.hasFaf) return -1; if (!a.hasFaf && b.hasFaf) return 1; return a.name.localeCompare(b.name); }); // Format output let output = `📁 ${resolvedPath}\n\n`; if (results.length === 0) { output += '(empty)\n'; } else { for (const item of results) { const indent = item.path.split('/').length - resolvedPath.split('/').length - 1; const prefix = ' '.repeat(indent); const icon = item.isDir ? '📁' : '📄'; const status = item.hasFaf ? '✅ project.faf' : ''; output += `${prefix}${icon} ${item.name}`; if (status) output += ` ${status}`; output += '\n'; } } output += `\nTotal: ${results.length} items`; if (filter === 'faf') { const fafCount = results.filter(r => r.hasFaf).length; output += ` (${fafCount} with project.faf)`; } return { content: [{ type: 'text', text: output }] }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : 'Unknown error';