@vybestack/llxprt-code 0.1.13-nightly.250727.2261021c → 0.1.14

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 (204) hide show
  1. package/README.md +24 -0
  2. package/dist/package.json +7 -5
  3. package/dist/src/acp/acpPeer.js +1 -0
  4. package/dist/src/acp/acpPeer.js.map +1 -1
  5. package/dist/src/config/config.d.ts +3 -2
  6. package/dist/src/config/config.js +19 -32
  7. package/dist/src/config/config.js.map +1 -1
  8. package/dist/src/config/settings.d.ts +7 -2
  9. package/dist/src/config/settings.js +25 -10
  10. package/dist/src/config/settings.js.map +1 -1
  11. package/dist/src/gemini.js +7 -33
  12. package/dist/src/gemini.js.map +1 -1
  13. package/dist/src/generated/git-commit.d.ts +1 -1
  14. package/dist/src/generated/git-commit.js +1 -1
  15. package/dist/src/patches/is-in-ci.d.ts +7 -0
  16. package/dist/src/patches/is-in-ci.js +15 -0
  17. package/dist/src/patches/is-in-ci.js.map +1 -0
  18. package/dist/src/services/BuiltinCommandLoader.d.ts +24 -0
  19. package/dist/src/services/BuiltinCommandLoader.js +82 -0
  20. package/dist/src/services/BuiltinCommandLoader.js.map +1 -0
  21. package/dist/src/services/CommandService.d.ts +43 -7
  22. package/dist/src/services/CommandService.js +61 -64
  23. package/dist/src/services/CommandService.js.map +1 -1
  24. package/dist/src/services/FileCommandLoader.d.ts +37 -0
  25. package/dist/src/services/FileCommandLoader.js +156 -0
  26. package/dist/src/services/FileCommandLoader.js.map +1 -0
  27. package/dist/src/services/prompt-processors/argumentProcessor.d.ts +21 -0
  28. package/dist/src/services/prompt-processors/argumentProcessor.js +28 -0
  29. package/dist/src/services/prompt-processors/argumentProcessor.js.map +1 -0
  30. package/dist/src/services/prompt-processors/types.d.ts +34 -0
  31. package/dist/src/services/prompt-processors/types.js +10 -0
  32. package/dist/src/services/prompt-processors/types.js.map +1 -0
  33. package/dist/src/services/types.d.ts +22 -0
  34. package/dist/src/services/types.js +7 -0
  35. package/dist/src/services/types.js.map +1 -0
  36. package/dist/src/ui/App.js +19 -10
  37. package/dist/src/ui/App.js.map +1 -1
  38. package/dist/src/ui/colors.js +6 -0
  39. package/dist/src/ui/colors.js.map +1 -1
  40. package/dist/src/ui/commands/aboutCommand.js +2 -0
  41. package/dist/src/ui/commands/aboutCommand.js.map +1 -1
  42. package/dist/src/ui/commands/authCommand.js +66 -4
  43. package/dist/src/ui/commands/authCommand.js.map +1 -1
  44. package/dist/src/ui/commands/baseurlCommand.js +2 -0
  45. package/dist/src/ui/commands/baseurlCommand.js.map +1 -1
  46. package/dist/src/ui/commands/bugCommand.js +3 -1
  47. package/dist/src/ui/commands/bugCommand.js.map +1 -1
  48. package/dist/src/ui/commands/chatCommand.js +12 -2
  49. package/dist/src/ui/commands/chatCommand.js.map +1 -1
  50. package/dist/src/ui/commands/clearCommand.js +2 -0
  51. package/dist/src/ui/commands/clearCommand.js.map +1 -1
  52. package/dist/src/ui/commands/compressCommand.js +3 -1
  53. package/dist/src/ui/commands/compressCommand.js.map +1 -1
  54. package/dist/src/ui/commands/copyCommand.d.ts +7 -0
  55. package/dist/src/ui/commands/copyCommand.js +59 -0
  56. package/dist/src/ui/commands/copyCommand.js.map +1 -0
  57. package/dist/src/ui/commands/docsCommand.js +2 -0
  58. package/dist/src/ui/commands/docsCommand.js.map +1 -1
  59. package/dist/src/ui/commands/editorCommand.js +2 -0
  60. package/dist/src/ui/commands/editorCommand.js.map +1 -1
  61. package/dist/src/ui/commands/extensionsCommand.js +2 -0
  62. package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
  63. package/dist/src/ui/commands/helpCommand.js +3 -1
  64. package/dist/src/ui/commands/helpCommand.js.map +1 -1
  65. package/dist/src/ui/commands/ideCommand.js +21 -23
  66. package/dist/src/ui/commands/ideCommand.js.map +1 -1
  67. package/dist/src/ui/commands/keyCommand.js +2 -0
  68. package/dist/src/ui/commands/keyCommand.js.map +1 -1
  69. package/dist/src/ui/commands/keyfileCommand.js +2 -0
  70. package/dist/src/ui/commands/keyfileCommand.js.map +1 -1
  71. package/dist/src/ui/commands/mcpCommand.js +181 -5
  72. package/dist/src/ui/commands/mcpCommand.js.map +1 -1
  73. package/dist/src/ui/commands/memoryCommand.js +11 -4
  74. package/dist/src/ui/commands/memoryCommand.js.map +1 -1
  75. package/dist/src/ui/commands/modelCommand.js +2 -0
  76. package/dist/src/ui/commands/modelCommand.js.map +1 -1
  77. package/dist/src/ui/commands/privacyCommand.js +2 -0
  78. package/dist/src/ui/commands/privacyCommand.js.map +1 -1
  79. package/dist/src/ui/commands/providerCommand.js +2 -0
  80. package/dist/src/ui/commands/providerCommand.js.map +1 -1
  81. package/dist/src/ui/commands/quitCommand.js +3 -1
  82. package/dist/src/ui/commands/quitCommand.js.map +1 -1
  83. package/dist/src/ui/commands/restoreCommand.js +2 -0
  84. package/dist/src/ui/commands/restoreCommand.js.map +1 -1
  85. package/dist/src/ui/commands/statsCommand.js +5 -1
  86. package/dist/src/ui/commands/statsCommand.js.map +1 -1
  87. package/dist/src/ui/commands/themeCommand.js +2 -0
  88. package/dist/src/ui/commands/themeCommand.js.map +1 -1
  89. package/dist/src/ui/commands/toolformatCommand.d.ts +7 -0
  90. package/dist/src/ui/commands/toolformatCommand.js +89 -0
  91. package/dist/src/ui/commands/toolformatCommand.js.map +1 -0
  92. package/dist/src/ui/commands/toolsCommand.js +2 -0
  93. package/dist/src/ui/commands/toolsCommand.js.map +1 -1
  94. package/dist/src/ui/commands/types.d.ts +30 -3
  95. package/dist/src/ui/commands/types.js +5 -1
  96. package/dist/src/ui/commands/types.js.map +1 -1
  97. package/dist/src/ui/commands/vimCommand.d.ts +7 -0
  98. package/dist/src/ui/commands/vimCommand.js +30 -0
  99. package/dist/src/ui/commands/vimCommand.js.map +1 -0
  100. package/dist/src/ui/components/ContextSummaryDisplay.d.ts +3 -2
  101. package/dist/src/ui/components/ContextSummaryDisplay.js +10 -10
  102. package/dist/src/ui/components/ContextSummaryDisplay.js.map +1 -1
  103. package/dist/src/ui/components/Footer.js +12 -1
  104. package/dist/src/ui/components/Footer.js.map +1 -1
  105. package/dist/src/ui/components/Help.d.ts +1 -1
  106. package/dist/src/ui/components/IDEContextDetailDisplay.d.ts +11 -0
  107. package/dist/src/ui/components/IDEContextDetailDisplay.js +19 -0
  108. package/dist/src/ui/components/IDEContextDetailDisplay.js.map +1 -0
  109. package/dist/src/ui/components/InputPrompt.d.ts +1 -1
  110. package/dist/src/ui/components/InputPrompt.js +9 -99
  111. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  112. package/dist/src/ui/components/ModelStatsDisplay.js.map +1 -1
  113. package/dist/src/ui/components/ThemeDialog.js +49 -18
  114. package/dist/src/ui/components/ThemeDialog.js.map +1 -1
  115. package/dist/src/ui/components/ToolStatsDisplay.js.map +1 -1
  116. package/dist/src/ui/components/messages/DiffRenderer.d.ts +1 -0
  117. package/dist/src/ui/components/messages/DiffRenderer.js +12 -11
  118. package/dist/src/ui/components/messages/DiffRenderer.js.map +1 -1
  119. package/dist/src/ui/components/messages/ToolConfirmationMessage.js +5 -4
  120. package/dist/src/ui/components/messages/ToolConfirmationMessage.js.map +1 -1
  121. package/dist/src/ui/components/shared/text-buffer.d.ts +3 -1
  122. package/dist/src/ui/components/shared/text-buffer.js +10 -9
  123. package/dist/src/ui/components/shared/text-buffer.js.map +1 -1
  124. package/dist/src/ui/containers/SessionController.js +42 -31
  125. package/dist/src/ui/containers/SessionController.js.map +1 -1
  126. package/dist/src/ui/contexts/VimModeContext.d.ts +19 -0
  127. package/dist/src/ui/contexts/VimModeContext.js +51 -0
  128. package/dist/src/ui/contexts/VimModeContext.js.map +1 -0
  129. package/dist/src/ui/hooks/atCommandProcessor.js +54 -15
  130. package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
  131. package/dist/src/ui/hooks/shellCommandProcessor.js +22 -8
  132. package/dist/src/ui/hooks/shellCommandProcessor.js.map +1 -1
  133. package/dist/src/ui/hooks/slashCommandProcessor.d.ts +2 -2
  134. package/dist/src/ui/hooks/slashCommandProcessor.js +43 -605
  135. package/dist/src/ui/hooks/slashCommandProcessor.js.map +1 -1
  136. package/dist/src/ui/hooks/useAuthCommand.js +2 -2
  137. package/dist/src/ui/hooks/useAuthCommand.js.map +1 -1
  138. package/dist/src/ui/hooks/useCompletion.d.ts +3 -1
  139. package/dist/src/ui/hooks/useCompletion.js +113 -24
  140. package/dist/src/ui/hooks/useCompletion.js.map +1 -1
  141. package/dist/src/ui/hooks/useGeminiStream.js +57 -11
  142. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  143. package/dist/src/ui/hooks/useInputHistory.d.ts +1 -1
  144. package/dist/src/ui/hooks/usePhraseCycler.js +27 -131
  145. package/dist/src/ui/hooks/usePhraseCycler.js.map +1 -1
  146. package/dist/src/ui/hooks/useShellHistory.d.ts +3 -2
  147. package/dist/src/ui/hooks/useShellHistory.js.map +1 -1
  148. package/dist/src/ui/hooks/useThemeCommand.js +22 -2
  149. package/dist/src/ui/hooks/useThemeCommand.js.map +1 -1
  150. package/dist/src/ui/privacy/PrivacyNotice.js +16 -2
  151. package/dist/src/ui/privacy/PrivacyNotice.js.map +1 -1
  152. package/dist/src/ui/themes/ansi-light.js +2 -0
  153. package/dist/src/ui/themes/ansi-light.js.map +1 -1
  154. package/dist/src/ui/themes/ansi.js +2 -0
  155. package/dist/src/ui/themes/ansi.js.map +1 -1
  156. package/dist/src/ui/themes/atom-one-dark.js +2 -0
  157. package/dist/src/ui/themes/atom-one-dark.js.map +1 -1
  158. package/dist/src/ui/themes/ayu-light.js +3 -1
  159. package/dist/src/ui/themes/ayu-light.js.map +1 -1
  160. package/dist/src/ui/themes/ayu.js +3 -1
  161. package/dist/src/ui/themes/ayu.js.map +1 -1
  162. package/dist/src/ui/themes/color-utils.d.ts +21 -0
  163. package/dist/src/ui/themes/color-utils.js +221 -0
  164. package/dist/src/ui/themes/color-utils.js.map +1 -0
  165. package/dist/src/ui/themes/dracula.js +2 -0
  166. package/dist/src/ui/themes/dracula.js.map +1 -1
  167. package/dist/src/ui/themes/github-dark.js +2 -0
  168. package/dist/src/ui/themes/github-dark.js.map +1 -1
  169. package/dist/src/ui/themes/github-light.js +2 -0
  170. package/dist/src/ui/themes/github-light.js.map +1 -1
  171. package/dist/src/ui/themes/googlecode.js +2 -0
  172. package/dist/src/ui/themes/googlecode.js.map +1 -1
  173. package/dist/src/ui/themes/green-screen.js +2 -0
  174. package/dist/src/ui/themes/green-screen.js.map +1 -1
  175. package/dist/src/ui/themes/no-color.js +3 -1
  176. package/dist/src/ui/themes/no-color.js.map +1 -1
  177. package/dist/src/ui/themes/shades-of-purple.d.ts +1 -1
  178. package/dist/src/ui/themes/shades-of-purple.js +3 -1
  179. package/dist/src/ui/themes/shades-of-purple.js.map +1 -1
  180. package/dist/src/ui/themes/theme-manager.d.ts +31 -6
  181. package/dist/src/ui/themes/theme-manager.js +104 -34
  182. package/dist/src/ui/themes/theme-manager.js.map +1 -1
  183. package/dist/src/ui/themes/theme.d.ts +22 -3
  184. package/dist/src/ui/themes/theme.js +229 -182
  185. package/dist/src/ui/themes/theme.js.map +1 -1
  186. package/dist/src/ui/themes/xcode.js +2 -0
  187. package/dist/src/ui/themes/xcode.js.map +1 -1
  188. package/dist/src/ui/types.d.ts +9 -1
  189. package/dist/src/ui/utils/CodeColorizer.d.ts +3 -1
  190. package/dist/src/ui/utils/CodeColorizer.js +23 -11
  191. package/dist/src/ui/utils/CodeColorizer.js.map +1 -1
  192. package/dist/src/ui/utils/commandUtils.d.ts +1 -0
  193. package/dist/src/ui/utils/commandUtils.js +47 -0
  194. package/dist/src/ui/utils/commandUtils.js.map +1 -1
  195. package/dist/src/ui/utils/markdownUtilities.js +1 -1
  196. package/dist/src/utils/sandbox.js +19 -19
  197. package/dist/src/utils/sandbox.js.map +1 -1
  198. package/dist/src/utils/userStartupWarnings.js +22 -1
  199. package/dist/src/utils/userStartupWarnings.js.map +1 -1
  200. package/dist/src/validateNonInterActiveAuth.d.ts +7 -0
  201. package/dist/src/validateNonInterActiveAuth.js +61 -0
  202. package/dist/src/validateNonInterActiveAuth.js.map +1 -0
  203. package/dist/tsconfig.tsbuildinfo +1 -1
  204. package/package.json +7 -5
@@ -5,23 +5,17 @@
5
5
  */
6
6
  import { useCallback, useMemo, useEffect, useState } from 'react';
7
7
  import process from 'node:process';
8
- import { ansi } from '../colors.js';
9
8
  import { useStateAndRef } from './useStateAndRef.js';
10
- import { GitService, Logger, MCPDiscoveryState, MCPServerStatus, getMCPDiscoveryState, getMCPServerStatus, AuthType, } from '@vybestack/llxprt-code-core';
9
+ import { GitService, Logger } from '@vybestack/llxprt-code-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';
19
- import { getProviderManager } from '../../providers/providerManagerInstance.js';
20
- import open from 'open';
13
+ import { BuiltinCommandLoader } from '../../services/BuiltinCommandLoader.js';
14
+ import { FileCommandLoader } from '../../services/FileCommandLoader.js';
21
15
  /**
22
16
  * Hook to define and process slash commands (e.g., /help, /clear).
23
17
  */
24
- export const useSlashCommandProcessor = (config, settings, addItem, clearItems, loadHistory, refreshStatic, setShowHelp, onDebugMessage, openThemeDialog, openAuthDialog, openEditorDialog, openProviderDialog, openProviderModelDialog, performMemoryRefresh, setQuittingMessages, openPrivacyNotice, checkPaymentModeChange, showToolDescriptions) => {
18
+ export const useSlashCommandProcessor = (config, settings, addItem, clearItems, loadHistory, refreshStatic, setShowHelp, onDebugMessage, openThemeDialog, openAuthDialog, openEditorDialog, openProviderDialog, openProviderModelDialog, performMemoryRefresh, setQuittingMessages, openPrivacyNotice, checkPaymentModeChange, showToolDescriptions, toggleVimEnabled) => {
25
19
  const session = useSessionStats();
26
20
  const [commands, setCommands] = useState([]);
27
21
  const gitService = useMemo(() => {
@@ -96,10 +90,6 @@ export const useSlashCommandProcessor = (config, settings, addItem, clearItems,
96
90
  }
97
91
  addItem(historyItemContent, message.timestamp.getTime());
98
92
  }, [addItem]);
99
- // const showMemoryAction = useMemo(
100
- // () => createShowMemoryAction(config, settings, addMessage),
101
- // [config, settings, addMessage],
102
- // );
103
93
  const commandContext = useMemo(() => ({
104
94
  services: {
105
95
  config,
@@ -118,6 +108,7 @@ export const useSlashCommandProcessor = (config, settings, addItem, clearItems,
118
108
  setDebugMessage: onDebugMessage,
119
109
  pendingItem: pendingCompressionItemRef.current,
120
110
  setPendingItem: setPendingCompressionItem,
111
+ toggleVimEnabled,
121
112
  },
122
113
  session: {
123
114
  stats: session.stats,
@@ -135,597 +126,23 @@ export const useSlashCommandProcessor = (config, settings, addItem, clearItems,
135
126
  onDebugMessage,
136
127
  pendingCompressionItemRef,
137
128
  setPendingCompressionItem,
129
+ toggleVimEnabled,
138
130
  ]);
139
- const commandService = useMemo(() => new CommandService(config), [config]);
140
131
  useEffect(() => {
132
+ const controller = new AbortController();
141
133
  const load = async () => {
142
- await commandService.loadCommands();
134
+ const loaders = [
135
+ new BuiltinCommandLoader(config),
136
+ new FileCommandLoader(config),
137
+ ];
138
+ const commandService = await CommandService.create(loaders, controller.signal);
143
139
  setCommands(commandService.getCommands());
144
140
  };
145
141
  load();
146
- }, [commandService]);
147
- const _savedChatTags = useCallback(async () => {
148
- const geminiDir = config?.getProjectTempDir();
149
- if (!geminiDir) {
150
- return [];
151
- }
152
- try {
153
- const files = await fs.readdir(geminiDir);
154
- return files
155
- .filter((file) => file.startsWith('checkpoint-') && file.endsWith('.json'))
156
- .map((file) => file.replace('checkpoint-', '').replace('.json', ''));
157
- }
158
- catch (_err) {
159
- return [];
160
- }
142
+ return () => {
143
+ controller.abort();
144
+ };
161
145
  }, [config]);
162
- // Define legacy commands
163
- // This list contains all commands that have NOT YET been migrated to the
164
- // new system. As commands are migrated, they are removed from this list.
165
- const _legacyCommands = useMemo(() => {
166
- const commands = [
167
- // `/help` and `/clear` have been migrated and REMOVED from this list.
168
- {
169
- name: 'docs',
170
- description: 'open full LLxprt Code documentation in your browser',
171
- action: async (_context, _args) => {
172
- const docsUrl = 'https://goo.gle/gemini-cli-docs';
173
- if (process.env.SANDBOX && process.env.SANDBOX !== 'sandbox-exec') {
174
- addMessage({
175
- type: MessageType.INFO,
176
- content: `Please open the following URL in your browser to view the documentation:\n${docsUrl}`,
177
- timestamp: new Date(),
178
- });
179
- }
180
- else {
181
- addMessage({
182
- type: MessageType.INFO,
183
- content: `Opening documentation in your browser: ${docsUrl}`,
184
- timestamp: new Date(),
185
- });
186
- await open(docsUrl);
187
- }
188
- },
189
- },
190
- {
191
- name: 'auth',
192
- description: 'change the auth method',
193
- action: async (_context, args) => {
194
- const authMode = args?.split(' ')[0];
195
- const providerManager = getProviderManager();
196
- // If no auth mode specified, open the dialog
197
- if (!authMode) {
198
- openAuthDialog();
199
- return;
200
- }
201
- // Handle specific auth mode changes for Gemini provider
202
- try {
203
- const activeProvider = providerManager.getActiveProvider();
204
- // Check if this is the Gemini provider
205
- if (activeProvider.name === 'gemini' && config) {
206
- const validModes = ['oauth', 'api-key', 'vertex'];
207
- if (!validModes.includes(authMode)) {
208
- addMessage({
209
- type: MessageType.ERROR,
210
- content: `Invalid auth mode. Valid modes: ${validModes.join(', ')}`,
211
- timestamp: new Date(),
212
- });
213
- return;
214
- }
215
- // Map the auth mode to the appropriate AuthType
216
- let authType;
217
- switch (authMode) {
218
- case 'oauth':
219
- authType = AuthType.LOGIN_WITH_GOOGLE;
220
- break;
221
- case 'api-key':
222
- authType = AuthType.USE_GEMINI;
223
- break;
224
- case 'vertex':
225
- authType = AuthType.USE_VERTEX_AI;
226
- break;
227
- default:
228
- authType = AuthType.LOGIN_WITH_GOOGLE;
229
- }
230
- // Refresh auth with the new type
231
- await config.refreshAuth(authType);
232
- addMessage({
233
- type: MessageType.INFO,
234
- content: `Switched to ${authMode} authentication mode`,
235
- timestamp: new Date(),
236
- });
237
- }
238
- else {
239
- addMessage({
240
- type: MessageType.ERROR,
241
- content: 'Auth mode switching is only supported for the Gemini provider',
242
- timestamp: new Date(),
243
- });
244
- }
245
- }
246
- catch (error) {
247
- addMessage({
248
- type: MessageType.ERROR,
249
- content: `Failed to switch auth mode: ${error instanceof Error ? error.message : String(error)}`,
250
- timestamp: new Date(),
251
- });
252
- }
253
- },
254
- },
255
- {
256
- name: 'editor',
257
- description: 'set external editor preference',
258
- action: (_context, _args) => ({
259
- type: 'dialog',
260
- dialog: 'editor',
261
- }),
262
- },
263
- {
264
- name: 'stats',
265
- altName: 'usage',
266
- description: 'check session stats. Usage: /stats [model|tools]',
267
- action: (_context, args) => {
268
- const subCommand = args?.split(' ')[0];
269
- if (subCommand === 'model') {
270
- addMessage({
271
- type: MessageType.MODEL_STATS,
272
- timestamp: new Date(),
273
- });
274
- return;
275
- }
276
- else if (subCommand === 'tools') {
277
- addMessage({
278
- type: MessageType.TOOL_STATS,
279
- timestamp: new Date(),
280
- });
281
- return;
282
- }
283
- const now = new Date();
284
- const { sessionStartTime } = session.stats;
285
- const wallDuration = now.getTime() - sessionStartTime.getTime();
286
- addMessage({
287
- type: MessageType.STATS,
288
- duration: formatDuration(wallDuration),
289
- timestamp: new Date(),
290
- });
291
- },
292
- },
293
- {
294
- name: 'mcp',
295
- description: 'list configured MCP servers and tools',
296
- action: async (_context, args) => {
297
- // Check if the args includes a specific flag to control description visibility
298
- const [subCommand, ...rest] = args?.split(' ') || [];
299
- const remainingArgs = rest.join(' ');
300
- let useShowDescriptions = showToolDescriptions;
301
- if (subCommand === 'desc' || subCommand === 'descriptions') {
302
- useShowDescriptions = true;
303
- }
304
- else if (subCommand === 'nodesc' ||
305
- subCommand === 'nodescriptions') {
306
- useShowDescriptions = false;
307
- }
308
- else if (remainingArgs === 'desc' ||
309
- remainingArgs === 'descriptions') {
310
- useShowDescriptions = true;
311
- }
312
- else if (remainingArgs === 'nodesc' ||
313
- remainingArgs === 'nodescriptions') {
314
- useShowDescriptions = false;
315
- }
316
- // Check if the args includes a specific flag to show detailed tool schema
317
- let useShowSchema = false;
318
- if (subCommand === 'schema' || remainingArgs === 'schema') {
319
- useShowSchema = true;
320
- }
321
- const toolRegistry = await config?.getToolRegistry();
322
- if (!toolRegistry) {
323
- addMessage({
324
- type: MessageType.ERROR,
325
- content: 'Could not retrieve tool registry.',
326
- timestamp: new Date(),
327
- });
328
- return;
329
- }
330
- const mcpServers = config?.getMcpServers() || {};
331
- const serverNames = Object.keys(mcpServers);
332
- if (serverNames.length === 0) {
333
- const docsUrl = 'https://goo.gle/gemini-cli-docs-mcp';
334
- if (process.env.SANDBOX && process.env.SANDBOX !== 'sandbox-exec') {
335
- addMessage({
336
- type: MessageType.INFO,
337
- content: `No MCP servers configured. Please open the following URL in your browser to view documentation:\n${docsUrl}`,
338
- timestamp: new Date(),
339
- });
340
- }
341
- else {
342
- addMessage({
343
- type: MessageType.INFO,
344
- content: `No MCP servers configured. Opening documentation in your browser: ${docsUrl}`,
345
- timestamp: new Date(),
346
- });
347
- await open(docsUrl);
348
- }
349
- return;
350
- }
351
- // Check if any servers are still connecting
352
- const connectingServers = serverNames.filter((name) => getMCPServerStatus(name) === MCPServerStatus.CONNECTING);
353
- const discoveryState = getMCPDiscoveryState();
354
- let message = '';
355
- // Add overall discovery status message if needed
356
- if (discoveryState === MCPDiscoveryState.IN_PROGRESS ||
357
- connectingServers.length > 0) {
358
- message +=
359
- ansi.accentYellow(`⏳ MCP servers are starting up (${connectingServers.length} initializing)...`) + '\n';
360
- message +=
361
- ansi.gray('Note: First startup may take longer. Tool availability will update automatically.') + '\n\n';
362
- }
363
- message += 'Configured MCP servers:\n\n';
364
- for (const serverName of serverNames) {
365
- const serverTools = toolRegistry.getToolsByServer(serverName);
366
- const status = getMCPServerStatus(serverName);
367
- // Add status indicator with descriptive text
368
- let statusIndicator = '';
369
- let statusText = '';
370
- switch (status) {
371
- case MCPServerStatus.CONNECTED:
372
- statusIndicator = '🟢';
373
- statusText = 'Ready';
374
- break;
375
- case MCPServerStatus.CONNECTING:
376
- statusIndicator = '🔄';
377
- statusText = 'Starting... (first startup may take longer)';
378
- break;
379
- case MCPServerStatus.DISCONNECTED:
380
- default:
381
- statusIndicator = '🔴';
382
- statusText = 'Disconnected';
383
- break;
384
- }
385
- // Get server description if available
386
- const server = mcpServers[serverName];
387
- // Format server header with bold formatting and status
388
- message += `${statusIndicator} ${ansi.bold(serverName)} - ${statusText}`;
389
- // Add tool count with conditional messaging
390
- if (status === MCPServerStatus.CONNECTED) {
391
- message += ` (${serverTools.length} tools)`;
392
- }
393
- else if (status === MCPServerStatus.CONNECTING) {
394
- message += ` (tools will appear when ready)`;
395
- }
396
- else {
397
- message += ` (${serverTools.length} tools cached)`;
398
- }
399
- // Add server description with proper handling of multi-line descriptions
400
- if ((useShowDescriptions || useShowSchema) && server?.description) {
401
- const descLines = server.description.trim().split('\n');
402
- if (descLines) {
403
- message += ':\n';
404
- for (const descLine of descLines) {
405
- message += ` ${ansi.accentGreen(descLine)}\n`;
406
- }
407
- }
408
- else {
409
- message += '\n';
410
- }
411
- }
412
- else {
413
- message += '\n';
414
- }
415
- if (serverTools.length > 0) {
416
- serverTools.forEach((tool) => {
417
- if ((useShowDescriptions || useShowSchema) &&
418
- tool.description) {
419
- // Format tool name in cyan using simple ANSI cyan color
420
- message += ` - ${ansi.accentCyan(tool.name)}`;
421
- // Handle multi-line descriptions by properly indenting and preserving formatting
422
- const descLines = tool.description.trim().split('\n');
423
- if (descLines) {
424
- message += ':\n';
425
- for (const descLine of descLines) {
426
- message += ` ${ansi.accentGreen(descLine)}\n`;
427
- }
428
- }
429
- else {
430
- message += '\n';
431
- }
432
- }
433
- else {
434
- // Use cyan color for the tool name even when not showing descriptions
435
- message += ` - ${ansi.accentCyan(tool.name)}\n`;
436
- }
437
- if (useShowSchema) {
438
- // Prefix the parameters in cyan
439
- message += ` ${ansi.accentCyan('Parameters')}:\n`;
440
- const paramsLines = JSON.stringify(tool.schema.parameters, null, 2)
441
- .trim()
442
- .split('\n');
443
- if (paramsLines) {
444
- for (const paramsLine of paramsLines) {
445
- message += ` ${ansi.accentGreen(paramsLine)}\n`;
446
- }
447
- }
448
- }
449
- });
450
- }
451
- else {
452
- message += ' No tools available\n';
453
- }
454
- message += '\n';
455
- }
456
- addMessage({
457
- type: MessageType.INFO,
458
- content: message,
459
- timestamp: new Date(),
460
- });
461
- },
462
- },
463
- {
464
- name: 'extensions',
465
- description: 'list active extensions',
466
- action: async () => {
467
- const activeExtensions = config?.getActiveExtensions();
468
- if (!activeExtensions || activeExtensions.length === 0) {
469
- addMessage({
470
- type: MessageType.INFO,
471
- content: 'No active extensions.',
472
- timestamp: new Date(),
473
- });
474
- return;
475
- }
476
- let message = 'Active extensions:\n\n';
477
- for (const ext of activeExtensions) {
478
- message += ` - \u001b[36m${ext.name} (v${ext.version})\u001b[0m\n`;
479
- }
480
- // Make sure to reset any ANSI formatting at the end to prevent it from affecting the terminal
481
- message += '\u001b[0m';
482
- addMessage({
483
- type: MessageType.INFO,
484
- content: message,
485
- timestamp: new Date(),
486
- });
487
- },
488
- },
489
- {
490
- name: 'tools',
491
- description: 'list available LLxprt Code tools',
492
- action: async (_context, args) => {
493
- // Check if the args includes a specific flag to control description visibility
494
- const [subCommand, ...rest] = args?.split(' ') || [];
495
- const remainingArgs = rest.join(' ');
496
- let useShowDescriptions = showToolDescriptions;
497
- if (subCommand === 'desc' || subCommand === 'descriptions') {
498
- useShowDescriptions = true;
499
- }
500
- else if (subCommand === 'nodesc' ||
501
- subCommand === 'nodescriptions') {
502
- useShowDescriptions = false;
503
- }
504
- else if (remainingArgs === 'desc' ||
505
- remainingArgs === 'descriptions') {
506
- useShowDescriptions = true;
507
- }
508
- else if (remainingArgs === 'nodesc' ||
509
- remainingArgs === 'nodescriptions') {
510
- useShowDescriptions = false;
511
- }
512
- const toolRegistry = await config?.getToolRegistry();
513
- const tools = toolRegistry?.getAllTools();
514
- if (!tools) {
515
- addMessage({
516
- type: MessageType.ERROR,
517
- content: 'Could not retrieve tools.',
518
- timestamp: new Date(),
519
- });
520
- return;
521
- }
522
- // Filter out MCP tools by checking if they have a serverName property
523
- const geminiTools = tools.filter((tool) => !('serverName' in tool));
524
- let message = 'Available Gemini CLI tools:\n\n';
525
- if (geminiTools.length > 0) {
526
- geminiTools.forEach((tool) => {
527
- if (useShowDescriptions && tool.description) {
528
- // Format tool name in cyan using simple ANSI cyan color
529
- message += ` - ${ansi.accentCyan(`${tool.displayName} (${tool.name})`)}:\n`;
530
- // Handle multi-line descriptions by properly indenting and preserving formatting
531
- const descLines = tool.description.trim().split('\n');
532
- // If there are multiple lines, add proper indentation for each line
533
- if (descLines) {
534
- for (const descLine of descLines) {
535
- message += ` ${ansi.accentGreen(descLine)}\n`;
536
- }
537
- }
538
- }
539
- else {
540
- // Use cyan color for the tool name even when not showing descriptions
541
- message += ` - ${ansi.accentCyan(tool.displayName)}\n`;
542
- }
543
- });
544
- }
545
- else {
546
- message += ' No tools available\n';
547
- }
548
- message += '\n';
549
- addMessage({
550
- type: MessageType.INFO,
551
- content: message,
552
- timestamp: new Date(),
553
- });
554
- },
555
- },
556
- {
557
- name: 'bug',
558
- description: 'submit a bug report',
559
- action: async (_context, args) => {
560
- const bugDescription = args?.trim() || '';
561
- const osVersion = `${process.platform} ${process.version}`;
562
- let sandboxEnv = 'no sandbox';
563
- if (process.env.SANDBOX && process.env.SANDBOX !== 'sandbox-exec') {
564
- sandboxEnv = process.env.SANDBOX.replace(/^gemini-(?:code-)?/, '');
565
- }
566
- else if (process.env.SANDBOX === 'sandbox-exec') {
567
- sandboxEnv = `sandbox-exec (${process.env.SEATBELT_PROFILE || 'unknown'})`;
568
- }
569
- const modelVersion = config?.getModel() || 'Unknown';
570
- const cliVersion = await getCliVersion();
571
- const memoryUsage = formatMemoryUsage(process.memoryUsage().rss);
572
- const info = `
573
- * **CLI Version:** ${cliVersion}
574
- * **Git Commit:** ${GIT_COMMIT_INFO}
575
- * **Operating System:** ${osVersion}
576
- * **Sandbox Environment:** ${sandboxEnv}
577
- * **Model Version:** ${modelVersion}
578
- * **Memory Usage:** ${memoryUsage}
579
- `;
580
- let bugReportUrl = 'https://github.com/acoliver/llxprt-code/issues/new?template=bug_report.yml&title={title}&info={info}';
581
- const bugCommand = config?.getBugCommand();
582
- if (bugCommand?.urlTemplate) {
583
- bugReportUrl = bugCommand.urlTemplate;
584
- }
585
- bugReportUrl = bugReportUrl
586
- .replace('{title}', encodeURIComponent(bugDescription))
587
- .replace('{info}', encodeURIComponent(info));
588
- addMessage({
589
- type: MessageType.INFO,
590
- content: `To submit your bug report, please open the following URL in your browser:\n${bugReportUrl}`,
591
- timestamp: new Date(),
592
- });
593
- (async () => {
594
- try {
595
- await open(bugReportUrl);
596
- }
597
- catch (error) {
598
- const errorMessage = error instanceof Error ? error.message : String(error);
599
- addMessage({
600
- type: MessageType.ERROR,
601
- content: `Could not open URL in browser: ${errorMessage}`,
602
- timestamp: new Date(),
603
- });
604
- }
605
- })();
606
- },
607
- },
608
- ];
609
- if (config?.getCheckpointingEnabled()) {
610
- commands.push({
611
- name: 'restore',
612
- 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',
613
- completion: async () => {
614
- const checkpointDir = config?.getProjectTempDir()
615
- ? path.join(config.getProjectTempDir(), 'checkpoints')
616
- : undefined;
617
- if (!checkpointDir) {
618
- return [];
619
- }
620
- try {
621
- const files = await fs.readdir(checkpointDir);
622
- return files
623
- .filter((file) => file.endsWith('.json'))
624
- .map((file) => file.replace('.json', ''));
625
- }
626
- catch (_err) {
627
- return [];
628
- }
629
- },
630
- action: async (_context, args) => {
631
- const subCommand = args?.split(' ')[0];
632
- const checkpointDir = config?.getProjectTempDir()
633
- ? path.join(config.getProjectTempDir(), 'checkpoints')
634
- : undefined;
635
- if (!checkpointDir) {
636
- addMessage({
637
- type: MessageType.ERROR,
638
- content: 'Could not determine the .llxprt directory path.',
639
- timestamp: new Date(),
640
- });
641
- return;
642
- }
643
- try {
644
- // Ensure the directory exists before trying to read it.
645
- await fs.mkdir(checkpointDir, { recursive: true });
646
- const files = await fs.readdir(checkpointDir);
647
- const jsonFiles = files.filter((file) => file.endsWith('.json'));
648
- if (!subCommand) {
649
- if (jsonFiles.length === 0) {
650
- addMessage({
651
- type: MessageType.INFO,
652
- content: 'No restorable tool calls found.',
653
- timestamp: new Date(),
654
- });
655
- return;
656
- }
657
- const truncatedFiles = jsonFiles.map((file) => {
658
- const components = file.split('.');
659
- if (components.length <= 1) {
660
- return file;
661
- }
662
- components.pop();
663
- return components.join('.');
664
- });
665
- const fileList = truncatedFiles.join('\n');
666
- addMessage({
667
- type: MessageType.INFO,
668
- content: `Available tool calls to restore:\n\n${fileList}`,
669
- timestamp: new Date(),
670
- });
671
- return;
672
- }
673
- const selectedFile = subCommand.endsWith('.json')
674
- ? subCommand
675
- : `${subCommand}.json`;
676
- if (!jsonFiles.includes(selectedFile)) {
677
- addMessage({
678
- type: MessageType.ERROR,
679
- content: `File not found: ${selectedFile}`,
680
- timestamp: new Date(),
681
- });
682
- return;
683
- }
684
- const filePath = path.join(checkpointDir, selectedFile);
685
- const data = await fs.readFile(filePath, 'utf-8');
686
- const toolCallData = JSON.parse(data);
687
- if (toolCallData.history) {
688
- loadHistory(toolCallData.history);
689
- }
690
- if (toolCallData.clientHistory) {
691
- await config
692
- ?.getGeminiClient()
693
- ?.setHistory(toolCallData.clientHistory);
694
- }
695
- if (toolCallData.commitHash) {
696
- await gitService?.restoreProjectFromSnapshot(toolCallData.commitHash);
697
- addMessage({
698
- type: MessageType.INFO,
699
- content: `Restored project to the state before the tool call.`,
700
- timestamp: new Date(),
701
- });
702
- }
703
- return {
704
- type: 'tool',
705
- toolName: toolCallData.toolCall.name,
706
- toolArgs: toolCallData.toolCall.args,
707
- };
708
- }
709
- catch (error) {
710
- addMessage({
711
- type: MessageType.ERROR,
712
- content: `Could not read restorable tool calls. This is the error: ${error}`,
713
- timestamp: new Date(),
714
- });
715
- }
716
- },
717
- });
718
- }
719
- return commands;
720
- }, [
721
- addMessage,
722
- openAuthDialog,
723
- config,
724
- session,
725
- gitService,
726
- loadHistory,
727
- showToolDescriptions,
728
- ]);
729
146
  const handleSlashCommand = useCallback(async (rawQuery) => {
730
147
  if (typeof rawQuery !== 'string') {
731
148
  return false;
@@ -735,16 +152,24 @@ export const useSlashCommandProcessor = (config, settings, addItem, clearItems,
735
152
  return false;
736
153
  }
737
154
  const userMessageTimestamp = Date.now();
738
- if (trimmed !== '/quit' && trimmed !== '/exit') {
739
- addItem({ type: MessageType.USER, text: trimmed }, userMessageTimestamp);
740
- }
155
+ addItem({ type: MessageType.USER, text: trimmed }, userMessageTimestamp);
741
156
  const parts = trimmed.substring(1).trim().split(/\s+/);
742
157
  const commandPath = parts.filter((p) => p); // The parts of the command, e.g., ['memory', 'add']
743
158
  let currentCommands = commands;
744
159
  let commandToExecute;
745
160
  let pathIndex = 0;
746
161
  for (const part of commandPath) {
747
- const foundCommand = currentCommands.find((cmd) => cmd.name === part || cmd.altName === part);
162
+ // TODO: For better performance and architectural clarity, this two-pass
163
+ // search could be replaced. A more optimal approach would be to
164
+ // pre-compute a single lookup map in `CommandService.ts` that resolves
165
+ // all name and alias conflicts during the initial loading phase. The
166
+ // processor would then perform a single, fast lookup on that map.
167
+ // First pass: check for an exact match on the primary command name.
168
+ let foundCommand = currentCommands.find((cmd) => cmd.name === part);
169
+ // Second pass: if no primary name matches, check for an alias.
170
+ if (!foundCommand) {
171
+ foundCommand = currentCommands.find((cmd) => cmd.altNames?.includes(part));
172
+ }
748
173
  if (foundCommand) {
749
174
  commandToExecute = foundCommand;
750
175
  pathIndex++;
@@ -762,7 +187,15 @@ export const useSlashCommandProcessor = (config, settings, addItem, clearItems,
762
187
  if (commandToExecute) {
763
188
  const args = parts.slice(pathIndex).join(' ');
764
189
  if (commandToExecute.action) {
765
- const result = await commandToExecute.action(commandContext, args);
190
+ const fullCommandContext = {
191
+ ...commandContext,
192
+ invocation: {
193
+ raw: trimmed,
194
+ name: commandToExecute.name,
195
+ args,
196
+ },
197
+ };
198
+ const result = await commandToExecute.action(fullCommandContext, args);
766
199
  if (result) {
767
200
  switch (result.type) {
768
201
  case 'tool':
@@ -811,9 +244,9 @@ export const useSlashCommandProcessor = (config, settings, addItem, clearItems,
811
244
  await config
812
245
  ?.getGeminiClient()
813
246
  ?.setHistory(result.clientHistory);
814
- commandContext.ui.clear();
247
+ fullCommandContext.ui.clear();
815
248
  result.history.forEach((item, index) => {
816
- commandContext.ui.addItem(item, index);
249
+ fullCommandContext.ui.addItem(item, index);
817
250
  });
818
251
  return { type: 'handled' };
819
252
  }
@@ -823,6 +256,11 @@ export const useSlashCommandProcessor = (config, settings, addItem, clearItems,
823
256
  process.exit(0);
824
257
  }, 100);
825
258
  return { type: 'handled' };
259
+ case 'submit_prompt':
260
+ return {
261
+ type: 'submit_prompt',
262
+ content: result.content,
263
+ };
826
264
  default: {
827
265
  const unhandled = result;
828
266
  throw new Error(`Unhandled slash command result: ${unhandled}`);