@google/gemini-cli 0.1.12 → 0.1.13-nightly.250726.fb751c54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/README.md +30 -3
  2. package/dist/google-gemini-cli-0.1.13.tgz +0 -0
  3. package/dist/package.json +6 -4
  4. package/dist/src/acp/acp.d.ts +208 -0
  5. package/dist/src/acp/acp.js +193 -0
  6. package/dist/src/acp/acp.js.map +1 -0
  7. package/dist/src/acp/acpPeer.d.ts +8 -0
  8. package/dist/src/acp/acpPeer.js +537 -0
  9. package/dist/src/acp/acpPeer.js.map +1 -0
  10. package/dist/src/config/config.d.ts +6 -2
  11. package/dist/src/config/config.js +77 -13
  12. package/dist/src/config/config.js.map +1 -1
  13. package/dist/src/config/extension.d.ts +2 -2
  14. package/dist/src/config/extension.js +21 -16
  15. package/dist/src/config/extension.js.map +1 -1
  16. package/dist/src/config/settings.d.ts +15 -2
  17. package/dist/src/config/settings.js +25 -10
  18. package/dist/src/config/settings.js.map +1 -1
  19. package/dist/src/gemini.js +10 -20
  20. package/dist/src/gemini.js.map +1 -1
  21. package/dist/src/generated/git-commit.d.ts +1 -1
  22. package/dist/src/generated/git-commit.js +1 -1
  23. package/dist/src/patches/is-in-ci.d.ts +7 -0
  24. package/dist/src/patches/is-in-ci.js +15 -0
  25. package/dist/src/patches/is-in-ci.js.map +1 -0
  26. package/dist/src/services/BuiltinCommandLoader.d.ts +24 -0
  27. package/dist/src/services/BuiltinCommandLoader.js +72 -0
  28. package/dist/src/services/BuiltinCommandLoader.js.map +1 -0
  29. package/dist/src/services/CommandService.d.ts +43 -5
  30. package/dist/src/services/CommandService.js +61 -19
  31. package/dist/src/services/CommandService.js.map +1 -1
  32. package/dist/src/services/FileCommandLoader.d.ts +37 -0
  33. package/dist/src/services/FileCommandLoader.js +156 -0
  34. package/dist/src/services/FileCommandLoader.js.map +1 -0
  35. package/dist/src/services/McpPromptLoader.d.ts +25 -0
  36. package/dist/src/services/McpPromptLoader.js +192 -0
  37. package/dist/src/services/McpPromptLoader.js.map +1 -0
  38. package/dist/src/services/prompt-processors/argumentProcessor.d.ts +21 -0
  39. package/dist/src/services/prompt-processors/argumentProcessor.js +28 -0
  40. package/dist/src/services/prompt-processors/argumentProcessor.js.map +1 -0
  41. package/dist/src/services/prompt-processors/types.d.ts +34 -0
  42. package/dist/src/services/prompt-processors/types.js +10 -0
  43. package/dist/src/services/prompt-processors/types.js.map +1 -0
  44. package/dist/src/services/types.d.ts +22 -0
  45. package/dist/src/services/types.js +7 -0
  46. package/dist/src/services/types.js.map +1 -0
  47. package/dist/src/ui/App.js +103 -98
  48. package/dist/src/ui/App.js.map +1 -1
  49. package/dist/src/ui/colors.js +6 -0
  50. package/dist/src/ui/colors.js.map +1 -1
  51. package/dist/src/ui/commands/aboutCommand.d.ts +7 -0
  52. package/dist/src/ui/commands/aboutCommand.js +39 -0
  53. package/dist/src/ui/commands/aboutCommand.js.map +1 -0
  54. package/dist/src/ui/commands/authCommand.d.ts +7 -0
  55. package/dist/src/ui/commands/authCommand.js +16 -0
  56. package/dist/src/ui/commands/authCommand.js.map +1 -0
  57. package/dist/src/ui/commands/bugCommand.d.ts +7 -0
  58. package/dist/src/ui/commands/bugCommand.js +63 -0
  59. package/dist/src/ui/commands/bugCommand.js.map +1 -0
  60. package/dist/src/ui/commands/chatCommand.d.ts +7 -0
  61. package/dist/src/ui/commands/chatCommand.js +179 -0
  62. package/dist/src/ui/commands/chatCommand.js.map +1 -0
  63. package/dist/src/ui/commands/clearCommand.js +14 -2
  64. package/dist/src/ui/commands/clearCommand.js.map +1 -1
  65. package/dist/src/ui/commands/compressCommand.d.ts +7 -0
  66. package/dist/src/ui/commands/compressCommand.js +64 -0
  67. package/dist/src/ui/commands/compressCommand.js.map +1 -0
  68. package/dist/src/ui/commands/copyCommand.d.ts +7 -0
  69. package/dist/src/ui/commands/copyCommand.js +59 -0
  70. package/dist/src/ui/commands/copyCommand.js.map +1 -0
  71. package/dist/src/ui/commands/corgiCommand.d.ts +7 -0
  72. package/dist/src/ui/commands/corgiCommand.js +15 -0
  73. package/dist/src/ui/commands/corgiCommand.js.map +1 -0
  74. package/dist/src/ui/commands/docsCommand.d.ts +7 -0
  75. package/dist/src/ui/commands/docsCommand.js +31 -0
  76. package/dist/src/ui/commands/docsCommand.js.map +1 -0
  77. package/dist/src/ui/commands/editorCommand.d.ts +7 -0
  78. package/dist/src/ui/commands/editorCommand.js +16 -0
  79. package/dist/src/ui/commands/editorCommand.js.map +1 -0
  80. package/dist/src/ui/commands/extensionsCommand.d.ts +7 -0
  81. package/dist/src/ui/commands/extensionsCommand.js +31 -0
  82. package/dist/src/ui/commands/extensionsCommand.js.map +1 -0
  83. package/dist/src/ui/commands/helpCommand.js +3 -1
  84. package/dist/src/ui/commands/helpCommand.js.map +1 -1
  85. package/dist/src/ui/commands/ideCommand.d.ts +8 -0
  86. package/dist/src/ui/commands/ideCommand.js +121 -0
  87. package/dist/src/ui/commands/ideCommand.js.map +1 -0
  88. package/dist/src/ui/commands/mcpCommand.d.ts +7 -0
  89. package/dist/src/ui/commands/mcpCommand.js +425 -0
  90. package/dist/src/ui/commands/mcpCommand.js.map +1 -0
  91. package/dist/src/ui/commands/memoryCommand.js +11 -4
  92. package/dist/src/ui/commands/memoryCommand.js.map +1 -1
  93. package/dist/src/ui/commands/privacyCommand.d.ts +7 -0
  94. package/dist/src/ui/commands/privacyCommand.js +16 -0
  95. package/dist/src/ui/commands/privacyCommand.js.map +1 -0
  96. package/dist/src/ui/commands/quitCommand.d.ts +7 -0
  97. package/dist/src/ui/commands/quitCommand.js +34 -0
  98. package/dist/src/ui/commands/quitCommand.js.map +1 -0
  99. package/dist/src/ui/commands/restoreCommand.d.ts +8 -0
  100. package/dist/src/ui/commands/restoreCommand.js +128 -0
  101. package/dist/src/ui/commands/restoreCommand.js.map +1 -0
  102. package/dist/src/ui/commands/statsCommand.d.ts +7 -0
  103. package/dist/src/ui/commands/statsCommand.js +54 -0
  104. package/dist/src/ui/commands/statsCommand.js.map +1 -0
  105. package/dist/src/ui/commands/themeCommand.js +2 -0
  106. package/dist/src/ui/commands/themeCommand.js.map +1 -1
  107. package/dist/src/ui/commands/toolsCommand.d.ts +7 -0
  108. package/dist/src/ui/commands/toolsCommand.js +56 -0
  109. package/dist/src/ui/commands/toolsCommand.js.map +1 -0
  110. package/dist/src/ui/commands/types.d.ts +61 -4
  111. package/dist/src/ui/commands/types.js +6 -1
  112. package/dist/src/ui/commands/types.js.map +1 -1
  113. package/dist/src/ui/commands/vimCommand.d.ts +7 -0
  114. package/dist/src/ui/commands/vimCommand.js +23 -0
  115. package/dist/src/ui/commands/vimCommand.js.map +1 -0
  116. package/dist/src/ui/components/ContextSummaryDisplay.d.ts +6 -1
  117. package/dist/src/ui/components/ContextSummaryDisplay.js +46 -19
  118. package/dist/src/ui/components/ContextSummaryDisplay.js.map +1 -1
  119. package/dist/src/ui/components/Footer.d.ts +1 -0
  120. package/dist/src/ui/components/Footer.js +2 -2
  121. package/dist/src/ui/components/Footer.js.map +1 -1
  122. package/dist/src/ui/components/Help.d.ts +1 -1
  123. package/dist/src/ui/components/IDEContextDetailDisplay.d.ts +11 -0
  124. package/dist/src/ui/components/IDEContextDetailDisplay.js +19 -0
  125. package/dist/src/ui/components/IDEContextDetailDisplay.js.map +1 -0
  126. package/dist/src/ui/components/InputPrompt.d.ts +3 -1
  127. package/dist/src/ui/components/InputPrompt.js +85 -113
  128. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  129. package/dist/src/ui/components/ThemeDialog.js +51 -25
  130. package/dist/src/ui/components/ThemeDialog.js.map +1 -1
  131. package/dist/src/ui/components/messages/DiffRenderer.d.ts +1 -0
  132. package/dist/src/ui/components/messages/DiffRenderer.js +12 -11
  133. package/dist/src/ui/components/messages/DiffRenderer.js.map +1 -1
  134. package/dist/src/ui/components/messages/ToolConfirmationMessage.js +5 -4
  135. package/dist/src/ui/components/messages/ToolConfirmationMessage.js.map +1 -1
  136. package/dist/src/ui/components/messages/ToolGroupMessage.js +3 -1
  137. package/dist/src/ui/components/messages/ToolGroupMessage.js.map +1 -1
  138. package/dist/src/ui/components/shared/MaxSizedBox.js +69 -2
  139. package/dist/src/ui/components/shared/MaxSizedBox.js.map +1 -1
  140. package/dist/src/ui/components/shared/RadioButtonSelect.d.ts +3 -1
  141. package/dist/src/ui/components/shared/RadioButtonSelect.js +60 -10
  142. package/dist/src/ui/components/shared/RadioButtonSelect.js.map +1 -1
  143. package/dist/src/ui/components/shared/text-buffer.d.ts +273 -3
  144. package/dist/src/ui/components/shared/text-buffer.js +426 -80
  145. package/dist/src/ui/components/shared/text-buffer.js.map +1 -1
  146. package/dist/src/ui/components/shared/vim-buffer-actions.d.ts +72 -0
  147. package/dist/src/ui/components/shared/vim-buffer-actions.js +565 -0
  148. package/dist/src/ui/components/shared/vim-buffer-actions.js.map +1 -0
  149. package/dist/src/ui/constants.d.ts +1 -0
  150. package/dist/src/ui/constants.js +1 -0
  151. package/dist/src/ui/constants.js.map +1 -1
  152. package/dist/src/ui/contexts/VimModeContext.d.ts +19 -0
  153. package/dist/src/ui/contexts/VimModeContext.js +48 -0
  154. package/dist/src/ui/contexts/VimModeContext.js.map +1 -0
  155. package/dist/src/ui/hooks/atCommandProcessor.js +54 -15
  156. package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
  157. package/dist/src/ui/hooks/shellCommandProcessor.d.ts +1 -1
  158. package/dist/src/ui/hooks/shellCommandProcessor.js +57 -19
  159. package/dist/src/ui/hooks/shellCommandProcessor.js.map +1 -1
  160. package/dist/src/ui/hooks/slashCommandProcessor.d.ts +3 -10
  161. package/dist/src/ui/hooks/slashCommandProcessor.js +121 -879
  162. package/dist/src/ui/hooks/slashCommandProcessor.js.map +1 -1
  163. package/dist/src/ui/hooks/useAuthCommand.js +2 -1
  164. package/dist/src/ui/hooks/useAuthCommand.js.map +1 -1
  165. package/dist/src/ui/hooks/useCompletion.d.ts +4 -1
  166. package/dist/src/ui/hooks/useCompletion.js +141 -25
  167. package/dist/src/ui/hooks/useCompletion.js.map +1 -1
  168. package/dist/src/ui/hooks/useFocus.d.ts +6 -0
  169. package/dist/src/ui/hooks/useFocus.js +41 -0
  170. package/dist/src/ui/hooks/useFocus.js.map +1 -0
  171. package/dist/src/ui/hooks/useGeminiStream.js +74 -11
  172. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  173. package/dist/src/ui/hooks/useInputHistory.d.ts +1 -1
  174. package/dist/src/ui/hooks/useKeypress.js +5 -2
  175. package/dist/src/ui/hooks/useKeypress.js.map +1 -1
  176. package/dist/src/ui/hooks/usePrivacySettings.js +5 -5
  177. package/dist/src/ui/hooks/usePrivacySettings.js.map +1 -1
  178. package/dist/src/ui/hooks/useShellHistory.d.ts +3 -2
  179. package/dist/src/ui/hooks/useShellHistory.js.map +1 -1
  180. package/dist/src/ui/hooks/useThemeCommand.js +24 -23
  181. package/dist/src/ui/hooks/useThemeCommand.js.map +1 -1
  182. package/dist/src/ui/hooks/vim.d.ts +28 -0
  183. package/dist/src/ui/hooks/vim.js +630 -0
  184. package/dist/src/ui/hooks/vim.js.map +1 -0
  185. package/dist/src/ui/themes/ansi-light.js +3 -1
  186. package/dist/src/ui/themes/ansi-light.js.map +1 -1
  187. package/dist/src/ui/themes/ansi.js +2 -0
  188. package/dist/src/ui/themes/ansi.js.map +1 -1
  189. package/dist/src/ui/themes/atom-one-dark.js +2 -0
  190. package/dist/src/ui/themes/atom-one-dark.js.map +1 -1
  191. package/dist/src/ui/themes/ayu-light.js +3 -1
  192. package/dist/src/ui/themes/ayu-light.js.map +1 -1
  193. package/dist/src/ui/themes/ayu.js +3 -1
  194. package/dist/src/ui/themes/ayu.js.map +1 -1
  195. package/dist/src/ui/themes/color-utils.d.ts +21 -0
  196. package/dist/src/ui/themes/color-utils.js +221 -0
  197. package/dist/src/ui/themes/color-utils.js.map +1 -0
  198. package/dist/src/ui/themes/dracula.js +2 -0
  199. package/dist/src/ui/themes/dracula.js.map +1 -1
  200. package/dist/src/ui/themes/github-dark.js +2 -0
  201. package/dist/src/ui/themes/github-dark.js.map +1 -1
  202. package/dist/src/ui/themes/github-light.js +2 -0
  203. package/dist/src/ui/themes/github-light.js.map +1 -1
  204. package/dist/src/ui/themes/googlecode.js +3 -1
  205. package/dist/src/ui/themes/googlecode.js.map +1 -1
  206. package/dist/src/ui/themes/no-color.js +3 -1
  207. package/dist/src/ui/themes/no-color.js.map +1 -1
  208. package/dist/src/ui/themes/shades-of-purple.d.ts +1 -1
  209. package/dist/src/ui/themes/shades-of-purple.js +3 -1
  210. package/dist/src/ui/themes/shades-of-purple.js.map +1 -1
  211. package/dist/src/ui/themes/theme-manager.d.ts +31 -6
  212. package/dist/src/ui/themes/theme-manager.js +104 -34
  213. package/dist/src/ui/themes/theme-manager.js.map +1 -1
  214. package/dist/src/ui/themes/theme.d.ts +22 -3
  215. package/dist/src/ui/themes/theme.js +229 -182
  216. package/dist/src/ui/themes/theme.js.map +1 -1
  217. package/dist/src/ui/themes/xcode.js +3 -1
  218. package/dist/src/ui/themes/xcode.js.map +1 -1
  219. package/dist/src/ui/types.d.ts +9 -1
  220. package/dist/src/ui/utils/CodeColorizer.d.ts +3 -1
  221. package/dist/src/ui/utils/CodeColorizer.js +23 -11
  222. package/dist/src/ui/utils/CodeColorizer.js.map +1 -1
  223. package/dist/src/ui/utils/commandUtils.d.ts +1 -0
  224. package/dist/src/ui/utils/commandUtils.js +47 -0
  225. package/dist/src/ui/utils/commandUtils.js.map +1 -1
  226. package/dist/src/ui/utils/markdownUtilities.js +1 -1
  227. package/dist/src/ui/utils/updateCheck.js +4 -0
  228. package/dist/src/ui/utils/updateCheck.js.map +1 -1
  229. package/dist/src/utils/sandbox.js +7 -3
  230. package/dist/src/utils/sandbox.js.map +1 -1
  231. package/dist/src/utils/userStartupWarnings.js +22 -1
  232. package/dist/src/utils/userStartupWarnings.js.map +1 -1
  233. package/dist/src/validateNonInterActiveAuth.d.ts +7 -0
  234. package/dist/src/validateNonInterActiveAuth.js +35 -0
  235. package/dist/src/validateNonInterActiveAuth.js.map +1 -0
  236. package/dist/tsconfig.tsbuildinfo +1 -1
  237. package/package.json +7 -5
  238. package/dist/google-gemini-cli-0.1.11.tgz +0 -0
@@ -4,22 +4,19 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import { useCallback, useMemo, useEffect, useState } from 'react';
7
- import open from 'open';
8
7
  import process from 'node:process';
9
8
  import { useStateAndRef } from './useStateAndRef.js';
10
- import { GitService, Logger, MCPDiscoveryState, MCPServerStatus, getMCPDiscoveryState, getMCPServerStatus, } from '@google/gemini-cli-core';
9
+ import { GitService, Logger } from '@google/gemini-cli-core';
11
10
  import { useSessionStats } from '../contexts/SessionContext.js';
12
11
  import { MessageType, } from '../types.js';
13
- import { promises as fs } from 'fs';
14
- import path from 'path';
15
- import { GIT_COMMIT_INFO } from '../../generated/git-commit.js';
16
- import { formatDuration, formatMemoryUsage } from '../utils/formatters.js';
17
- import { getCliVersion } from '../../utils/version.js';
18
12
  import { CommandService } from '../../services/CommandService.js';
13
+ import { BuiltinCommandLoader } from '../../services/BuiltinCommandLoader.js';
14
+ import { FileCommandLoader } from '../../services/FileCommandLoader.js';
15
+ import { McpPromptLoader } from '../../services/McpPromptLoader.js';
19
16
  /**
20
17
  * Hook to define and process slash commands (e.g., /help, /clear).
21
18
  */
22
- export const useSlashCommandProcessor = (config, settings, history, addItem, clearItems, loadHistory, refreshStatic, setShowHelp, onDebugMessage, openThemeDialog, openAuthDialog, openEditorDialog, toggleCorgiMode, showToolDescriptions = false, setQuittingMessages, openPrivacyNotice) => {
19
+ export const useSlashCommandProcessor = (config, settings, addItem, clearItems, loadHistory, refreshStatic, setShowHelp, onDebugMessage, openThemeDialog, openAuthDialog, openEditorDialog, toggleCorgiMode, setQuittingMessages, openPrivacyNotice, toggleVimEnabled) => {
23
20
  const session = useSessionStats();
24
21
  const [commands, setCommands] = useState([]);
25
22
  const gitService = useMemo(() => {
@@ -106,7 +103,12 @@ export const useSlashCommandProcessor = (config, settings, history, addItem, cle
106
103
  console.clear();
107
104
  refreshStatic();
108
105
  },
106
+ loadHistory,
109
107
  setDebugMessage: onDebugMessage,
108
+ pendingItem: pendingCompressionItemRef.current,
109
+ setPendingItem: setPendingCompressionItem,
110
+ toggleCorgiMode,
111
+ toggleVimEnabled,
110
112
  },
111
113
  session: {
112
114
  stats: session.stats,
@@ -116,805 +118,33 @@ export const useSlashCommandProcessor = (config, settings, history, addItem, cle
116
118
  settings,
117
119
  gitService,
118
120
  logger,
121
+ loadHistory,
119
122
  addItem,
120
123
  clearItems,
121
124
  refreshStatic,
122
125
  session.stats,
123
126
  onDebugMessage,
127
+ pendingCompressionItemRef,
128
+ setPendingCompressionItem,
129
+ toggleCorgiMode,
130
+ toggleVimEnabled,
124
131
  ]);
125
- const commandService = useMemo(() => new CommandService(), []);
126
132
  useEffect(() => {
133
+ const controller = new AbortController();
127
134
  const load = async () => {
128
- await commandService.loadCommands();
135
+ const loaders = [
136
+ new McpPromptLoader(config),
137
+ new BuiltinCommandLoader(config),
138
+ new FileCommandLoader(config),
139
+ ];
140
+ const commandService = await CommandService.create(loaders, controller.signal);
129
141
  setCommands(commandService.getCommands());
130
142
  };
131
143
  load();
132
- }, [commandService]);
133
- const savedChatTags = useCallback(async () => {
134
- const geminiDir = config?.getProjectTempDir();
135
- if (!geminiDir) {
136
- return [];
137
- }
138
- try {
139
- const files = await fs.readdir(geminiDir);
140
- return files
141
- .filter((file) => file.startsWith('checkpoint-') && file.endsWith('.json'))
142
- .map((file) => file.replace('checkpoint-', '').replace('.json', ''));
143
- }
144
- catch (_err) {
145
- return [];
146
- }
144
+ return () => {
145
+ controller.abort();
146
+ };
147
147
  }, [config]);
148
- // Define legacy commands
149
- // This list contains all commands that have NOT YET been migrated to the
150
- // new system. As commands are migrated, they are removed from this list.
151
- const legacyCommands = useMemo(() => {
152
- const commands = [
153
- // `/help` and `/clear` have been migrated and REMOVED from this list.
154
- {
155
- name: 'docs',
156
- description: 'open full Gemini CLI documentation in your browser',
157
- action: async (_mainCommand, _subCommand, _args) => {
158
- const docsUrl = 'https://goo.gle/gemini-cli-docs';
159
- if (process.env.SANDBOX && process.env.SANDBOX !== 'sandbox-exec') {
160
- addMessage({
161
- type: MessageType.INFO,
162
- content: `Please open the following URL in your browser to view the documentation:\n${docsUrl}`,
163
- timestamp: new Date(),
164
- });
165
- }
166
- else {
167
- addMessage({
168
- type: MessageType.INFO,
169
- content: `Opening documentation in your browser: ${docsUrl}`,
170
- timestamp: new Date(),
171
- });
172
- await open(docsUrl);
173
- }
174
- },
175
- },
176
- {
177
- name: 'auth',
178
- description: 'change the auth method',
179
- action: (_mainCommand, _subCommand, _args) => openAuthDialog(),
180
- },
181
- {
182
- name: 'editor',
183
- description: 'set external editor preference',
184
- action: (_mainCommand, _subCommand, _args) => openEditorDialog(),
185
- },
186
- {
187
- name: 'privacy',
188
- description: 'display the privacy notice',
189
- action: (_mainCommand, _subCommand, _args) => openPrivacyNotice(),
190
- },
191
- {
192
- name: 'stats',
193
- altName: 'usage',
194
- description: 'check session stats. Usage: /stats [model|tools]',
195
- action: (_mainCommand, subCommand, _args) => {
196
- if (subCommand === 'model') {
197
- addMessage({
198
- type: MessageType.MODEL_STATS,
199
- timestamp: new Date(),
200
- });
201
- return;
202
- }
203
- else if (subCommand === 'tools') {
204
- addMessage({
205
- type: MessageType.TOOL_STATS,
206
- timestamp: new Date(),
207
- });
208
- return;
209
- }
210
- const now = new Date();
211
- const { sessionStartTime } = session.stats;
212
- const wallDuration = now.getTime() - sessionStartTime.getTime();
213
- addMessage({
214
- type: MessageType.STATS,
215
- duration: formatDuration(wallDuration),
216
- timestamp: new Date(),
217
- });
218
- },
219
- },
220
- {
221
- name: 'mcp',
222
- description: 'list configured MCP servers and tools',
223
- action: async (_mainCommand, _subCommand, _args) => {
224
- // Check if the _subCommand includes a specific flag to control description visibility
225
- let useShowDescriptions = showToolDescriptions;
226
- if (_subCommand === 'desc' || _subCommand === 'descriptions') {
227
- useShowDescriptions = true;
228
- }
229
- else if (_subCommand === 'nodesc' ||
230
- _subCommand === 'nodescriptions') {
231
- useShowDescriptions = false;
232
- }
233
- else if (_args === 'desc' || _args === 'descriptions') {
234
- useShowDescriptions = true;
235
- }
236
- else if (_args === 'nodesc' || _args === 'nodescriptions') {
237
- useShowDescriptions = false;
238
- }
239
- // Check if the _subCommand includes a specific flag to show detailed tool schema
240
- let useShowSchema = false;
241
- if (_subCommand === 'schema' || _args === 'schema') {
242
- useShowSchema = true;
243
- }
244
- const toolRegistry = await config?.getToolRegistry();
245
- if (!toolRegistry) {
246
- addMessage({
247
- type: MessageType.ERROR,
248
- content: 'Could not retrieve tool registry.',
249
- timestamp: new Date(),
250
- });
251
- return;
252
- }
253
- const mcpServers = config?.getMcpServers() || {};
254
- const serverNames = Object.keys(mcpServers);
255
- if (serverNames.length === 0) {
256
- const docsUrl = 'https://goo.gle/gemini-cli-docs-mcp';
257
- if (process.env.SANDBOX && process.env.SANDBOX !== 'sandbox-exec') {
258
- addMessage({
259
- type: MessageType.INFO,
260
- content: `No MCP servers configured. Please open the following URL in your browser to view documentation:\n${docsUrl}`,
261
- timestamp: new Date(),
262
- });
263
- }
264
- else {
265
- addMessage({
266
- type: MessageType.INFO,
267
- content: `No MCP servers configured. Opening documentation in your browser: ${docsUrl}`,
268
- timestamp: new Date(),
269
- });
270
- await open(docsUrl);
271
- }
272
- return;
273
- }
274
- // Check if any servers are still connecting
275
- const connectingServers = serverNames.filter((name) => getMCPServerStatus(name) === MCPServerStatus.CONNECTING);
276
- const discoveryState = getMCPDiscoveryState();
277
- let message = '';
278
- // Add overall discovery status message if needed
279
- if (discoveryState === MCPDiscoveryState.IN_PROGRESS ||
280
- connectingServers.length > 0) {
281
- message += `\u001b[33m⏳ MCP servers are starting up (${connectingServers.length} initializing)...\u001b[0m\n`;
282
- message += `\u001b[90mNote: First startup may take longer. Tool availability will update automatically.\u001b[0m\n\n`;
283
- }
284
- message += 'Configured MCP servers:\n\n';
285
- for (const serverName of serverNames) {
286
- const serverTools = toolRegistry.getToolsByServer(serverName);
287
- const status = getMCPServerStatus(serverName);
288
- // Add status indicator with descriptive text
289
- let statusIndicator = '';
290
- let statusText = '';
291
- switch (status) {
292
- case MCPServerStatus.CONNECTED:
293
- statusIndicator = '🟢';
294
- statusText = 'Ready';
295
- break;
296
- case MCPServerStatus.CONNECTING:
297
- statusIndicator = '🔄';
298
- statusText = 'Starting... (first startup may take longer)';
299
- break;
300
- case MCPServerStatus.DISCONNECTED:
301
- default:
302
- statusIndicator = '🔴';
303
- statusText = 'Disconnected';
304
- break;
305
- }
306
- // Get server description if available
307
- const server = mcpServers[serverName];
308
- // Format server header with bold formatting and status
309
- message += `${statusIndicator} \u001b[1m${serverName}\u001b[0m - ${statusText}`;
310
- // Add tool count with conditional messaging
311
- if (status === MCPServerStatus.CONNECTED) {
312
- message += ` (${serverTools.length} tools)`;
313
- }
314
- else if (status === MCPServerStatus.CONNECTING) {
315
- message += ` (tools will appear when ready)`;
316
- }
317
- else {
318
- message += ` (${serverTools.length} tools cached)`;
319
- }
320
- // Add server description with proper handling of multi-line descriptions
321
- if ((useShowDescriptions || useShowSchema) && server?.description) {
322
- const greenColor = '\u001b[32m';
323
- const resetColor = '\u001b[0m';
324
- const descLines = server.description.trim().split('\n');
325
- if (descLines) {
326
- message += ':\n';
327
- for (const descLine of descLines) {
328
- message += ` ${greenColor}${descLine}${resetColor}\n`;
329
- }
330
- }
331
- else {
332
- message += '\n';
333
- }
334
- }
335
- else {
336
- message += '\n';
337
- }
338
- // Reset formatting after server entry
339
- message += '\u001b[0m';
340
- if (serverTools.length > 0) {
341
- serverTools.forEach((tool) => {
342
- if ((useShowDescriptions || useShowSchema) &&
343
- tool.description) {
344
- // Format tool name in cyan using simple ANSI cyan color
345
- message += ` - \u001b[36m${tool.name}\u001b[0m`;
346
- // Apply green color to the description text
347
- const greenColor = '\u001b[32m';
348
- const resetColor = '\u001b[0m';
349
- // Handle multi-line descriptions by properly indenting and preserving formatting
350
- const descLines = tool.description.trim().split('\n');
351
- if (descLines) {
352
- message += ':\n';
353
- for (const descLine of descLines) {
354
- message += ` ${greenColor}${descLine}${resetColor}\n`;
355
- }
356
- }
357
- else {
358
- message += '\n';
359
- }
360
- // Reset is handled inline with each line now
361
- }
362
- else {
363
- // Use cyan color for the tool name even when not showing descriptions
364
- message += ` - \u001b[36m${tool.name}\u001b[0m\n`;
365
- }
366
- if (useShowSchema) {
367
- // Prefix the parameters in cyan
368
- message += ` \u001b[36mParameters:\u001b[0m\n`;
369
- // Apply green color to the parameter text
370
- const greenColor = '\u001b[32m';
371
- const resetColor = '\u001b[0m';
372
- const paramsLines = JSON.stringify(tool.schema.parameters, null, 2)
373
- .trim()
374
- .split('\n');
375
- if (paramsLines) {
376
- for (const paramsLine of paramsLines) {
377
- message += ` ${greenColor}${paramsLine}${resetColor}\n`;
378
- }
379
- }
380
- }
381
- });
382
- }
383
- else {
384
- message += ' No tools available\n';
385
- }
386
- message += '\n';
387
- }
388
- // Make sure to reset any ANSI formatting at the end to prevent it from affecting the terminal
389
- message += '\u001b[0m';
390
- addMessage({
391
- type: MessageType.INFO,
392
- content: message,
393
- timestamp: new Date(),
394
- });
395
- },
396
- },
397
- {
398
- name: 'extensions',
399
- description: 'list active extensions',
400
- action: async () => {
401
- const activeExtensions = config?.getActiveExtensions();
402
- if (!activeExtensions || activeExtensions.length === 0) {
403
- addMessage({
404
- type: MessageType.INFO,
405
- content: 'No active extensions.',
406
- timestamp: new Date(),
407
- });
408
- return;
409
- }
410
- let message = 'Active extensions:\n\n';
411
- for (const ext of activeExtensions) {
412
- message += ` - \u001b[36m${ext.name} (v${ext.version})\u001b[0m\n`;
413
- }
414
- // Make sure to reset any ANSI formatting at the end to prevent it from affecting the terminal
415
- message += '\u001b[0m';
416
- addMessage({
417
- type: MessageType.INFO,
418
- content: message,
419
- timestamp: new Date(),
420
- });
421
- },
422
- },
423
- {
424
- name: 'tools',
425
- description: 'list available Gemini CLI tools',
426
- action: async (_mainCommand, _subCommand, _args) => {
427
- // Check if the _subCommand includes a specific flag to control description visibility
428
- let useShowDescriptions = showToolDescriptions;
429
- if (_subCommand === 'desc' || _subCommand === 'descriptions') {
430
- useShowDescriptions = true;
431
- }
432
- else if (_subCommand === 'nodesc' ||
433
- _subCommand === 'nodescriptions') {
434
- useShowDescriptions = false;
435
- }
436
- else if (_args === 'desc' || _args === 'descriptions') {
437
- useShowDescriptions = true;
438
- }
439
- else if (_args === 'nodesc' || _args === 'nodescriptions') {
440
- useShowDescriptions = false;
441
- }
442
- const toolRegistry = await config?.getToolRegistry();
443
- const tools = toolRegistry?.getAllTools();
444
- if (!tools) {
445
- addMessage({
446
- type: MessageType.ERROR,
447
- content: 'Could not retrieve tools.',
448
- timestamp: new Date(),
449
- });
450
- return;
451
- }
452
- // Filter out MCP tools by checking if they have a serverName property
453
- const geminiTools = tools.filter((tool) => !('serverName' in tool));
454
- let message = 'Available Gemini CLI tools:\n\n';
455
- if (geminiTools.length > 0) {
456
- geminiTools.forEach((tool) => {
457
- if (useShowDescriptions && tool.description) {
458
- // Format tool name in cyan using simple ANSI cyan color
459
- message += ` - \u001b[36m${tool.displayName} (${tool.name})\u001b[0m:\n`;
460
- // Apply green color to the description text
461
- const greenColor = '\u001b[32m';
462
- const resetColor = '\u001b[0m';
463
- // Handle multi-line descriptions by properly indenting and preserving formatting
464
- const descLines = tool.description.trim().split('\n');
465
- // If there are multiple lines, add proper indentation for each line
466
- if (descLines) {
467
- for (const descLine of descLines) {
468
- message += ` ${greenColor}${descLine}${resetColor}\n`;
469
- }
470
- }
471
- }
472
- else {
473
- // Use cyan color for the tool name even when not showing descriptions
474
- message += ` - \u001b[36m${tool.displayName}\u001b[0m\n`;
475
- }
476
- });
477
- }
478
- else {
479
- message += ' No tools available\n';
480
- }
481
- message += '\n';
482
- // Make sure to reset any ANSI formatting at the end to prevent it from affecting the terminal
483
- message += '\u001b[0m';
484
- addMessage({
485
- type: MessageType.INFO,
486
- content: message,
487
- timestamp: new Date(),
488
- });
489
- },
490
- },
491
- {
492
- name: 'corgi',
493
- action: (_mainCommand, _subCommand, _args) => {
494
- toggleCorgiMode();
495
- },
496
- },
497
- {
498
- name: 'about',
499
- description: 'show version info',
500
- action: async (_mainCommand, _subCommand, _args) => {
501
- const osVersion = process.platform;
502
- let sandboxEnv = 'no sandbox';
503
- if (process.env.SANDBOX && process.env.SANDBOX !== 'sandbox-exec') {
504
- sandboxEnv = process.env.SANDBOX;
505
- }
506
- else if (process.env.SANDBOX === 'sandbox-exec') {
507
- sandboxEnv = `sandbox-exec (${process.env.SEATBELT_PROFILE || 'unknown'})`;
508
- }
509
- const modelVersion = config?.getModel() || 'Unknown';
510
- const cliVersion = await getCliVersion();
511
- const selectedAuthType = settings.merged.selectedAuthType || '';
512
- const gcpProject = process.env.GOOGLE_CLOUD_PROJECT || '';
513
- addMessage({
514
- type: MessageType.ABOUT,
515
- timestamp: new Date(),
516
- cliVersion,
517
- osVersion,
518
- sandboxEnv,
519
- modelVersion,
520
- selectedAuthType,
521
- gcpProject,
522
- });
523
- },
524
- },
525
- {
526
- name: 'bug',
527
- description: 'submit a bug report',
528
- action: async (_mainCommand, _subCommand, args) => {
529
- let bugDescription = _subCommand || '';
530
- if (args) {
531
- bugDescription += ` ${args}`;
532
- }
533
- bugDescription = bugDescription.trim();
534
- const osVersion = `${process.platform} ${process.version}`;
535
- let sandboxEnv = 'no sandbox';
536
- if (process.env.SANDBOX && process.env.SANDBOX !== 'sandbox-exec') {
537
- sandboxEnv = process.env.SANDBOX.replace(/^gemini-(?:code-)?/, '');
538
- }
539
- else if (process.env.SANDBOX === 'sandbox-exec') {
540
- sandboxEnv = `sandbox-exec (${process.env.SEATBELT_PROFILE || 'unknown'})`;
541
- }
542
- const modelVersion = config?.getModel() || 'Unknown';
543
- const cliVersion = await getCliVersion();
544
- const memoryUsage = formatMemoryUsage(process.memoryUsage().rss);
545
- const info = `
546
- * **CLI Version:** ${cliVersion}
547
- * **Git Commit:** ${GIT_COMMIT_INFO}
548
- * **Operating System:** ${osVersion}
549
- * **Sandbox Environment:** ${sandboxEnv}
550
- * **Model Version:** ${modelVersion}
551
- * **Memory Usage:** ${memoryUsage}
552
- `;
553
- let bugReportUrl = 'https://github.com/google-gemini/gemini-cli/issues/new?template=bug_report.yml&title={title}&info={info}';
554
- const bugCommand = config?.getBugCommand();
555
- if (bugCommand?.urlTemplate) {
556
- bugReportUrl = bugCommand.urlTemplate;
557
- }
558
- bugReportUrl = bugReportUrl
559
- .replace('{title}', encodeURIComponent(bugDescription))
560
- .replace('{info}', encodeURIComponent(info));
561
- addMessage({
562
- type: MessageType.INFO,
563
- content: `To submit your bug report, please open the following URL in your browser:\n${bugReportUrl}`,
564
- timestamp: new Date(),
565
- });
566
- (async () => {
567
- try {
568
- await open(bugReportUrl);
569
- }
570
- catch (error) {
571
- const errorMessage = error instanceof Error ? error.message : String(error);
572
- addMessage({
573
- type: MessageType.ERROR,
574
- content: `Could not open URL in browser: ${errorMessage}`,
575
- timestamp: new Date(),
576
- });
577
- }
578
- })();
579
- },
580
- },
581
- {
582
- name: 'chat',
583
- description: 'Manage conversation history. Usage: /chat <list|save|resume> <tag>',
584
- action: async (_mainCommand, subCommand, args) => {
585
- const tag = (args || '').trim();
586
- const logger = new Logger(config?.getSessionId() || '');
587
- await logger.initialize();
588
- const chat = await config?.getGeminiClient()?.getChat();
589
- if (!chat) {
590
- addMessage({
591
- type: MessageType.ERROR,
592
- content: 'No chat client available for conversation status.',
593
- timestamp: new Date(),
594
- });
595
- return;
596
- }
597
- if (!subCommand) {
598
- addMessage({
599
- type: MessageType.ERROR,
600
- content: 'Missing command\nUsage: /chat <list|save|resume> <tag>',
601
- timestamp: new Date(),
602
- });
603
- return;
604
- }
605
- switch (subCommand) {
606
- case 'save': {
607
- if (!tag) {
608
- addMessage({
609
- type: MessageType.ERROR,
610
- content: 'Missing tag. Usage: /chat save <tag>',
611
- timestamp: new Date(),
612
- });
613
- return;
614
- }
615
- const history = chat.getHistory();
616
- if (history.length > 0) {
617
- await logger.saveCheckpoint(chat?.getHistory() || [], tag);
618
- addMessage({
619
- type: MessageType.INFO,
620
- content: `Conversation checkpoint saved with tag: ${tag}.`,
621
- timestamp: new Date(),
622
- });
623
- }
624
- else {
625
- addMessage({
626
- type: MessageType.INFO,
627
- content: 'No conversation found to save.',
628
- timestamp: new Date(),
629
- });
630
- }
631
- return;
632
- }
633
- case 'resume':
634
- case 'restore':
635
- case 'load': {
636
- if (!tag) {
637
- addMessage({
638
- type: MessageType.ERROR,
639
- content: 'Missing tag. Usage: /chat resume <tag>',
640
- timestamp: new Date(),
641
- });
642
- return;
643
- }
644
- const conversation = await logger.loadCheckpoint(tag);
645
- if (conversation.length === 0) {
646
- addMessage({
647
- type: MessageType.INFO,
648
- content: `No saved checkpoint found with tag: ${tag}.`,
649
- timestamp: new Date(),
650
- });
651
- return;
652
- }
653
- clearItems();
654
- chat.clearHistory();
655
- const rolemap = {
656
- user: MessageType.USER,
657
- model: MessageType.GEMINI,
658
- };
659
- let hasSystemPrompt = false;
660
- let i = 0;
661
- for (const item of conversation) {
662
- i += 1;
663
- // Add each item to history regardless of whether we display
664
- // it.
665
- chat.addHistory(item);
666
- const text = item.parts
667
- ?.filter((m) => !!m.text)
668
- .map((m) => m.text)
669
- .join('') || '';
670
- if (!text) {
671
- // Parsing Part[] back to various non-text output not yet implemented.
672
- continue;
673
- }
674
- if (i === 1 && text.match(/context for our chat/)) {
675
- hasSystemPrompt = true;
676
- }
677
- if (i > 2 || !hasSystemPrompt) {
678
- addItem({
679
- type: (item.role && rolemap[item.role]) || MessageType.GEMINI,
680
- text,
681
- }, i);
682
- }
683
- }
684
- console.clear();
685
- refreshStatic();
686
- return;
687
- }
688
- case 'list':
689
- addMessage({
690
- type: MessageType.INFO,
691
- content: 'list of saved conversations: ' +
692
- (await savedChatTags()).join(', '),
693
- timestamp: new Date(),
694
- });
695
- return;
696
- default:
697
- addMessage({
698
- type: MessageType.ERROR,
699
- content: `Unknown /chat command: ${subCommand}. Available: list, save, resume`,
700
- timestamp: new Date(),
701
- });
702
- return;
703
- }
704
- },
705
- completion: async () => (await savedChatTags()).map((tag) => 'resume ' + tag),
706
- },
707
- {
708
- name: 'quit',
709
- altName: 'exit',
710
- description: 'exit the cli',
711
- action: async (mainCommand, _subCommand, _args) => {
712
- const now = new Date();
713
- const { sessionStartTime } = session.stats;
714
- const wallDuration = now.getTime() - sessionStartTime.getTime();
715
- setQuittingMessages([
716
- {
717
- type: 'user',
718
- text: `/${mainCommand}`,
719
- id: now.getTime() - 1,
720
- },
721
- {
722
- type: 'quit',
723
- duration: formatDuration(wallDuration),
724
- id: now.getTime(),
725
- },
726
- ]);
727
- setTimeout(() => {
728
- process.exit(0);
729
- }, 100);
730
- },
731
- },
732
- {
733
- name: 'compress',
734
- altName: 'summarize',
735
- description: 'Compresses the context by replacing it with a summary.',
736
- action: async (_mainCommand, _subCommand, _args) => {
737
- if (pendingCompressionItemRef.current !== null) {
738
- addMessage({
739
- type: MessageType.ERROR,
740
- content: 'Already compressing, wait for previous request to complete',
741
- timestamp: new Date(),
742
- });
743
- return;
744
- }
745
- setPendingCompressionItem({
746
- type: MessageType.COMPRESSION,
747
- compression: {
748
- isPending: true,
749
- originalTokenCount: null,
750
- newTokenCount: null,
751
- },
752
- });
753
- try {
754
- const compressed = await config
755
- .getGeminiClient()
756
- // TODO: Set Prompt id for CompressChat from SlashCommandProcessor.
757
- .tryCompressChat('Prompt Id not set', true);
758
- if (compressed) {
759
- addMessage({
760
- type: MessageType.COMPRESSION,
761
- compression: {
762
- isPending: false,
763
- originalTokenCount: compressed.originalTokenCount,
764
- newTokenCount: compressed.newTokenCount,
765
- },
766
- timestamp: new Date(),
767
- });
768
- }
769
- else {
770
- addMessage({
771
- type: MessageType.ERROR,
772
- content: 'Failed to compress chat history.',
773
- timestamp: new Date(),
774
- });
775
- }
776
- }
777
- catch (e) {
778
- addMessage({
779
- type: MessageType.ERROR,
780
- content: `Failed to compress chat history: ${e instanceof Error ? e.message : String(e)}`,
781
- timestamp: new Date(),
782
- });
783
- }
784
- setPendingCompressionItem(null);
785
- },
786
- },
787
- ];
788
- if (config?.getCheckpointingEnabled()) {
789
- commands.push({
790
- name: 'restore',
791
- description: 'restore a tool call. This will reset the conversation and file history to the state it was in when the tool call was suggested',
792
- completion: async () => {
793
- const checkpointDir = config?.getProjectTempDir()
794
- ? path.join(config.getProjectTempDir(), 'checkpoints')
795
- : undefined;
796
- if (!checkpointDir) {
797
- return [];
798
- }
799
- try {
800
- const files = await fs.readdir(checkpointDir);
801
- return files
802
- .filter((file) => file.endsWith('.json'))
803
- .map((file) => file.replace('.json', ''));
804
- }
805
- catch (_err) {
806
- return [];
807
- }
808
- },
809
- action: async (_mainCommand, subCommand, _args) => {
810
- const checkpointDir = config?.getProjectTempDir()
811
- ? path.join(config.getProjectTempDir(), 'checkpoints')
812
- : undefined;
813
- if (!checkpointDir) {
814
- addMessage({
815
- type: MessageType.ERROR,
816
- content: 'Could not determine the .gemini directory path.',
817
- timestamp: new Date(),
818
- });
819
- return;
820
- }
821
- try {
822
- // Ensure the directory exists before trying to read it.
823
- await fs.mkdir(checkpointDir, { recursive: true });
824
- const files = await fs.readdir(checkpointDir);
825
- const jsonFiles = files.filter((file) => file.endsWith('.json'));
826
- if (!subCommand) {
827
- if (jsonFiles.length === 0) {
828
- addMessage({
829
- type: MessageType.INFO,
830
- content: 'No restorable tool calls found.',
831
- timestamp: new Date(),
832
- });
833
- return;
834
- }
835
- const truncatedFiles = jsonFiles.map((file) => {
836
- const components = file.split('.');
837
- if (components.length <= 1) {
838
- return file;
839
- }
840
- components.pop();
841
- return components.join('.');
842
- });
843
- const fileList = truncatedFiles.join('\n');
844
- addMessage({
845
- type: MessageType.INFO,
846
- content: `Available tool calls to restore:\n\n${fileList}`,
847
- timestamp: new Date(),
848
- });
849
- return;
850
- }
851
- const selectedFile = subCommand.endsWith('.json')
852
- ? subCommand
853
- : `${subCommand}.json`;
854
- if (!jsonFiles.includes(selectedFile)) {
855
- addMessage({
856
- type: MessageType.ERROR,
857
- content: `File not found: ${selectedFile}`,
858
- timestamp: new Date(),
859
- });
860
- return;
861
- }
862
- const filePath = path.join(checkpointDir, selectedFile);
863
- const data = await fs.readFile(filePath, 'utf-8');
864
- const toolCallData = JSON.parse(data);
865
- if (toolCallData.history) {
866
- loadHistory(toolCallData.history);
867
- }
868
- if (toolCallData.clientHistory) {
869
- await config
870
- ?.getGeminiClient()
871
- ?.setHistory(toolCallData.clientHistory);
872
- }
873
- if (toolCallData.commitHash) {
874
- await gitService?.restoreProjectFromSnapshot(toolCallData.commitHash);
875
- addMessage({
876
- type: MessageType.INFO,
877
- content: `Restored project to the state before the tool call.`,
878
- timestamp: new Date(),
879
- });
880
- }
881
- return {
882
- type: 'tool',
883
- toolName: toolCallData.toolCall.name,
884
- toolArgs: toolCallData.toolCall.args,
885
- };
886
- }
887
- catch (error) {
888
- addMessage({
889
- type: MessageType.ERROR,
890
- content: `Could not read restorable tool calls. This is the error: ${error}`,
891
- timestamp: new Date(),
892
- });
893
- }
894
- },
895
- });
896
- }
897
- return commands;
898
- }, [
899
- addMessage,
900
- openAuthDialog,
901
- openEditorDialog,
902
- openPrivacyNotice,
903
- toggleCorgiMode,
904
- savedChatTags,
905
- config,
906
- settings,
907
- showToolDescriptions,
908
- session,
909
- gitService,
910
- loadHistory,
911
- addItem,
912
- setQuittingMessages,
913
- pendingCompressionItemRef,
914
- setPendingCompressionItem,
915
- clearItems,
916
- refreshStatic,
917
- ]);
918
148
  const handleSlashCommand = useCallback(async (rawQuery) => {
919
149
  if (typeof rawQuery !== 'string') {
920
150
  return false;
@@ -924,17 +154,24 @@ export const useSlashCommandProcessor = (config, settings, history, addItem, cle
924
154
  return false;
925
155
  }
926
156
  const userMessageTimestamp = Date.now();
927
- if (trimmed !== '/quit' && trimmed !== '/exit') {
928
- addItem({ type: MessageType.USER, text: trimmed }, userMessageTimestamp);
929
- }
157
+ addItem({ type: MessageType.USER, text: trimmed }, userMessageTimestamp);
930
158
  const parts = trimmed.substring(1).trim().split(/\s+/);
931
159
  const commandPath = parts.filter((p) => p); // The parts of the command, e.g., ['memory', 'add']
932
- // --- Start of New Tree Traversal Logic ---
933
160
  let currentCommands = commands;
934
161
  let commandToExecute;
935
162
  let pathIndex = 0;
936
163
  for (const part of commandPath) {
937
- const foundCommand = currentCommands.find((cmd) => cmd.name === part || cmd.altName === part);
164
+ // TODO: For better performance and architectural clarity, this two-pass
165
+ // search could be replaced. A more optimal approach would be to
166
+ // pre-compute a single lookup map in `CommandService.ts` that resolves
167
+ // all name and alias conflicts during the initial loading phase. The
168
+ // processor would then perform a single, fast lookup on that map.
169
+ // First pass: check for an exact match on the primary command name.
170
+ let foundCommand = currentCommands.find((cmd) => cmd.name === part);
171
+ // Second pass: if no primary name matches, check for an alias.
172
+ if (!foundCommand) {
173
+ foundCommand = currentCommands.find((cmd) => cmd.altNames?.includes(part));
174
+ }
938
175
  if (foundCommand) {
939
176
  commandToExecute = foundCommand;
940
177
  pathIndex++;
@@ -952,42 +189,89 @@ export const useSlashCommandProcessor = (config, settings, history, addItem, cle
952
189
  if (commandToExecute) {
953
190
  const args = parts.slice(pathIndex).join(' ');
954
191
  if (commandToExecute.action) {
955
- const result = await commandToExecute.action(commandContext, args);
956
- if (result) {
957
- switch (result.type) {
958
- case 'tool':
959
- return {
960
- type: 'schedule_tool',
961
- toolName: result.toolName,
962
- toolArgs: result.toolArgs,
963
- };
964
- case 'message':
965
- addItem({
966
- type: result.messageType === 'error'
967
- ? MessageType.ERROR
968
- : MessageType.INFO,
969
- text: result.content,
970
- }, Date.now());
971
- return { type: 'handled' };
972
- case 'dialog':
973
- switch (result.dialog) {
974
- case 'help':
975
- setShowHelp(true);
976
- return { type: 'handled' };
977
- case 'theme':
978
- openThemeDialog();
979
- return { type: 'handled' };
980
- default: {
981
- const unhandled = result.dialog;
982
- throw new Error(`Unhandled slash command result: ${unhandled}`);
192
+ const fullCommandContext = {
193
+ ...commandContext,
194
+ invocation: {
195
+ raw: trimmed,
196
+ name: commandToExecute.name,
197
+ args,
198
+ },
199
+ };
200
+ try {
201
+ const result = await commandToExecute.action(fullCommandContext, args);
202
+ if (result) {
203
+ switch (result.type) {
204
+ case 'tool':
205
+ return {
206
+ type: 'schedule_tool',
207
+ toolName: result.toolName,
208
+ toolArgs: result.toolArgs,
209
+ };
210
+ case 'message':
211
+ addItem({
212
+ type: result.messageType === 'error'
213
+ ? MessageType.ERROR
214
+ : MessageType.INFO,
215
+ text: result.content,
216
+ }, Date.now());
217
+ return { type: 'handled' };
218
+ case 'dialog':
219
+ switch (result.dialog) {
220
+ case 'help':
221
+ setShowHelp(true);
222
+ return { type: 'handled' };
223
+ case 'auth':
224
+ openAuthDialog();
225
+ return { type: 'handled' };
226
+ case 'theme':
227
+ openThemeDialog();
228
+ return { type: 'handled' };
229
+ case 'editor':
230
+ openEditorDialog();
231
+ return { type: 'handled' };
232
+ case 'privacy':
233
+ openPrivacyNotice();
234
+ return { type: 'handled' };
235
+ default: {
236
+ const unhandled = result.dialog;
237
+ throw new Error(`Unhandled slash command result: ${unhandled}`);
238
+ }
983
239
  }
240
+ case 'load_history': {
241
+ await config
242
+ ?.getGeminiClient()
243
+ ?.setHistory(result.clientHistory);
244
+ fullCommandContext.ui.clear();
245
+ result.history.forEach((item, index) => {
246
+ fullCommandContext.ui.addItem(item, index);
247
+ });
248
+ return { type: 'handled' };
249
+ }
250
+ case 'quit':
251
+ setQuittingMessages(result.messages);
252
+ setTimeout(() => {
253
+ process.exit(0);
254
+ }, 100);
255
+ return { type: 'handled' };
256
+ case 'submit_prompt':
257
+ return {
258
+ type: 'submit_prompt',
259
+ content: result.content,
260
+ };
261
+ default: {
262
+ const unhandled = result;
263
+ throw new Error(`Unhandled slash command result: ${unhandled}`);
984
264
  }
985
- default: {
986
- const unhandled = result;
987
- throw new Error(`Unhandled slash command result: ${unhandled}`);
988
265
  }
989
266
  }
990
267
  }
268
+ catch (e) {
269
+ addItem({
270
+ type: MessageType.ERROR,
271
+ text: e instanceof Error ? e.message : String(e),
272
+ }, Date.now());
273
+ return { type: 'handled' };
274
+ }
991
275
  return { type: 'handled' };
992
276
  }
993
277
  else if (commandToExecute.subCommands) {
@@ -1002,32 +286,6 @@ export const useSlashCommandProcessor = (config, settings, history, addItem, cle
1002
286
  return { type: 'handled' };
1003
287
  }
1004
288
  }
1005
- // --- End of New Tree Traversal Logic ---
1006
- // --- Legacy Fallback Logic (for commands not yet migrated) ---
1007
- const mainCommand = parts[0];
1008
- const subCommand = parts[1];
1009
- const legacyArgs = parts.slice(2).join(' ');
1010
- for (const cmd of legacyCommands) {
1011
- if (mainCommand === cmd.name || mainCommand === cmd.altName) {
1012
- const actionResult = await cmd.action(mainCommand, subCommand, legacyArgs);
1013
- if (actionResult?.type === 'tool') {
1014
- return {
1015
- type: 'schedule_tool',
1016
- toolName: actionResult.toolName,
1017
- toolArgs: actionResult.toolArgs,
1018
- };
1019
- }
1020
- if (actionResult?.type === 'message') {
1021
- addItem({
1022
- type: actionResult.messageType === 'error'
1023
- ? MessageType.ERROR
1024
- : MessageType.INFO,
1025
- text: actionResult.content,
1026
- }, Date.now());
1027
- }
1028
- return { type: 'handled' };
1029
- }
1030
- }
1031
289
  addMessage({
1032
290
  type: MessageType.ERROR,
1033
291
  content: `Unknown command: ${trimmed}`,
@@ -1035,37 +293,21 @@ export const useSlashCommandProcessor = (config, settings, history, addItem, cle
1035
293
  });
1036
294
  return { type: 'handled' };
1037
295
  }, [
296
+ config,
1038
297
  addItem,
1039
298
  setShowHelp,
299
+ openAuthDialog,
1040
300
  commands,
1041
- legacyCommands,
1042
301
  commandContext,
1043
302
  addMessage,
1044
303
  openThemeDialog,
304
+ openPrivacyNotice,
305
+ openEditorDialog,
306
+ setQuittingMessages,
1045
307
  ]);
1046
- const allCommands = useMemo(() => {
1047
- // Adapt legacy commands to the new SlashCommand interface
1048
- const adaptedLegacyCommands = legacyCommands.map((legacyCmd) => ({
1049
- name: legacyCmd.name,
1050
- altName: legacyCmd.altName,
1051
- description: legacyCmd.description,
1052
- action: async (_context, args) => {
1053
- const parts = args.split(/\s+/);
1054
- const subCommand = parts[0] || undefined;
1055
- const restOfArgs = parts.slice(1).join(' ') || undefined;
1056
- return legacyCmd.action(legacyCmd.name, subCommand, restOfArgs);
1057
- },
1058
- completion: legacyCmd.completion
1059
- ? async (_context, _partialArg) => legacyCmd.completion()
1060
- : undefined,
1061
- }));
1062
- const newCommandNames = new Set(commands.map((c) => c.name));
1063
- const filteredAdaptedLegacy = adaptedLegacyCommands.filter((c) => !newCommandNames.has(c.name));
1064
- return [...commands, ...filteredAdaptedLegacy];
1065
- }, [commands, legacyCommands]);
1066
308
  return {
1067
309
  handleSlashCommand,
1068
- slashCommands: allCommands,
310
+ slashCommands: commands,
1069
311
  pendingHistoryItems,
1070
312
  commandContext,
1071
313
  };