1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { useCallback } from 'react';
import { type PartListUnion } from '@google/genai';
import { HistoryItem } from '../types.js';
import { getCommandFromQuery } from '../utils/commandUtils.js';
export interface SlashCommand {
name: string; // slash command
description: string; // flavor text in UI
action: (value: PartListUnion) => void;
}
const addHistoryItem = (
setHistory: React.Dispatch<React.SetStateAction<HistoryItem[]>>,
itemData: Omit<HistoryItem, 'id'>,
id: number,
) => {
setHistory((prevHistory) => [
...prevHistory,
{ ...itemData, id } as HistoryItem,
]);
};
export const useSlashCommandProcessor = (
setHistory: React.Dispatch<React.SetStateAction<HistoryItem[]>>,
refreshStatic: () => void,
setDebugMessage: React.Dispatch<React.SetStateAction<string>>,
getNextMessageId: (baseTimestamp: number) => number,
openThemeDialog: () => void,
) => {
const slashCommands: SlashCommand[] = [
{
name: 'help',
description: 'for help on gemini-code',
action: (_value: PartListUnion) => {
const helpText =
'I am an interactive CLI tool assistant designed to ' +
'help with software engineering tasks. I can use tools to read ' +
'and write files, search code, execute bash commands, and more ' +
'to assist with development workflows. I will explain commands ' +
'and ask for permission before running them and will not ' +
'commit changes unless explicitly instructed.';
const timestamp = getNextMessageId(Date.now());
addHistoryItem(setHistory, { type: 'info', text: helpText }, timestamp);
},
},
{
name: 'clear',
description: 'clear the screen',
action: (_value: PartListUnion) => {
// This just clears the *UI* history, not the model history.
setDebugMessage('Clearing terminal.');
setHistory((_) => []);
refreshStatic();
},
},
{
name: 'theme',
description: 'change the theme',
action: (_value: PartListUnion) => {
openThemeDialog();
},
},
{
name: 'exit',
description: '',
action: (_value: PartListUnion) => {
setDebugMessage('Exiting. Good-bye.');
const timestamp = getNextMessageId(Date.now());
addHistoryItem(
setHistory,
{ type: 'info', text: 'good-bye!' },
timestamp,
);
process.exit(0);
},
},
{
// TODO: dedup with exit by adding altName or cmdRegex.
name: 'quit',
description: '',
action: (_value: PartListUnion) => {
setDebugMessage('Quitting. Good-bye.');
const timestamp = getNextMessageId(Date.now());
addHistoryItem(
setHistory,
{ type: 'info', text: 'good-bye!' },
timestamp,
);
process.exit(0);
},
},
];
// Checks if the query is a slash command and executes the command if it is.
const handleSlashCommand = useCallback(
(rawQuery: PartListUnion): boolean => {
if (typeof rawQuery !== 'string') {
return false;
}
const trimmed = rawQuery.trim();
const [symbol, test] = getCommandFromQuery(trimmed);
// Skip non slash commands
if (symbol !== '/') {
return false;
}
for (const cmd of slashCommands) {
if (test === cmd.name) {
// Add user message *before* execution
const userMessageTimestamp = Date.now();
addHistoryItem(
setHistory,
{ type: 'user', text: trimmed },
userMessageTimestamp,
);
cmd.action(trimmed);
return true; // Command was handled
}
}
return false; // Not a recognized slash command
},
[setDebugMessage, setHistory, getNextMessageId, slashCommands],
);
return { handleSlashCommand, slashCommands };
};
|