summaryrefslogtreecommitdiff
path: root/packages/cli/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src')
-rw-r--r--packages/cli/src/ui/hooks/slashCommandProcessor.test.ts94
-rw-r--r--packages/cli/src/ui/hooks/slashCommandProcessor.ts15
2 files changed, 109 insertions, 0 deletions
diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts
index 42c2e277..30a14815 100644
--- a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts
+++ b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts
@@ -4,6 +4,21 @@
* SPDX-License-Identifier: Apache-2.0
*/
+const { logSlashCommand, SlashCommandEvent } = vi.hoisted(() => ({
+ logSlashCommand: vi.fn(),
+ SlashCommandEvent: vi.fn((command, subCommand) => ({ command, subCommand })),
+}));
+
+vi.mock('@google/gemini-cli-core', async (importOriginal) => {
+ const original =
+ await importOriginal<typeof import('@google/gemini-cli-core')>();
+ return {
+ ...original,
+ logSlashCommand,
+ SlashCommandEvent,
+ };
+});
+
const { mockProcessExit } = vi.hoisted(() => ({
mockProcessExit: vi.fn((_code?: number): never => undefined as never),
}));
@@ -814,4 +829,83 @@ describe('useSlashCommandProcessor', () => {
expect(abortSpy).toHaveBeenCalledTimes(1);
});
});
+
+ describe('Slash Command Logging', () => {
+ const mockCommandAction = vi.fn().mockResolvedValue({ type: 'handled' });
+ const loggingTestCommands: SlashCommand[] = [
+ createTestCommand({
+ name: 'logtest',
+ action: mockCommandAction,
+ }),
+ createTestCommand({
+ name: 'logwithsub',
+ subCommands: [
+ createTestCommand({
+ name: 'sub',
+ action: mockCommandAction,
+ }),
+ ],
+ }),
+ createTestCommand({
+ name: 'logalias',
+ altNames: ['la'],
+ action: mockCommandAction,
+ }),
+ ];
+
+ beforeEach(() => {
+ mockCommandAction.mockClear();
+ vi.mocked(logSlashCommand).mockClear();
+ vi.mocked(SlashCommandEvent).mockClear();
+ });
+
+ it('should log a simple slash command', async () => {
+ const result = setupProcessorHook(loggingTestCommands);
+ await waitFor(() =>
+ expect(result.current.slashCommands.length).toBeGreaterThan(0),
+ );
+ await act(async () => {
+ await result.current.handleSlashCommand('/logtest');
+ });
+
+ expect(logSlashCommand).toHaveBeenCalledTimes(1);
+ expect(SlashCommandEvent).toHaveBeenCalledWith('logtest', undefined);
+ });
+
+ it('should log a slash command with a subcommand', async () => {
+ const result = setupProcessorHook(loggingTestCommands);
+ await waitFor(() =>
+ expect(result.current.slashCommands.length).toBeGreaterThan(0),
+ );
+ await act(async () => {
+ await result.current.handleSlashCommand('/logwithsub sub');
+ });
+
+ expect(logSlashCommand).toHaveBeenCalledTimes(1);
+ expect(SlashCommandEvent).toHaveBeenCalledWith('logwithsub', 'sub');
+ });
+
+ it('should log the command path when an alias is used', async () => {
+ const result = setupProcessorHook(loggingTestCommands);
+ await waitFor(() =>
+ expect(result.current.slashCommands.length).toBeGreaterThan(0),
+ );
+ await act(async () => {
+ await result.current.handleSlashCommand('/la');
+ });
+ expect(logSlashCommand).toHaveBeenCalledTimes(1);
+ expect(SlashCommandEvent).toHaveBeenCalledWith('logalias', undefined);
+ });
+
+ it('should not log for unknown commands', async () => {
+ const result = setupProcessorHook(loggingTestCommands);
+ await waitFor(() =>
+ expect(result.current.slashCommands.length).toBeGreaterThan(0),
+ );
+ await act(async () => {
+ await result.current.handleSlashCommand('/unknown');
+ });
+ expect(logSlashCommand).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.ts
index be32de11..e315ba97 100644
--- a/packages/cli/src/ui/hooks/slashCommandProcessor.ts
+++ b/packages/cli/src/ui/hooks/slashCommandProcessor.ts
@@ -13,6 +13,8 @@ import {
Config,
GitService,
Logger,
+ logSlashCommand,
+ SlashCommandEvent,
ToolConfirmationOutcome,
} from '@google/gemini-cli-core';
import { useSessionStats } from '../contexts/SessionContext.js';
@@ -233,6 +235,7 @@ export const useSlashCommandProcessor = (
let currentCommands = commands;
let commandToExecute: SlashCommand | undefined;
let pathIndex = 0;
+ const canonicalPath: string[] = [];
for (const part of commandPath) {
// TODO: For better performance and architectural clarity, this two-pass
@@ -253,6 +256,7 @@ export const useSlashCommandProcessor = (
if (foundCommand) {
commandToExecute = foundCommand;
+ canonicalPath.push(foundCommand.name);
pathIndex++;
if (foundCommand.subCommands) {
currentCommands = foundCommand.subCommands;
@@ -268,6 +272,17 @@ export const useSlashCommandProcessor = (
const args = parts.slice(pathIndex).join(' ');
if (commandToExecute.action) {
+ if (config) {
+ const resolvedCommandPath = canonicalPath;
+ const event = new SlashCommandEvent(
+ resolvedCommandPath[0],
+ resolvedCommandPath.length > 1
+ ? resolvedCommandPath.slice(1).join(' ')
+ : undefined,
+ );
+ logSlashCommand(config, event);
+ }
+
const fullCommandContext: CommandContext = {
...commandContext,
invocation: {