|
1 | | -import { existsSync, readdirSync, readFileSync } from "fs" |
| 1 | +import { existsSync, readdirSync, readFileSync, realpathSync, type Dirent } from "fs" |
2 | 2 | import { join, basename } from "path" |
3 | 3 | import { parseFrontmatter } from "../../shared/frontmatter" |
4 | 4 | import { sanitizeModelField } from "../../shared/model-sanitizer" |
5 | 5 | import { isMarkdownFile } from "../../shared/file-utils" |
6 | 6 | import { getClaudeConfigDir } from "../../shared" |
| 7 | +import { log } from "../../shared/logger" |
7 | 8 | import type { CommandScope, CommandDefinition, CommandFrontmatter, LoadedCommand } from "./types" |
8 | 9 |
|
9 | | -function loadCommandsFromDir(commandsDir: string, scope: CommandScope): LoadedCommand[] { |
| 10 | +function loadCommandsFromDir( |
| 11 | + commandsDir: string, |
| 12 | + scope: CommandScope, |
| 13 | + visited: Set<string> = new Set(), |
| 14 | + prefix: string = "" |
| 15 | +): LoadedCommand[] { |
10 | 16 | if (!existsSync(commandsDir)) { |
11 | 17 | return [] |
12 | 18 | } |
13 | 19 |
|
14 | | - const entries = readdirSync(commandsDir, { withFileTypes: true }) |
| 20 | + let realPath: string |
| 21 | + try { |
| 22 | + realPath = realpathSync(commandsDir) |
| 23 | + } catch (error) { |
| 24 | + log(`Failed to resolve command directory: ${commandsDir}`, error) |
| 25 | + return [] |
| 26 | + } |
| 27 | + |
| 28 | + if (visited.has(realPath)) { |
| 29 | + return [] |
| 30 | + } |
| 31 | + visited.add(realPath) |
| 32 | + |
| 33 | + let entries: Dirent[] |
| 34 | + try { |
| 35 | + entries = readdirSync(commandsDir, { withFileTypes: true }) |
| 36 | + } catch (error) { |
| 37 | + log(`Failed to read command directory: ${commandsDir}`, error) |
| 38 | + return [] |
| 39 | + } |
| 40 | + |
15 | 41 | const commands: LoadedCommand[] = [] |
16 | 42 |
|
17 | 43 | for (const entry of entries) { |
| 44 | + if (entry.isDirectory()) { |
| 45 | + if (entry.name.startsWith(".")) continue |
| 46 | + const subDirPath = join(commandsDir, entry.name) |
| 47 | + const subPrefix = prefix ? `${prefix}:${entry.name}` : entry.name |
| 48 | + commands.push(...loadCommandsFromDir(subDirPath, scope, visited, subPrefix)) |
| 49 | + continue |
| 50 | + } |
| 51 | + |
18 | 52 | if (!isMarkdownFile(entry)) continue |
19 | 53 |
|
20 | 54 | const commandPath = join(commandsDir, entry.name) |
21 | | - const commandName = basename(entry.name, ".md") |
| 55 | + const baseCommandName = basename(entry.name, ".md") |
| 56 | + const commandName = prefix ? `${prefix}:${baseCommandName}` : baseCommandName |
22 | 57 |
|
23 | 58 | try { |
24 | 59 | const content = readFileSync(commandPath, "utf-8") |
@@ -51,7 +86,8 @@ $ARGUMENTS |
51 | 86 | definition, |
52 | 87 | scope, |
53 | 88 | }) |
54 | | - } catch { |
| 89 | + } catch (error) { |
| 90 | + log(`Failed to parse command: ${commandPath}`, error) |
55 | 91 | continue |
56 | 92 | } |
57 | 93 | } |
|
0 commit comments