diff options
Diffstat (limited to 'packages/cli/src/services/CommandService.ts')
| -rw-r--r-- | packages/cli/src/services/CommandService.ts | 133 |
1 files changed, 66 insertions, 67 deletions
diff --git a/packages/cli/src/services/CommandService.ts b/packages/cli/src/services/CommandService.ts index 99eccbf2..ef4f4d14 100644 --- a/packages/cli/src/services/CommandService.ts +++ b/packages/cli/src/services/CommandService.ts @@ -4,81 +4,80 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Config } from '@google/gemini-cli-core'; import { SlashCommand } from '../ui/commands/types.js'; -import { memoryCommand } from '../ui/commands/memoryCommand.js'; -import { helpCommand } from '../ui/commands/helpCommand.js'; -import { clearCommand } from '../ui/commands/clearCommand.js'; -import { copyCommand } from '../ui/commands/copyCommand.js'; -import { corgiCommand } from '../ui/commands/corgiCommand.js'; -import { docsCommand } from '../ui/commands/docsCommand.js'; -import { mcpCommand } from '../ui/commands/mcpCommand.js'; -import { authCommand } from '../ui/commands/authCommand.js'; -import { themeCommand } from '../ui/commands/themeCommand.js'; -import { editorCommand } from '../ui/commands/editorCommand.js'; -import { chatCommand } from '../ui/commands/chatCommand.js'; -import { statsCommand } from '../ui/commands/statsCommand.js'; -import { privacyCommand } from '../ui/commands/privacyCommand.js'; -import { aboutCommand } from '../ui/commands/aboutCommand.js'; -import { extensionsCommand } from '../ui/commands/extensionsCommand.js'; -import { toolsCommand } from '../ui/commands/toolsCommand.js'; -import { compressCommand } from '../ui/commands/compressCommand.js'; -import { ideCommand } from '../ui/commands/ideCommand.js'; -import { bugCommand } from '../ui/commands/bugCommand.js'; -import { quitCommand } from '../ui/commands/quitCommand.js'; -import { restoreCommand } from '../ui/commands/restoreCommand.js'; +import { ICommandLoader } from './types.js'; -const loadBuiltInCommands = async ( - config: Config | null, -): Promise<SlashCommand[]> => { - const allCommands = [ - aboutCommand, - authCommand, - bugCommand, - chatCommand, - clearCommand, - copyCommand, - compressCommand, - corgiCommand, - docsCommand, - editorCommand, - extensionsCommand, - helpCommand, - ideCommand(config), - mcpCommand, - memoryCommand, - privacyCommand, - quitCommand, - restoreCommand(config), - statsCommand, - themeCommand, - toolsCommand, - ]; +/** + * Orchestrates the discovery and loading of all slash commands for the CLI. + * + * This service operates on a provider-based loader pattern. It is initialized + * with an array of `ICommandLoader` instances, each responsible for fetching + * commands from a specific source (e.g., built-in code, local files). + * + * The CommandService is responsible for invoking these loaders, aggregating their + * results, and resolving any name conflicts. This architecture allows the command + * system to be extended with new sources without modifying the service itself. + */ +export class CommandService { + /** + * Private constructor to enforce the use of the async factory. + * @param commands A readonly array of the fully loaded and de-duplicated commands. + */ + private constructor(private readonly commands: readonly SlashCommand[]) {} - return allCommands.filter( - (command): command is SlashCommand => command !== null, - ); -}; + /** + * Asynchronously creates and initializes a new CommandService instance. + * + * This factory method orchestrates the entire command loading process. It + * runs all provided loaders in parallel, aggregates their results, handles + * name conflicts by letting the last-loaded command win, and then returns a + * fully constructed `CommandService` instance. + * + * @param loaders An array of objects that conform to the `ICommandLoader` + * interface. The order of loaders is significant: if multiple loaders + * provide a command with the same name, the command from the loader that + * appears later in the array will take precedence. + * @param signal An AbortSignal to cancel the loading process. + * @returns A promise that resolves to a new, fully initialized `CommandService` instance. + */ + static async create( + loaders: ICommandLoader[], + signal: AbortSignal, + ): Promise<CommandService> { + const results = await Promise.allSettled( + loaders.map((loader) => loader.loadCommands(signal)), + ); -export class CommandService { - private commands: SlashCommand[] = []; + const allCommands: SlashCommand[] = []; + for (const result of results) { + if (result.status === 'fulfilled') { + allCommands.push(...result.value); + } else { + console.debug('A command loader failed:', result.reason); + } + } - constructor( - private config: Config | null, - private commandLoader: ( - config: Config | null, - ) => Promise<SlashCommand[]> = loadBuiltInCommands, - ) { - // The constructor can be used for dependency injection in the future. - } + // De-duplicate commands using a Map. The last one found with a given name wins. + // This creates a natural override system based on the order of the loaders + // passed to the constructor. + const commandMap = new Map<string, SlashCommand>(); + for (const cmd of allCommands) { + commandMap.set(cmd.name, cmd); + } - async loadCommands(): Promise<void> { - // For now, we only load the built-in commands. - // File-based and remote commands will be added later. - this.commands = await this.commandLoader(this.config); + const finalCommands = Object.freeze(Array.from(commandMap.values())); + return new CommandService(finalCommands); } - getCommands(): SlashCommand[] { + /** + * Retrieves the currently loaded and de-duplicated list of slash commands. + * + * This method is a safe accessor for the service's state. It returns a + * readonly array, preventing consumers from modifying the service's internal state. + * + * @returns A readonly, unified array of available `SlashCommand` objects. + */ + getCommands(): readonly SlashCommand[] { return this.commands; } } |
