@@ -15,13 +15,6 @@ function uniqueSortedCommandNames(commands: Iterable<string>): string[] {
|
15 | 15 | ); |
16 | 16 | } |
17 | 17 | |
18 | | -export function getKnownCliCommandNames(): string[] { |
19 | | -return uniqueSortedCommandNames([ |
20 | | - ...getCoreCliCommandNames(), |
21 | | - ...getSubCliEntries().map((entry) => entry.name), |
22 | | -]); |
23 | | -} |
24 | | - |
25 | 18 | export function levenshteinDistance(left: string, right: string): number { |
26 | 19 | if (left === right) { |
27 | 20 | return 0; |
@@ -52,37 +45,40 @@ export function levenshteinDistance(left: string, right: string): number {
|
52 | 45 | return previous[right.length] ?? 0; |
53 | 46 | } |
54 | 47 | |
55 | | -export function suggestCliCommands( |
56 | | -input: string, |
57 | | -candidates: Iterable<string> = getKnownCliCommandNames(), |
58 | | -): string[] { |
| 48 | +export function formatCliCommandSuggestions(input: string): string | undefined { |
59 | 49 | const normalizedInput = input.trim().toLowerCase(); |
60 | 50 | if (!normalizedInput) { |
61 | | -return []; |
| 51 | +return undefined; |
62 | 52 | } |
63 | 53 | |
64 | | -const knownCommands = uniqueSortedCommandNames(candidates); |
| 54 | +const knownCommands = uniqueSortedCommandNames([ |
| 55 | + ...getCoreCliCommandNames(), |
| 56 | + ...getSubCliEntries().map((entry) => entry.name), |
| 57 | +]); |
65 | 58 | const explicitAlias = EXPLICIT_COMMAND_ALIASES.get(normalizedInput); |
66 | 59 | if (explicitAlias && knownCommands.includes(explicitAlias)) { |
67 | | -return [explicitAlias]; |
| 60 | +return formatCliSuggestionLines([explicitAlias]); |
68 | 61 | } |
| 62 | +const suggestions = findCliCommandSuggestions(normalizedInput, knownCommands); |
| 63 | +if (suggestions.length === 0) { |
| 64 | +return undefined; |
| 65 | +} |
| 66 | +return formatCliSuggestionLines(suggestions); |
| 67 | +} |
69 | 68 | |
70 | | -const maxDistance = Math.max(1, Math.floor(normalizedInput.length * 0.4)); |
71 | | -return knownCommands |
72 | | -.map((command) => ({ command, distance: levenshteinDistance(normalizedInput, command) })) |
73 | | -.filter(({ command, distance }) => command !== normalizedInput && distance <= maxDistance) |
| 69 | +function findCliCommandSuggestions(input: string, candidates: readonly string[]): string[] { |
| 70 | +const maxDistance = Math.max(1, Math.floor(input.length * 0.4)); |
| 71 | +return candidates |
| 72 | +.map((command) => ({ command, distance: levenshteinDistance(input, command) })) |
| 73 | +.filter(({ command, distance }) => command !== input && distance <= maxDistance) |
74 | 74 | .toSorted( |
75 | 75 | (left, right) => left.distance - right.distance || left.command.localeCompare(right.command), |
76 | 76 | ) |
77 | 77 | .slice(0, MAX_SUGGESTIONS) |
78 | 78 | .map(({ command }) => command); |
79 | 79 | } |
80 | 80 | |
81 | | -export function formatCliCommandSuggestions(input: string): string | undefined { |
82 | | -const suggestions = suggestCliCommands(input); |
83 | | -if (suggestions.length === 0) { |
84 | | -return undefined; |
85 | | -} |
| 81 | +function formatCliSuggestionLines(suggestions: readonly string[]): string { |
86 | 82 | const commandLines = suggestions |
87 | 83 | .map((command) => ` ${formatCliCommand(`openclaw ${command}`)}`) |
88 | 84 | .join("\n"); |
|