summaryrefslogtreecommitdiff
path: root/packages/cli/src/services/CommandService.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src/services/CommandService.ts')
-rw-r--r--packages/cli/src/services/CommandService.ts133
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;
}
}