@google/gemini-cli 0.10.0-preview.2 → 0.11.0-nightly.20251020.a96f0659

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 (417) hide show
  1. package/README.md +9 -0
  2. package/dist/google-gemini-cli-0.11.0-nightly.20251015.203bad7c.tgz +0 -0
  3. package/dist/package.json +2 -2
  4. package/dist/src/commands/extensions/examples/mcp-server/example.d.ts +6 -0
  5. package/dist/src/commands/extensions/examples/mcp-server/example.js +46 -0
  6. package/dist/src/commands/extensions/examples/mcp-server/example.js.map +1 -0
  7. package/dist/src/commands/extensions/install.d.ts +1 -0
  8. package/dist/src/commands/extensions/install.js +15 -2
  9. package/dist/src/commands/extensions/install.js.map +1 -1
  10. package/dist/src/commands/extensions/list.js +3 -2
  11. package/dist/src/commands/extensions/list.js.map +1 -1
  12. package/dist/src/commands/extensions/update.js +2 -2
  13. package/dist/src/commands/extensions/update.js.map +1 -1
  14. package/dist/src/commands/mcp/add.test.d.ts +6 -0
  15. package/dist/src/commands/mcp/add.test.js +234 -0
  16. package/dist/src/commands/mcp/add.test.js.map +1 -0
  17. package/dist/src/commands/mcp/list.js +5 -3
  18. package/dist/src/commands/mcp/list.js.map +1 -1
  19. package/dist/src/commands/mcp/list.test.d.ts +6 -0
  20. package/dist/src/commands/mcp/list.test.js +117 -0
  21. package/dist/src/commands/mcp/list.test.js.map +1 -0
  22. package/dist/src/commands/mcp/remove.test.d.ts +6 -0
  23. package/dist/src/commands/mcp/remove.test.js +175 -0
  24. package/dist/src/commands/mcp/remove.test.js.map +1 -0
  25. package/dist/src/commands/mcp.test.d.ts +6 -0
  26. package/dist/src/commands/mcp.test.js +62 -0
  27. package/dist/src/commands/mcp.test.js.map +1 -0
  28. package/dist/src/config/auth.js +3 -1
  29. package/dist/src/config/auth.js.map +1 -1
  30. package/dist/src/config/auth.test.js +3 -1
  31. package/dist/src/config/auth.test.js.map +1 -1
  32. package/dist/src/config/config.d.ts +0 -11
  33. package/dist/src/config/config.integration.test.d.ts +6 -0
  34. package/dist/src/config/config.integration.test.js +351 -0
  35. package/dist/src/config/config.integration.test.js.map +1 -0
  36. package/dist/src/config/config.js +17 -74
  37. package/dist/src/config/config.js.map +1 -1
  38. package/dist/src/config/config.test.d.ts +6 -0
  39. package/dist/src/config/config.test.js +2001 -0
  40. package/dist/src/config/config.test.js.map +1 -0
  41. package/dist/src/config/extension.d.ts +1 -4
  42. package/dist/src/config/extension.js +64 -69
  43. package/dist/src/config/extension.js.map +1 -1
  44. package/dist/src/config/extension.test.d.ts +6 -0
  45. package/dist/src/config/extension.test.js +1176 -0
  46. package/dist/src/config/extension.test.js.map +1 -0
  47. package/dist/src/config/extensions/extensionEnablement.d.ts +1 -1
  48. package/dist/src/config/extensions/extensionEnablement.js +4 -3
  49. package/dist/src/config/extensions/extensionEnablement.js.map +1 -1
  50. package/dist/src/config/extensions/extensionEnablement.test.js +21 -18
  51. package/dist/src/config/extensions/extensionEnablement.test.js.map +1 -1
  52. package/dist/src/config/extensions/github.d.ts +15 -7
  53. package/dist/src/config/extensions/github.js +95 -21
  54. package/dist/src/config/extensions/github.js.map +1 -1
  55. package/dist/src/config/extensions/github.test.js +14 -12
  56. package/dist/src/config/extensions/github.test.js.map +1 -1
  57. package/dist/src/config/extensions/update.test.js +9 -9
  58. package/dist/src/config/extensions/update.test.js.map +1 -1
  59. package/dist/src/config/keyBindings.d.ts +2 -1
  60. package/dist/src/config/keyBindings.js +4 -2
  61. package/dist/src/config/keyBindings.js.map +1 -1
  62. package/dist/src/config/policy.js +13 -14
  63. package/dist/src/config/policy.js.map +1 -1
  64. package/dist/src/config/policy.test.js +3 -3
  65. package/dist/src/config/policy.test.js.map +1 -1
  66. package/dist/src/config/sandboxConfig.d.ts +0 -1
  67. package/dist/src/config/sandboxConfig.js +1 -3
  68. package/dist/src/config/sandboxConfig.js.map +1 -1
  69. package/dist/src/config/settings.test.d.ts +6 -0
  70. package/dist/src/config/settings.test.js +1937 -0
  71. package/dist/src/config/settings.test.js.map +1 -0
  72. package/dist/src/gemini.js +12 -10
  73. package/dist/src/gemini.js.map +1 -1
  74. package/dist/src/gemini.test.js +34 -11
  75. package/dist/src/gemini.test.js.map +1 -1
  76. package/dist/src/generated/git-commit.d.ts +2 -2
  77. package/dist/src/generated/git-commit.js +2 -2
  78. package/dist/src/generated/git-commit.js.map +1 -1
  79. package/dist/src/nonInteractiveCli.js +91 -3
  80. package/dist/src/nonInteractiveCli.js.map +1 -1
  81. package/dist/src/nonInteractiveCli.test.d.ts +6 -0
  82. package/dist/src/nonInteractiveCli.test.js +711 -0
  83. package/dist/src/nonInteractiveCli.test.js.map +1 -0
  84. package/dist/src/services/FileCommandLoader.test.d.ts +6 -0
  85. package/dist/src/services/FileCommandLoader.test.js +971 -0
  86. package/dist/src/services/FileCommandLoader.test.js.map +1 -0
  87. package/dist/src/services/prompt-processors/argumentProcessor.test.d.ts +6 -0
  88. package/dist/src/services/prompt-processors/argumentProcessor.test.js +40 -0
  89. package/dist/src/services/prompt-processors/argumentProcessor.test.js.map +1 -0
  90. package/dist/src/services/prompt-processors/shellProcessor.test.d.ts +6 -0
  91. package/dist/src/services/prompt-processors/shellProcessor.test.js +482 -0
  92. package/dist/src/services/prompt-processors/shellProcessor.test.js.map +1 -0
  93. package/dist/src/test-utils/render.d.ts +2 -1
  94. package/dist/src/test-utils/render.js +5 -2
  95. package/dist/src/test-utils/render.js.map +1 -1
  96. package/dist/src/ui/App.test.d.ts +6 -0
  97. package/dist/src/ui/App.test.js +110 -0
  98. package/dist/src/ui/App.test.js.map +1 -0
  99. package/dist/src/ui/AppContainer.js +39 -26
  100. package/dist/src/ui/AppContainer.js.map +1 -1
  101. package/dist/src/ui/AppContainer.test.js +35 -9
  102. package/dist/src/ui/AppContainer.test.js.map +1 -1
  103. package/dist/src/ui/auth/AuthDialog.d.ts +1 -1
  104. package/dist/src/ui/auth/AuthDialog.js +3 -1
  105. package/dist/src/ui/auth/AuthDialog.js.map +1 -1
  106. package/dist/src/ui/auth/useAuth.d.ts +1 -1
  107. package/dist/src/ui/auth/useAuth.js +3 -1
  108. package/dist/src/ui/auth/useAuth.js.map +1 -1
  109. package/dist/src/ui/commands/aboutCommand.js +1 -1
  110. package/dist/src/ui/commands/aboutCommand.test.d.ts +6 -0
  111. package/dist/src/ui/commands/aboutCommand.test.js +130 -0
  112. package/dist/src/ui/commands/aboutCommand.test.js.map +1 -0
  113. package/dist/src/ui/commands/authCommand.js +1 -1
  114. package/dist/src/ui/commands/authCommand.test.d.ts +6 -0
  115. package/dist/src/ui/commands/authCommand.test.js +30 -0
  116. package/dist/src/ui/commands/authCommand.test.js.map +1 -0
  117. package/dist/src/ui/commands/bugCommand.js +1 -1
  118. package/dist/src/ui/commands/bugCommand.test.d.ts +6 -0
  119. package/dist/src/ui/commands/bugCommand.test.js +105 -0
  120. package/dist/src/ui/commands/bugCommand.test.js.map +1 -0
  121. package/dist/src/ui/commands/chatCommand.js +1 -1
  122. package/dist/src/ui/commands/chatCommand.js.map +1 -1
  123. package/dist/src/ui/commands/chatCommand.test.d.ts +6 -0
  124. package/dist/src/ui/commands/chatCommand.test.js +555 -0
  125. package/dist/src/ui/commands/chatCommand.test.js.map +1 -0
  126. package/dist/src/ui/commands/clearCommand.js +1 -1
  127. package/dist/src/ui/commands/clearCommand.test.d.ts +6 -0
  128. package/dist/src/ui/commands/clearCommand.test.js +76 -0
  129. package/dist/src/ui/commands/clearCommand.test.js.map +1 -0
  130. package/dist/src/ui/commands/compressCommand.js +1 -1
  131. package/dist/src/ui/commands/compressCommand.js.map +1 -1
  132. package/dist/src/ui/commands/compressCommand.test.d.ts +6 -0
  133. package/dist/src/ui/commands/compressCommand.test.js +98 -0
  134. package/dist/src/ui/commands/compressCommand.test.js.map +1 -0
  135. package/dist/src/ui/commands/copyCommand.test.d.ts +6 -0
  136. package/dist/src/ui/commands/copyCommand.test.js +242 -0
  137. package/dist/src/ui/commands/copyCommand.test.js.map +1 -0
  138. package/dist/src/ui/commands/corgiCommand.js +1 -1
  139. package/dist/src/ui/commands/corgiCommand.js.map +1 -1
  140. package/dist/src/ui/commands/corgiCommand.test.d.ts +6 -0
  141. package/dist/src/ui/commands/corgiCommand.test.js +28 -0
  142. package/dist/src/ui/commands/corgiCommand.test.js.map +1 -0
  143. package/dist/src/ui/commands/directoryCommand.test.d.ts +6 -0
  144. package/dist/src/ui/commands/directoryCommand.test.js +145 -0
  145. package/dist/src/ui/commands/directoryCommand.test.js.map +1 -0
  146. package/dist/src/ui/commands/docsCommand.js +1 -1
  147. package/dist/src/ui/commands/docsCommand.test.d.ts +6 -0
  148. package/dist/src/ui/commands/docsCommand.test.js +72 -0
  149. package/dist/src/ui/commands/docsCommand.test.js.map +1 -0
  150. package/dist/src/ui/commands/editorCommand.js +1 -1
  151. package/dist/src/ui/commands/editorCommand.test.d.ts +6 -0
  152. package/dist/src/ui/commands/editorCommand.test.js +27 -0
  153. package/dist/src/ui/commands/editorCommand.test.js.map +1 -0
  154. package/dist/src/ui/commands/extensionsCommand.test.d.ts +6 -0
  155. package/dist/src/ui/commands/extensionsCommand.test.js +241 -0
  156. package/dist/src/ui/commands/extensionsCommand.test.js.map +1 -0
  157. package/dist/src/ui/commands/helpCommand.js +1 -1
  158. package/dist/src/ui/commands/helpCommand.test.d.ts +6 -0
  159. package/dist/src/ui/commands/helpCommand.test.js +42 -0
  160. package/dist/src/ui/commands/helpCommand.test.js.map +1 -0
  161. package/dist/src/ui/commands/ideCommand.js +6 -6
  162. package/dist/src/ui/commands/ideCommand.test.d.ts +6 -0
  163. package/dist/src/ui/commands/ideCommand.test.js +203 -0
  164. package/dist/src/ui/commands/ideCommand.test.js.map +1 -0
  165. package/dist/src/ui/commands/initCommand.js +1 -1
  166. package/dist/src/ui/commands/initCommand.js.map +1 -1
  167. package/dist/src/ui/commands/initCommand.test.d.ts +6 -0
  168. package/dist/src/ui/commands/initCommand.test.js +80 -0
  169. package/dist/src/ui/commands/initCommand.test.js.map +1 -0
  170. package/dist/src/ui/commands/mcpCommand.js +98 -88
  171. package/dist/src/ui/commands/mcpCommand.js.map +1 -1
  172. package/dist/src/ui/commands/mcpCommand.test.d.ts +6 -0
  173. package/dist/src/ui/commands/mcpCommand.test.js +148 -0
  174. package/dist/src/ui/commands/mcpCommand.test.js.map +1 -0
  175. package/dist/src/ui/commands/memoryCommand.js +5 -5
  176. package/dist/src/ui/commands/memoryCommand.js.map +1 -1
  177. package/dist/src/ui/commands/memoryCommand.test.d.ts +6 -0
  178. package/dist/src/ui/commands/memoryCommand.test.js +266 -0
  179. package/dist/src/ui/commands/memoryCommand.test.js.map +1 -0
  180. package/dist/src/ui/commands/privacyCommand.js +1 -1
  181. package/dist/src/ui/commands/privacyCommand.test.d.ts +6 -0
  182. package/dist/src/ui/commands/privacyCommand.test.js +32 -0
  183. package/dist/src/ui/commands/privacyCommand.test.js.map +1 -0
  184. package/dist/src/ui/commands/quitCommand.js +1 -1
  185. package/dist/src/ui/commands/quitCommand.test.d.ts +6 -0
  186. package/dist/src/ui/commands/quitCommand.test.js +50 -0
  187. package/dist/src/ui/commands/quitCommand.test.js.map +1 -0
  188. package/dist/src/ui/commands/restoreCommand.test.d.ts +6 -0
  189. package/dist/src/ui/commands/restoreCommand.test.js +190 -0
  190. package/dist/src/ui/commands/restoreCommand.test.js.map +1 -0
  191. package/dist/src/ui/commands/settingsCommand.test.d.ts +6 -0
  192. package/dist/src/ui/commands/settingsCommand.test.js +30 -0
  193. package/dist/src/ui/commands/settingsCommand.test.js.map +1 -0
  194. package/dist/src/ui/commands/setupGithubCommand.test.js +1 -2
  195. package/dist/src/ui/commands/setupGithubCommand.test.js.map +1 -1
  196. package/dist/src/ui/commands/statsCommand.js +3 -3
  197. package/dist/src/ui/commands/statsCommand.js.map +1 -1
  198. package/dist/src/ui/commands/statsCommand.test.d.ts +6 -0
  199. package/dist/src/ui/commands/statsCommand.test.js +53 -0
  200. package/dist/src/ui/commands/statsCommand.test.js.map +1 -0
  201. package/dist/src/ui/commands/terminalSetupCommand.test.d.ts +6 -0
  202. package/dist/src/ui/commands/terminalSetupCommand.test.js +66 -0
  203. package/dist/src/ui/commands/terminalSetupCommand.test.js.map +1 -0
  204. package/dist/src/ui/commands/themeCommand.js +1 -1
  205. package/dist/src/ui/commands/themeCommand.test.d.ts +6 -0
  206. package/dist/src/ui/commands/themeCommand.test.js +32 -0
  207. package/dist/src/ui/commands/themeCommand.test.js.map +1 -0
  208. package/dist/src/ui/commands/toolsCommand.js +1 -1
  209. package/dist/src/ui/commands/toolsCommand.test.d.ts +6 -0
  210. package/dist/src/ui/commands/toolsCommand.test.js +100 -0
  211. package/dist/src/ui/commands/toolsCommand.test.js.map +1 -0
  212. package/dist/src/ui/commands/vimCommand.js +1 -1
  213. package/dist/src/ui/components/Composer.js +5 -3
  214. package/dist/src/ui/components/Composer.js.map +1 -1
  215. package/dist/src/ui/components/Composer.test.js +16 -1
  216. package/dist/src/ui/components/Composer.test.js.map +1 -1
  217. package/dist/src/ui/components/ContextSummaryDisplay.d.ts +0 -1
  218. package/dist/src/ui/components/ContextSummaryDisplay.js +2 -12
  219. package/dist/src/ui/components/ContextSummaryDisplay.js.map +1 -1
  220. package/dist/src/ui/components/ContextSummaryDisplay.test.d.ts +6 -0
  221. package/dist/src/ui/components/ContextSummaryDisplay.test.js +66 -0
  222. package/dist/src/ui/components/ContextSummaryDisplay.test.js.map +1 -0
  223. package/dist/src/ui/components/DialogManager.js +1 -5
  224. package/dist/src/ui/components/DialogManager.js.map +1 -1
  225. package/dist/src/ui/components/EditorSettingsDialog.js +1 -1
  226. package/dist/src/ui/components/EditorSettingsDialog.js.map +1 -1
  227. package/dist/src/ui/components/FolderTrustDialog.test.js +7 -3
  228. package/dist/src/ui/components/FolderTrustDialog.test.js.map +1 -1
  229. package/dist/src/ui/components/Footer.js +1 -1
  230. package/dist/src/ui/components/Footer.js.map +1 -1
  231. package/dist/src/ui/components/Footer.test.d.ts +6 -0
  232. package/dist/src/ui/components/Footer.test.js +231 -0
  233. package/dist/src/ui/components/Footer.test.js.map +1 -0
  234. package/dist/src/ui/components/InputPrompt.d.ts +4 -0
  235. package/dist/src/ui/components/InputPrompt.js +53 -4
  236. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  237. package/dist/src/ui/components/InputPrompt.test.d.ts +6 -0
  238. package/dist/src/ui/components/InputPrompt.test.js +1716 -0
  239. package/dist/src/ui/components/InputPrompt.test.js.map +1 -0
  240. package/dist/src/ui/components/ModelStatsDisplay.test.d.ts +6 -0
  241. package/dist/src/ui/components/ModelStatsDisplay.test.js +285 -0
  242. package/dist/src/ui/components/ModelStatsDisplay.test.js.map +1 -0
  243. package/dist/src/ui/components/PermissionsModifyTrustDialog.js +22 -18
  244. package/dist/src/ui/components/PermissionsModifyTrustDialog.js.map +1 -1
  245. package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js +10 -2
  246. package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js.map +1 -1
  247. package/dist/src/ui/components/QueuedMessageDisplay.js +3 -3
  248. package/dist/src/ui/components/QueuedMessageDisplay.js.map +1 -1
  249. package/dist/src/ui/components/QueuedMessageDisplay.test.js +4 -0
  250. package/dist/src/ui/components/QueuedMessageDisplay.test.js.map +1 -1
  251. package/dist/src/ui/components/RawMarkdownIndicator.d.ts +7 -0
  252. package/dist/src/ui/components/RawMarkdownIndicator.js +8 -0
  253. package/dist/src/ui/components/RawMarkdownIndicator.js.map +1 -0
  254. package/dist/src/ui/components/SessionSummaryDisplay.test.d.ts +6 -0
  255. package/dist/src/ui/components/SessionSummaryDisplay.test.js +74 -0
  256. package/dist/src/ui/components/SessionSummaryDisplay.test.js.map +1 -0
  257. package/dist/src/ui/components/SettingsDialog.js +8 -8
  258. package/dist/src/ui/components/SettingsDialog.js.map +1 -1
  259. package/dist/src/ui/components/SettingsDialog.test.js +188 -56
  260. package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
  261. package/dist/src/ui/components/StatsDisplay.test.d.ts +6 -0
  262. package/dist/src/ui/components/StatsDisplay.test.js +351 -0
  263. package/dist/src/ui/components/StatsDisplay.test.js.map +1 -0
  264. package/dist/src/ui/components/ThemeDialog.d.ts +4 -2
  265. package/dist/src/ui/components/ThemeDialog.js +3 -3
  266. package/dist/src/ui/components/ThemeDialog.js.map +1 -1
  267. package/dist/src/ui/components/ThemeDialog.test.js +13 -0
  268. package/dist/src/ui/components/ThemeDialog.test.js.map +1 -1
  269. package/dist/src/ui/components/ToolStatsDisplay.test.d.ts +6 -0
  270. package/dist/src/ui/components/ToolStatsDisplay.test.js +227 -0
  271. package/dist/src/ui/components/ToolStatsDisplay.test.js.map +1 -0
  272. package/dist/src/ui/components/messages/GeminiMessage.js +3 -1
  273. package/dist/src/ui/components/messages/GeminiMessage.js.map +1 -1
  274. package/dist/src/ui/components/messages/GeminiMessage.test.d.ts +6 -0
  275. package/dist/src/ui/components/messages/GeminiMessage.test.js +35 -0
  276. package/dist/src/ui/components/messages/GeminiMessage.test.js.map +1 -0
  277. package/dist/src/ui/components/messages/GeminiMessageContent.js +3 -1
  278. package/dist/src/ui/components/messages/GeminiMessageContent.js.map +1 -1
  279. package/dist/src/ui/components/messages/Todo.d.ts +7 -0
  280. package/dist/src/ui/components/messages/Todo.js +59 -0
  281. package/dist/src/ui/components/messages/Todo.js.map +1 -0
  282. package/dist/src/ui/components/messages/Todo.test.d.ts +6 -0
  283. package/dist/src/ui/components/messages/Todo.test.js +113 -0
  284. package/dist/src/ui/components/messages/Todo.test.js.map +1 -0
  285. package/dist/src/ui/components/messages/ToolGroupMessage.js +1 -1
  286. package/dist/src/ui/components/messages/ToolGroupMessage.js.map +1 -1
  287. package/dist/src/ui/components/messages/ToolMessage.js +8 -3
  288. package/dist/src/ui/components/messages/ToolMessage.js.map +1 -1
  289. package/dist/src/ui/components/messages/ToolMessage.test.js +2 -2
  290. package/dist/src/ui/components/messages/ToolMessage.test.js.map +1 -1
  291. package/dist/src/ui/components/messages/ToolMessageRawMarkdown.test.d.ts +6 -0
  292. package/dist/src/ui/components/messages/ToolMessageRawMarkdown.test.js +30 -0
  293. package/dist/src/ui/components/messages/ToolMessageRawMarkdown.test.js.map +1 -0
  294. package/dist/src/ui/components/messages/UserShellMessage.js +1 -1
  295. package/dist/src/ui/components/messages/UserShellMessage.js.map +1 -1
  296. package/dist/src/ui/components/shared/BaseSelectionList.test.js +1 -1
  297. package/dist/src/ui/components/shared/BaseSelectionList.test.js.map +1 -1
  298. package/dist/src/ui/components/shared/text-buffer.test.d.ts +6 -0
  299. package/dist/src/ui/components/shared/text-buffer.test.js +1554 -0
  300. package/dist/src/ui/components/shared/text-buffer.test.js.map +1 -0
  301. package/dist/src/ui/components/shared/vim-buffer-actions.test.d.ts +6 -0
  302. package/dist/src/ui/components/shared/vim-buffer-actions.test.js +951 -0
  303. package/dist/src/ui/components/shared/vim-buffer-actions.test.js.map +1 -0
  304. package/dist/src/ui/components/views/McpStatus.d.ts +0 -1
  305. package/dist/src/ui/components/views/McpStatus.js +2 -2
  306. package/dist/src/ui/components/views/McpStatus.js.map +1 -1
  307. package/dist/src/ui/components/views/McpStatus.test.js +0 -5
  308. package/dist/src/ui/components/views/McpStatus.test.js.map +1 -1
  309. package/dist/src/ui/components/views/ToolsList.test.js +4 -4
  310. package/dist/src/ui/components/views/ToolsList.test.js.map +1 -1
  311. package/dist/src/ui/contexts/KeypressContext.d.ts +1 -0
  312. package/dist/src/ui/contexts/KeypressContext.js +176 -50
  313. package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
  314. package/dist/src/ui/contexts/KeypressContext.test.js +413 -14
  315. package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
  316. package/dist/src/ui/contexts/SessionContext.test.d.ts +6 -0
  317. package/dist/src/ui/contexts/SessionContext.test.js +177 -0
  318. package/dist/src/ui/contexts/SessionContext.test.js.map +1 -0
  319. package/dist/src/ui/contexts/UIActionsContext.d.ts +5 -4
  320. package/dist/src/ui/contexts/UIActionsContext.js.map +1 -1
  321. package/dist/src/ui/contexts/UIStateContext.d.ts +3 -3
  322. package/dist/src/ui/contexts/UIStateContext.js.map +1 -1
  323. package/dist/src/ui/hooks/slashCommandProcessor.test.d.ts +6 -0
  324. package/dist/src/ui/hooks/slashCommandProcessor.test.js +779 -0
  325. package/dist/src/ui/hooks/slashCommandProcessor.test.js.map +1 -0
  326. package/dist/src/ui/hooks/useAtCompletion.js +2 -2
  327. package/dist/src/ui/hooks/useAtCompletion.js.map +1 -1
  328. package/dist/src/ui/hooks/useAtCompletion.test.d.ts +6 -0
  329. package/dist/src/ui/hooks/useAtCompletion.test.js +385 -0
  330. package/dist/src/ui/hooks/useAtCompletion.test.js.map +1 -0
  331. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js +0 -1
  332. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js.map +1 -1
  333. package/dist/src/ui/hooks/useCommandCompletion.d.ts +1 -1
  334. package/dist/src/ui/hooks/useCommandCompletion.js +5 -3
  335. package/dist/src/ui/hooks/useCommandCompletion.js.map +1 -1
  336. package/dist/src/ui/hooks/useCommandCompletion.test.d.ts +6 -0
  337. package/dist/src/ui/hooks/useCommandCompletion.test.js +375 -0
  338. package/dist/src/ui/hooks/useCommandCompletion.test.js.map +1 -0
  339. package/dist/src/ui/hooks/useConsoleMessages.test.d.ts +6 -0
  340. package/dist/src/ui/hooks/useConsoleMessages.test.js +110 -0
  341. package/dist/src/ui/hooks/useConsoleMessages.test.js.map +1 -0
  342. package/dist/src/ui/hooks/useExtensionUpdates.test.js +3 -3
  343. package/dist/src/ui/hooks/useExtensionUpdates.test.js.map +1 -1
  344. package/dist/src/ui/hooks/useFocus.test.d.ts +6 -0
  345. package/dist/src/ui/hooks/useFocus.test.js +115 -0
  346. package/dist/src/ui/hooks/useFocus.test.js.map +1 -0
  347. package/dist/src/ui/hooks/useFolderTrust.test.d.ts +6 -0
  348. package/dist/src/ui/hooks/useFolderTrust.test.js +164 -0
  349. package/dist/src/ui/hooks/useFolderTrust.test.js.map +1 -0
  350. package/dist/src/ui/hooks/useGeminiStream.js +33 -31
  351. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  352. package/dist/src/ui/hooks/useGeminiStream.test.d.ts +6 -0
  353. package/dist/src/ui/hooks/useGeminiStream.test.js +1936 -0
  354. package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -0
  355. package/dist/src/ui/hooks/useKeypress.test.d.ts +6 -0
  356. package/dist/src/ui/hooks/useKeypress.test.js +234 -0
  357. package/dist/src/ui/hooks/useKeypress.test.js.map +1 -0
  358. package/dist/src/ui/hooks/useLoadingIndicator.test.js +5 -0
  359. package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -1
  360. package/dist/src/ui/hooks/useMessageQueue.d.ts +1 -0
  361. package/dist/src/ui/hooks/useMessageQueue.js +14 -0
  362. package/dist/src/ui/hooks/useMessageQueue.js.map +1 -1
  363. package/dist/src/ui/hooks/useMessageQueue.test.js +121 -0
  364. package/dist/src/ui/hooks/useMessageQueue.test.js.map +1 -1
  365. package/dist/src/ui/hooks/usePhraseCycler.d.ts +1 -0
  366. package/dist/src/ui/hooks/usePhraseCycler.js +156 -5
  367. package/dist/src/ui/hooks/usePhraseCycler.js.map +1 -1
  368. package/dist/src/ui/hooks/usePhraseCycler.test.d.ts +6 -0
  369. package/dist/src/ui/hooks/usePhraseCycler.test.js +155 -0
  370. package/dist/src/ui/hooks/usePhraseCycler.test.js.map +1 -0
  371. package/dist/src/ui/hooks/useThemeCommand.d.ts +2 -1
  372. package/dist/src/ui/hooks/useThemeCommand.js +6 -0
  373. package/dist/src/ui/hooks/useThemeCommand.js.map +1 -1
  374. package/dist/src/ui/hooks/useToolScheduler.test.js +3 -0
  375. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
  376. package/dist/src/ui/hooks/vim.test.d.ts +6 -0
  377. package/dist/src/ui/hooks/vim.test.js +1389 -0
  378. package/dist/src/ui/hooks/vim.test.js.map +1 -0
  379. package/dist/src/ui/keyMatchers.test.js +9 -3
  380. package/dist/src/ui/keyMatchers.test.js.map +1 -1
  381. package/dist/src/ui/themes/theme.test.d.ts +6 -0
  382. package/dist/src/ui/themes/theme.test.js +85 -0
  383. package/dist/src/ui/themes/theme.test.js.map +1 -0
  384. package/dist/src/ui/types.d.ts +0 -1
  385. package/dist/src/ui/types.js.map +1 -1
  386. package/dist/src/ui/utils/CodeColorizer.d.ts +1 -1
  387. package/dist/src/ui/utils/CodeColorizer.js +4 -2
  388. package/dist/src/ui/utils/CodeColorizer.js.map +1 -1
  389. package/dist/src/ui/utils/MarkdownDisplay.d.ts +1 -0
  390. package/dist/src/ui/utils/MarkdownDisplay.js +8 -1
  391. package/dist/src/ui/utils/MarkdownDisplay.js.map +1 -1
  392. package/dist/src/ui/utils/commandUtils.js +18 -2
  393. package/dist/src/ui/utils/commandUtils.js.map +1 -1
  394. package/dist/src/ui/utils/commandUtils.test.js +61 -6
  395. package/dist/src/ui/utils/commandUtils.test.js.map +1 -1
  396. package/dist/src/ui/utils/updateCheck.d.ts +2 -1
  397. package/dist/src/ui/utils/updateCheck.js +4 -1
  398. package/dist/src/ui/utils/updateCheck.js.map +1 -1
  399. package/dist/src/ui/utils/updateCheck.test.js +25 -10
  400. package/dist/src/ui/utils/updateCheck.test.js.map +1 -1
  401. package/dist/src/utils/errors.d.ts +1 -0
  402. package/dist/src/utils/errors.js +66 -5
  403. package/dist/src/utils/errors.js.map +1 -1
  404. package/dist/src/validateNonInterActiveAuth.test.d.ts +6 -0
  405. package/dist/src/validateNonInterActiveAuth.test.js +336 -0
  406. package/dist/src/validateNonInterActiveAuth.test.js.map +1 -0
  407. package/dist/src/zed-integration/zedIntegration.js +1 -2
  408. package/dist/src/zed-integration/zedIntegration.js.map +1 -1
  409. package/dist/tsconfig.tsbuildinfo +1 -1
  410. package/package.json +3 -3
  411. package/dist/google-gemini-cli-0.10.0-preview.1.tgz +0 -0
  412. package/dist/src/ui/components/WorkspaceMigrationDialog.d.ts +0 -11
  413. package/dist/src/ui/components/WorkspaceMigrationDialog.js +0 -44
  414. package/dist/src/ui/components/WorkspaceMigrationDialog.js.map +0 -1
  415. package/dist/src/ui/hooks/useWorkspaceMigration.d.ts +0 -13
  416. package/dist/src/ui/hooks/useWorkspaceMigration.js +0 -59
  417. package/dist/src/ui/hooks/useWorkspaceMigration.js.map +0 -1
@@ -0,0 +1,779 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { act, renderHook, waitFor } from '@testing-library/react';
7
+ import { vi, describe, it, expect, beforeEach } from 'vitest';
8
+ import { useSlashCommandProcessor } from './slashCommandProcessor.js';
9
+ import { CommandKind } from '../commands/types.js';
10
+ import { MessageType } from '../types.js';
11
+ import { BuiltinCommandLoader } from '../../services/BuiltinCommandLoader.js';
12
+ import { FileCommandLoader } from '../../services/FileCommandLoader.js';
13
+ import { McpPromptLoader } from '../../services/McpPromptLoader.js';
14
+ import { SlashCommandStatus, ToolConfirmationOutcome, makeFakeConfig, } from '@google/gemini-cli-core';
15
+ const { logSlashCommand } = vi.hoisted(() => ({
16
+ logSlashCommand: vi.fn(),
17
+ }));
18
+ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
19
+ const original = await importOriginal();
20
+ return {
21
+ ...original,
22
+ logSlashCommand,
23
+ getIdeInstaller: vi.fn().mockReturnValue(null),
24
+ };
25
+ });
26
+ const { mockProcessExit } = vi.hoisted(() => ({
27
+ mockProcessExit: vi.fn((_code) => undefined),
28
+ }));
29
+ vi.mock('node:process', () => {
30
+ const mockProcess = {
31
+ exit: mockProcessExit,
32
+ platform: 'sunos',
33
+ cwd: () => '/fake/dir',
34
+ };
35
+ return {
36
+ ...mockProcess,
37
+ default: mockProcess,
38
+ };
39
+ });
40
+ const mockBuiltinLoadCommands = vi.fn();
41
+ vi.mock('../../services/BuiltinCommandLoader.js', () => ({
42
+ BuiltinCommandLoader: vi.fn().mockImplementation(() => ({
43
+ loadCommands: mockBuiltinLoadCommands,
44
+ })),
45
+ }));
46
+ const mockFileLoadCommands = vi.fn();
47
+ vi.mock('../../services/FileCommandLoader.js', () => ({
48
+ FileCommandLoader: vi.fn().mockImplementation(() => ({
49
+ loadCommands: mockFileLoadCommands,
50
+ })),
51
+ }));
52
+ const mockMcpLoadCommands = vi.fn();
53
+ vi.mock('../../services/McpPromptLoader.js', () => ({
54
+ McpPromptLoader: vi.fn().mockImplementation(() => ({
55
+ loadCommands: mockMcpLoadCommands,
56
+ })),
57
+ }));
58
+ vi.mock('../contexts/SessionContext.js', () => ({
59
+ useSessionStats: vi.fn(() => ({ stats: {} })),
60
+ }));
61
+ const { mockRunExitCleanup } = vi.hoisted(() => ({
62
+ mockRunExitCleanup: vi.fn(),
63
+ }));
64
+ vi.mock('../../utils/cleanup.js', () => ({
65
+ runExitCleanup: mockRunExitCleanup,
66
+ }));
67
+ function createTestCommand(overrides, kind = CommandKind.BUILT_IN) {
68
+ return {
69
+ name: 'test',
70
+ description: 'a test command',
71
+ kind,
72
+ ...overrides,
73
+ };
74
+ }
75
+ describe('useSlashCommandProcessor', () => {
76
+ const mockAddItem = vi.fn();
77
+ const mockClearItems = vi.fn();
78
+ const mockLoadHistory = vi.fn();
79
+ const mockOpenThemeDialog = vi.fn();
80
+ const mockOpenAuthDialog = vi.fn();
81
+ const mockOpenModelDialog = vi.fn();
82
+ const mockSetQuittingMessages = vi.fn();
83
+ const mockConfig = makeFakeConfig({});
84
+ const mockSettings = {};
85
+ beforeEach(() => {
86
+ vi.clearAllMocks();
87
+ vi.mocked(BuiltinCommandLoader).mockClear();
88
+ mockBuiltinLoadCommands.mockResolvedValue([]);
89
+ mockFileLoadCommands.mockResolvedValue([]);
90
+ mockMcpLoadCommands.mockResolvedValue([]);
91
+ });
92
+ const setupProcessorHook = (builtinCommands = [], fileCommands = [], mcpCommands = [], setIsProcessing = vi.fn()) => {
93
+ mockBuiltinLoadCommands.mockResolvedValue(Object.freeze(builtinCommands));
94
+ mockFileLoadCommands.mockResolvedValue(Object.freeze(fileCommands));
95
+ mockMcpLoadCommands.mockResolvedValue(Object.freeze(mcpCommands));
96
+ const { result } = renderHook(() => useSlashCommandProcessor(mockConfig, mockSettings, mockAddItem, mockClearItems, mockLoadHistory, vi.fn(), // refreshStatic
97
+ vi.fn(), // toggleVimEnabled
98
+ setIsProcessing, vi.fn(), // setGeminiMdFileCount
99
+ {
100
+ openAuthDialog: mockOpenAuthDialog,
101
+ openThemeDialog: mockOpenThemeDialog,
102
+ openEditorDialog: vi.fn(),
103
+ openPrivacyNotice: vi.fn(),
104
+ openSettingsDialog: vi.fn(),
105
+ openModelDialog: mockOpenModelDialog,
106
+ openPermissionsDialog: vi.fn(),
107
+ quit: mockSetQuittingMessages,
108
+ setDebugMessage: vi.fn(),
109
+ toggleCorgiMode: vi.fn(),
110
+ toggleDebugProfiler: vi.fn(),
111
+ dispatchExtensionStateUpdate: vi.fn(),
112
+ addConfirmUpdateExtensionRequest: vi.fn(),
113
+ }, new Map(), // extensionsUpdateState
114
+ true));
115
+ return result;
116
+ };
117
+ describe('Initialization and Command Loading', () => {
118
+ it('should initialize CommandService with all required loaders', () => {
119
+ setupProcessorHook();
120
+ expect(BuiltinCommandLoader).toHaveBeenCalledWith(mockConfig);
121
+ expect(FileCommandLoader).toHaveBeenCalledWith(mockConfig);
122
+ expect(McpPromptLoader).toHaveBeenCalledWith(mockConfig);
123
+ });
124
+ it('should call loadCommands and populate state after mounting', async () => {
125
+ const testCommand = createTestCommand({ name: 'test' });
126
+ const result = setupProcessorHook([testCommand]);
127
+ await waitFor(() => {
128
+ expect(result.current.slashCommands).toHaveLength(1);
129
+ });
130
+ expect(result.current.slashCommands?.[0]?.name).toBe('test');
131
+ expect(mockBuiltinLoadCommands).toHaveBeenCalledTimes(1);
132
+ expect(mockFileLoadCommands).toHaveBeenCalledTimes(1);
133
+ expect(mockMcpLoadCommands).toHaveBeenCalledTimes(1);
134
+ });
135
+ it('should provide an immutable array of commands to consumers', async () => {
136
+ const testCommand = createTestCommand({ name: 'test' });
137
+ const result = setupProcessorHook([testCommand]);
138
+ await waitFor(() => {
139
+ expect(result.current.slashCommands).toHaveLength(1);
140
+ });
141
+ const commands = result.current.slashCommands;
142
+ expect(() => {
143
+ // @ts-expect-error - We are intentionally testing a violation of the readonly type.
144
+ commands.push(createTestCommand({ name: 'rogue' }));
145
+ }).toThrow(TypeError);
146
+ });
147
+ it('should override built-in commands with file-based commands of the same name', async () => {
148
+ const builtinAction = vi.fn();
149
+ const fileAction = vi.fn();
150
+ const builtinCommand = createTestCommand({
151
+ name: 'override',
152
+ description: 'builtin',
153
+ action: builtinAction,
154
+ });
155
+ const fileCommand = createTestCommand({ name: 'override', description: 'file', action: fileAction }, CommandKind.FILE);
156
+ const result = setupProcessorHook([builtinCommand], [fileCommand]);
157
+ await waitFor(() => {
158
+ // The service should only return one command with the name 'override'
159
+ expect(result.current.slashCommands).toHaveLength(1);
160
+ });
161
+ await act(async () => {
162
+ await result.current.handleSlashCommand('/override');
163
+ });
164
+ // Only the file-based command's action should be called.
165
+ expect(fileAction).toHaveBeenCalledTimes(1);
166
+ expect(builtinAction).not.toHaveBeenCalled();
167
+ });
168
+ });
169
+ describe('Command Execution Logic', () => {
170
+ it('should display an error for an unknown command', async () => {
171
+ const result = setupProcessorHook();
172
+ await waitFor(() => expect(result.current.slashCommands).toBeDefined());
173
+ await act(async () => {
174
+ await result.current.handleSlashCommand('/nonexistent');
175
+ });
176
+ // Expect 2 calls: one for the user's input, one for the error message.
177
+ expect(mockAddItem).toHaveBeenCalledTimes(2);
178
+ expect(mockAddItem).toHaveBeenLastCalledWith({
179
+ type: MessageType.ERROR,
180
+ text: 'Unknown command: /nonexistent',
181
+ }, expect.any(Number));
182
+ });
183
+ it('should display help for a parent command invoked without a subcommand', async () => {
184
+ const parentCommand = {
185
+ name: 'parent',
186
+ description: 'a parent command',
187
+ kind: CommandKind.BUILT_IN,
188
+ subCommands: [
189
+ {
190
+ name: 'child1',
191
+ description: 'First child.',
192
+ kind: CommandKind.BUILT_IN,
193
+ },
194
+ ],
195
+ };
196
+ const result = setupProcessorHook([parentCommand]);
197
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
198
+ await act(async () => {
199
+ await result.current.handleSlashCommand('/parent');
200
+ });
201
+ expect(mockAddItem).toHaveBeenCalledTimes(2);
202
+ expect(mockAddItem).toHaveBeenLastCalledWith({
203
+ type: MessageType.INFO,
204
+ text: expect.stringContaining("Command '/parent' requires a subcommand."),
205
+ }, expect.any(Number));
206
+ });
207
+ it('should correctly find and execute a nested subcommand', async () => {
208
+ const childAction = vi.fn();
209
+ const parentCommand = {
210
+ name: 'parent',
211
+ description: 'a parent command',
212
+ kind: CommandKind.BUILT_IN,
213
+ subCommands: [
214
+ {
215
+ name: 'child',
216
+ description: 'a child command',
217
+ kind: CommandKind.BUILT_IN,
218
+ action: childAction,
219
+ },
220
+ ],
221
+ };
222
+ const result = setupProcessorHook([parentCommand]);
223
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
224
+ await act(async () => {
225
+ await result.current.handleSlashCommand('/parent child with args');
226
+ });
227
+ expect(childAction).toHaveBeenCalledTimes(1);
228
+ expect(childAction).toHaveBeenCalledWith(expect.objectContaining({
229
+ services: expect.objectContaining({
230
+ config: mockConfig,
231
+ }),
232
+ ui: expect.objectContaining({
233
+ addItem: mockAddItem,
234
+ }),
235
+ }), 'with args');
236
+ });
237
+ it('sets isProcessing to false if the the input is not a command', async () => {
238
+ const setMockIsProcessing = vi.fn();
239
+ const result = setupProcessorHook([], [], [], setMockIsProcessing);
240
+ await act(async () => {
241
+ await result.current.handleSlashCommand('imnotacommand');
242
+ });
243
+ expect(setMockIsProcessing).not.toHaveBeenCalled();
244
+ });
245
+ it('sets isProcessing to false if the command has an error', async () => {
246
+ const setMockIsProcessing = vi.fn();
247
+ const failCommand = createTestCommand({
248
+ name: 'fail',
249
+ action: vi.fn().mockRejectedValue(new Error('oh no!')),
250
+ });
251
+ const result = setupProcessorHook([failCommand], [], [], setMockIsProcessing);
252
+ await waitFor(() => expect(result.current.slashCommands).toBeDefined());
253
+ await act(async () => {
254
+ await result.current.handleSlashCommand('/fail');
255
+ });
256
+ expect(setMockIsProcessing).toHaveBeenNthCalledWith(1, true);
257
+ expect(setMockIsProcessing).toHaveBeenNthCalledWith(2, false);
258
+ });
259
+ it('should set isProcessing to true during execution and false afterwards', async () => {
260
+ const mockSetIsProcessing = vi.fn();
261
+ const command = createTestCommand({
262
+ name: 'long-running',
263
+ action: () => new Promise((resolve) => setTimeout(resolve, 50)),
264
+ });
265
+ const result = setupProcessorHook([command], [], [], mockSetIsProcessing);
266
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
267
+ const executionPromise = act(async () => {
268
+ await result.current.handleSlashCommand('/long-running');
269
+ });
270
+ // It should be true immediately after starting
271
+ expect(mockSetIsProcessing).toHaveBeenNthCalledWith(1, true);
272
+ // It should not have been called with false yet
273
+ expect(mockSetIsProcessing).not.toHaveBeenCalledWith(false);
274
+ await executionPromise;
275
+ // After the promise resolves, it should be called with false
276
+ expect(mockSetIsProcessing).toHaveBeenNthCalledWith(2, false);
277
+ expect(mockSetIsProcessing).toHaveBeenCalledTimes(2);
278
+ });
279
+ });
280
+ describe('Action Result Handling', () => {
281
+ it('should handle "dialog: theme" action', async () => {
282
+ const command = createTestCommand({
283
+ name: 'themecmd',
284
+ action: vi.fn().mockResolvedValue({ type: 'dialog', dialog: 'theme' }),
285
+ });
286
+ const result = setupProcessorHook([command]);
287
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
288
+ await act(async () => {
289
+ await result.current.handleSlashCommand('/themecmd');
290
+ });
291
+ expect(mockOpenThemeDialog).toHaveBeenCalled();
292
+ });
293
+ it('should handle "dialog: model" action', async () => {
294
+ const command = createTestCommand({
295
+ name: 'modelcmd',
296
+ action: vi.fn().mockResolvedValue({ type: 'dialog', dialog: 'model' }),
297
+ });
298
+ const result = setupProcessorHook([command]);
299
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
300
+ await act(async () => {
301
+ await result.current.handleSlashCommand('/modelcmd');
302
+ });
303
+ expect(mockOpenModelDialog).toHaveBeenCalled();
304
+ });
305
+ it('should handle "load_history" action', async () => {
306
+ const mockClient = {
307
+ setHistory: vi.fn(),
308
+ stripThoughtsFromHistory: vi.fn(),
309
+ };
310
+ vi.spyOn(mockConfig, 'getGeminiClient').mockReturnValue(mockClient);
311
+ const command = createTestCommand({
312
+ name: 'load',
313
+ action: vi.fn().mockResolvedValue({
314
+ type: 'load_history',
315
+ history: [{ type: MessageType.USER, text: 'old prompt' }],
316
+ clientHistory: [{ role: 'user', parts: [{ text: 'old prompt' }] }],
317
+ }),
318
+ });
319
+ const result = setupProcessorHook([command]);
320
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
321
+ await act(async () => {
322
+ await result.current.handleSlashCommand('/load');
323
+ });
324
+ expect(mockClearItems).toHaveBeenCalledTimes(1);
325
+ expect(mockAddItem).toHaveBeenCalledWith({ type: 'user', text: 'old prompt' }, expect.any(Number));
326
+ });
327
+ it('should strip thoughts when handling "load_history" action', async () => {
328
+ const mockClient = {
329
+ setHistory: vi.fn(),
330
+ stripThoughtsFromHistory: vi.fn(),
331
+ };
332
+ vi.spyOn(mockConfig, 'getGeminiClient').mockReturnValue(mockClient);
333
+ const historyWithThoughts = [
334
+ {
335
+ role: 'model',
336
+ parts: [{ text: 'response', thoughtSignature: 'CikB...' }],
337
+ },
338
+ ];
339
+ const command = createTestCommand({
340
+ name: 'loadwiththoughts',
341
+ action: vi.fn().mockResolvedValue({
342
+ type: 'load_history',
343
+ history: [{ type: MessageType.GEMINI, text: 'response' }],
344
+ clientHistory: historyWithThoughts,
345
+ }),
346
+ });
347
+ const result = setupProcessorHook([command]);
348
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
349
+ await act(async () => {
350
+ await result.current.handleSlashCommand('/loadwiththoughts');
351
+ });
352
+ expect(mockClient.setHistory).toHaveBeenCalledTimes(1);
353
+ expect(mockClient.stripThoughtsFromHistory).toHaveBeenCalledWith();
354
+ });
355
+ it('should handle a "quit" action', async () => {
356
+ const quitAction = vi
357
+ .fn()
358
+ .mockResolvedValue({ type: 'quit', messages: ['bye'] });
359
+ const command = createTestCommand({
360
+ name: 'exit',
361
+ action: quitAction,
362
+ });
363
+ const result = setupProcessorHook([command]);
364
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
365
+ await act(async () => {
366
+ await result.current.handleSlashCommand('/exit');
367
+ });
368
+ expect(mockSetQuittingMessages).toHaveBeenCalledWith(['bye']);
369
+ });
370
+ it('should handle "submit_prompt" action returned from a file-based command', async () => {
371
+ const fileCommand = createTestCommand({
372
+ name: 'filecmd',
373
+ description: 'A command from a file',
374
+ action: async () => ({
375
+ type: 'submit_prompt',
376
+ content: [{ text: 'The actual prompt from the TOML file.' }],
377
+ }),
378
+ }, CommandKind.FILE);
379
+ const result = setupProcessorHook([], [fileCommand]);
380
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
381
+ let actionResult;
382
+ await act(async () => {
383
+ actionResult = await result.current.handleSlashCommand('/filecmd');
384
+ });
385
+ expect(actionResult).toEqual({
386
+ type: 'submit_prompt',
387
+ content: [{ text: 'The actual prompt from the TOML file.' }],
388
+ });
389
+ expect(mockAddItem).toHaveBeenCalledWith({ type: MessageType.USER, text: '/filecmd' }, expect.any(Number));
390
+ });
391
+ it('should handle "submit_prompt" action returned from a mcp-based command', async () => {
392
+ const mcpCommand = createTestCommand({
393
+ name: 'mcpcmd',
394
+ description: 'A command from mcp',
395
+ action: async () => ({
396
+ type: 'submit_prompt',
397
+ content: [{ text: 'The actual prompt from the mcp command.' }],
398
+ }),
399
+ }, CommandKind.MCP_PROMPT);
400
+ const result = setupProcessorHook([], [], [mcpCommand]);
401
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
402
+ let actionResult;
403
+ await act(async () => {
404
+ actionResult = await result.current.handleSlashCommand('/mcpcmd');
405
+ });
406
+ expect(actionResult).toEqual({
407
+ type: 'submit_prompt',
408
+ content: [{ text: 'The actual prompt from the mcp command.' }],
409
+ });
410
+ expect(mockAddItem).toHaveBeenCalledWith({ type: MessageType.USER, text: '/mcpcmd' }, expect.any(Number));
411
+ });
412
+ });
413
+ describe('Shell Command Confirmation Flow', () => {
414
+ // Use a generic vi.fn() for the action. We will change its behavior in each test.
415
+ const mockCommandAction = vi.fn();
416
+ const shellCommand = createTestCommand({
417
+ name: 'shellcmd',
418
+ action: mockCommandAction,
419
+ });
420
+ beforeEach(() => {
421
+ // Reset the mock before each test
422
+ mockCommandAction.mockClear();
423
+ // Default behavior: request confirmation
424
+ mockCommandAction.mockResolvedValue({
425
+ type: 'confirm_shell_commands',
426
+ commandsToConfirm: ['rm -rf /'],
427
+ originalInvocation: { raw: '/shellcmd' },
428
+ });
429
+ });
430
+ it('should set confirmation request when action returns confirm_shell_commands', async () => {
431
+ const result = setupProcessorHook([shellCommand]);
432
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
433
+ // This is intentionally not awaited, because the promise it returns
434
+ // will not resolve until the user responds to the confirmation.
435
+ act(() => {
436
+ result.current.handleSlashCommand('/shellcmd');
437
+ });
438
+ // We now wait for the state to be updated with the request.
439
+ await waitFor(() => {
440
+ expect(result.current.shellConfirmationRequest).not.toBeNull();
441
+ });
442
+ expect(result.current.shellConfirmationRequest?.commands).toEqual([
443
+ 'rm -rf /',
444
+ ]);
445
+ });
446
+ it('should do nothing if user cancels confirmation', async () => {
447
+ const result = setupProcessorHook([shellCommand]);
448
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
449
+ act(() => {
450
+ result.current.handleSlashCommand('/shellcmd');
451
+ });
452
+ // Wait for the confirmation dialog to be set
453
+ await waitFor(() => {
454
+ expect(result.current.shellConfirmationRequest).not.toBeNull();
455
+ });
456
+ const onConfirm = result.current.shellConfirmationRequest?.onConfirm;
457
+ expect(onConfirm).toBeDefined();
458
+ // Change the mock action's behavior for a potential second run.
459
+ // If the test is flawed, this will be called, and we can detect it.
460
+ mockCommandAction.mockResolvedValue({
461
+ type: 'message',
462
+ messageType: 'info',
463
+ content: 'This should not be called',
464
+ });
465
+ await act(async () => {
466
+ onConfirm(ToolConfirmationOutcome.Cancel, []); // Pass empty array for safety
467
+ });
468
+ expect(result.current.shellConfirmationRequest).toBeNull();
469
+ // Verify the action was only called the initial time.
470
+ expect(mockCommandAction).toHaveBeenCalledTimes(1);
471
+ });
472
+ it('should re-run command with one-time allowlist on "Proceed Once"', async () => {
473
+ const result = setupProcessorHook([shellCommand]);
474
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
475
+ act(() => {
476
+ result.current.handleSlashCommand('/shellcmd');
477
+ });
478
+ await waitFor(() => {
479
+ expect(result.current.shellConfirmationRequest).not.toBeNull();
480
+ });
481
+ const onConfirm = result.current.shellConfirmationRequest?.onConfirm;
482
+ // **Change the mock's behavior for the SECOND run.**
483
+ // This is the key to testing the outcome.
484
+ mockCommandAction.mockResolvedValue({
485
+ type: 'message',
486
+ messageType: 'info',
487
+ content: 'Success!',
488
+ });
489
+ await act(async () => {
490
+ onConfirm(ToolConfirmationOutcome.ProceedOnce, ['rm -rf /']);
491
+ });
492
+ expect(result.current.shellConfirmationRequest).toBeNull();
493
+ // The action should have been called twice (initial + re-run).
494
+ await waitFor(() => {
495
+ expect(mockCommandAction).toHaveBeenCalledTimes(2);
496
+ });
497
+ // We can inspect the context of the second call to ensure the one-time list was used.
498
+ const secondCallContext = mockCommandAction.mock
499
+ .calls[1][0];
500
+ expect(secondCallContext.session.sessionShellAllowlist.has('rm -rf /')).toBe(true);
501
+ // Verify the final success message was added.
502
+ expect(mockAddItem).toHaveBeenCalledWith({ type: MessageType.INFO, text: 'Success!' }, expect.any(Number));
503
+ // Verify the session-wide allowlist was NOT permanently updated.
504
+ // Re-render the hook by calling a no-op command to get the latest context.
505
+ await act(async () => {
506
+ result.current.handleSlashCommand('/no-op');
507
+ });
508
+ const finalContext = result.current.commandContext;
509
+ expect(finalContext.session.sessionShellAllowlist.size).toBe(0);
510
+ });
511
+ it('should re-run command and update session allowlist on "Proceed Always"', async () => {
512
+ const result = setupProcessorHook([shellCommand]);
513
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
514
+ act(() => {
515
+ result.current.handleSlashCommand('/shellcmd');
516
+ });
517
+ await waitFor(() => {
518
+ expect(result.current.shellConfirmationRequest).not.toBeNull();
519
+ });
520
+ const onConfirm = result.current.shellConfirmationRequest?.onConfirm;
521
+ mockCommandAction.mockResolvedValue({
522
+ type: 'message',
523
+ messageType: 'info',
524
+ content: 'Success!',
525
+ });
526
+ await act(async () => {
527
+ onConfirm(ToolConfirmationOutcome.ProceedAlways, ['rm -rf /']);
528
+ });
529
+ expect(result.current.shellConfirmationRequest).toBeNull();
530
+ await waitFor(() => {
531
+ expect(mockCommandAction).toHaveBeenCalledTimes(2);
532
+ });
533
+ expect(mockAddItem).toHaveBeenCalledWith({ type: MessageType.INFO, text: 'Success!' }, expect.any(Number));
534
+ // Check that the session-wide allowlist WAS updated.
535
+ await waitFor(() => {
536
+ const finalContext = result.current.commandContext;
537
+ expect(finalContext.session.sessionShellAllowlist.has('rm -rf /')).toBe(true);
538
+ });
539
+ });
540
+ });
541
+ describe('Command Parsing and Matching', () => {
542
+ it('should be case-sensitive', async () => {
543
+ const command = createTestCommand({ name: 'test' });
544
+ const result = setupProcessorHook([command]);
545
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
546
+ await act(async () => {
547
+ // Use uppercase when command is lowercase
548
+ await result.current.handleSlashCommand('/Test');
549
+ });
550
+ // It should fail and call addItem with an error
551
+ expect(mockAddItem).toHaveBeenCalledWith({
552
+ type: MessageType.ERROR,
553
+ text: 'Unknown command: /Test',
554
+ }, expect.any(Number));
555
+ });
556
+ it('should correctly match an altName', async () => {
557
+ const action = vi.fn();
558
+ const command = createTestCommand({
559
+ name: 'main',
560
+ altNames: ['alias'],
561
+ description: 'a command with an alias',
562
+ action,
563
+ });
564
+ const result = setupProcessorHook([command]);
565
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
566
+ await act(async () => {
567
+ await result.current.handleSlashCommand('/alias');
568
+ });
569
+ expect(action).toHaveBeenCalledTimes(1);
570
+ expect(mockAddItem).not.toHaveBeenCalledWith(expect.objectContaining({ type: MessageType.ERROR }));
571
+ });
572
+ it('should handle extra whitespace around the command', async () => {
573
+ const action = vi.fn();
574
+ const command = createTestCommand({ name: 'test', action });
575
+ const result = setupProcessorHook([command]);
576
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
577
+ await act(async () => {
578
+ await result.current.handleSlashCommand(' /test with-args ');
579
+ });
580
+ expect(action).toHaveBeenCalledWith(expect.anything(), 'with-args');
581
+ });
582
+ it('should handle `?` as a command prefix', async () => {
583
+ const action = vi.fn();
584
+ const command = createTestCommand({ name: 'help', action });
585
+ const result = setupProcessorHook([command]);
586
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
587
+ await act(async () => {
588
+ await result.current.handleSlashCommand('?help');
589
+ });
590
+ expect(action).toHaveBeenCalledTimes(1);
591
+ });
592
+ });
593
+ describe('Command Precedence', () => {
594
+ it('should override mcp-based commands with file-based commands of the same name', async () => {
595
+ const mcpAction = vi.fn();
596
+ const fileAction = vi.fn();
597
+ const mcpCommand = createTestCommand({
598
+ name: 'override',
599
+ description: 'mcp',
600
+ action: mcpAction,
601
+ }, CommandKind.MCP_PROMPT);
602
+ const fileCommand = createTestCommand({ name: 'override', description: 'file', action: fileAction }, CommandKind.FILE);
603
+ const result = setupProcessorHook([], [fileCommand], [mcpCommand]);
604
+ await waitFor(() => {
605
+ // The service should only return one command with the name 'override'
606
+ expect(result.current.slashCommands).toHaveLength(1);
607
+ });
608
+ await act(async () => {
609
+ await result.current.handleSlashCommand('/override');
610
+ });
611
+ // Only the file-based command's action should be called.
612
+ expect(fileAction).toHaveBeenCalledTimes(1);
613
+ expect(mcpAction).not.toHaveBeenCalled();
614
+ });
615
+ it('should prioritize a command with a primary name over a command with a matching alias', async () => {
616
+ const quitAction = vi.fn();
617
+ const exitAction = vi.fn();
618
+ const quitCommand = createTestCommand({
619
+ name: 'quit',
620
+ altNames: ['exit'],
621
+ action: quitAction,
622
+ });
623
+ const exitCommand = createTestCommand({
624
+ name: 'exit',
625
+ action: exitAction,
626
+ }, CommandKind.FILE);
627
+ // The order of commands in the final loaded array is not guaranteed,
628
+ // so the test must work regardless of which comes first.
629
+ const result = setupProcessorHook([quitCommand], [exitCommand]);
630
+ await waitFor(() => {
631
+ expect(result.current.slashCommands).toHaveLength(2);
632
+ });
633
+ await act(async () => {
634
+ await result.current.handleSlashCommand('/exit');
635
+ });
636
+ // The action for the command whose primary name is 'exit' should be called.
637
+ expect(exitAction).toHaveBeenCalledTimes(1);
638
+ // The action for the command that has 'exit' as an alias should NOT be called.
639
+ expect(quitAction).not.toHaveBeenCalled();
640
+ });
641
+ it('should add an overridden command to the history', async () => {
642
+ const quitCommand = createTestCommand({
643
+ name: 'quit',
644
+ altNames: ['exit'],
645
+ action: vi.fn(),
646
+ });
647
+ const exitCommand = createTestCommand({ name: 'exit', action: vi.fn() }, CommandKind.FILE);
648
+ const result = setupProcessorHook([quitCommand], [exitCommand]);
649
+ await waitFor(() => expect(result.current.slashCommands).toHaveLength(2));
650
+ await act(async () => {
651
+ await result.current.handleSlashCommand('/exit');
652
+ });
653
+ // It should be added to the history.
654
+ expect(mockAddItem).toHaveBeenCalledWith({ type: MessageType.USER, text: '/exit' }, expect.any(Number));
655
+ });
656
+ });
657
+ describe('Lifecycle', () => {
658
+ it('should abort command loading when the hook unmounts', () => {
659
+ const abortSpy = vi.spyOn(AbortController.prototype, 'abort');
660
+ const { unmount } = renderHook(() => useSlashCommandProcessor(mockConfig, mockSettings, mockAddItem, mockClearItems, mockLoadHistory, vi.fn(), // refreshStatic
661
+ vi.fn().mockResolvedValue(false), // toggleVimEnabled
662
+ vi.fn(), // setIsProcessing
663
+ vi.fn(), // setGeminiMdFileCount
664
+ {
665
+ openAuthDialog: vi.fn(),
666
+ openThemeDialog: vi.fn(),
667
+ openEditorDialog: vi.fn(),
668
+ openPrivacyNotice: vi.fn(),
669
+ openSettingsDialog: vi.fn(),
670
+ openModelDialog: vi.fn(),
671
+ openPermissionsDialog: vi.fn(),
672
+ quit: vi.fn(),
673
+ setDebugMessage: vi.fn(),
674
+ toggleCorgiMode: vi.fn(),
675
+ toggleDebugProfiler: vi.fn(),
676
+ dispatchExtensionStateUpdate: vi.fn(),
677
+ addConfirmUpdateExtensionRequest: vi.fn(),
678
+ }, new Map(), // extensionsUpdateState
679
+ true));
680
+ unmount();
681
+ expect(abortSpy).toHaveBeenCalledTimes(1);
682
+ });
683
+ });
684
+ describe('Slash Command Logging', () => {
685
+ const mockCommandAction = vi.fn().mockResolvedValue({ type: 'handled' });
686
+ const loggingTestCommands = [
687
+ createTestCommand({
688
+ name: 'logtest',
689
+ action: vi
690
+ .fn()
691
+ .mockResolvedValue({ type: 'message', content: 'hello world' }),
692
+ }),
693
+ createTestCommand({
694
+ name: 'logwithsub',
695
+ subCommands: [
696
+ createTestCommand({
697
+ name: 'sub',
698
+ action: mockCommandAction,
699
+ }),
700
+ ],
701
+ }),
702
+ createTestCommand({
703
+ name: 'fail',
704
+ action: vi.fn().mockRejectedValue(new Error('oh no!')),
705
+ }),
706
+ createTestCommand({
707
+ name: 'logalias',
708
+ altNames: ['la'],
709
+ action: mockCommandAction,
710
+ }),
711
+ ];
712
+ beforeEach(() => {
713
+ mockCommandAction.mockClear();
714
+ vi.mocked(logSlashCommand).mockClear();
715
+ });
716
+ it('should log a simple slash command', async () => {
717
+ const result = setupProcessorHook(loggingTestCommands);
718
+ await waitFor(() => expect(result.current.slashCommands?.length).toBeGreaterThan(0));
719
+ await act(async () => {
720
+ await result.current.handleSlashCommand('/logtest');
721
+ });
722
+ expect(logSlashCommand).toHaveBeenCalledWith(mockConfig, expect.objectContaining({
723
+ command: 'logtest',
724
+ subcommand: undefined,
725
+ status: SlashCommandStatus.SUCCESS,
726
+ }));
727
+ });
728
+ it('logs nothing for a bogus command', async () => {
729
+ const result = setupProcessorHook(loggingTestCommands);
730
+ await waitFor(() => expect(result.current.slashCommands?.length).toBeGreaterThan(0));
731
+ await act(async () => {
732
+ await result.current.handleSlashCommand('/bogusbogusbogus');
733
+ });
734
+ expect(logSlashCommand).not.toHaveBeenCalled();
735
+ });
736
+ it('logs a failure event for a failed command', async () => {
737
+ const result = setupProcessorHook(loggingTestCommands);
738
+ await waitFor(() => expect(result.current.slashCommands?.length).toBeGreaterThan(0));
739
+ await act(async () => {
740
+ await result.current.handleSlashCommand('/fail');
741
+ });
742
+ expect(logSlashCommand).toHaveBeenCalledWith(mockConfig, expect.objectContaining({
743
+ command: 'fail',
744
+ status: 'error',
745
+ subcommand: undefined,
746
+ }));
747
+ });
748
+ it('should log a slash command with a subcommand', async () => {
749
+ const result = setupProcessorHook(loggingTestCommands);
750
+ await waitFor(() => expect(result.current.slashCommands?.length).toBeGreaterThan(0));
751
+ await act(async () => {
752
+ await result.current.handleSlashCommand('/logwithsub sub');
753
+ });
754
+ expect(logSlashCommand).toHaveBeenCalledWith(mockConfig, expect.objectContaining({
755
+ command: 'logwithsub',
756
+ subcommand: 'sub',
757
+ }));
758
+ });
759
+ it('should log the command path when an alias is used', async () => {
760
+ const result = setupProcessorHook(loggingTestCommands);
761
+ await waitFor(() => expect(result.current.slashCommands?.length).toBeGreaterThan(0));
762
+ await act(async () => {
763
+ await result.current.handleSlashCommand('/la');
764
+ });
765
+ expect(logSlashCommand).toHaveBeenCalledWith(mockConfig, expect.objectContaining({
766
+ command: 'logalias',
767
+ }));
768
+ });
769
+ it('should not log for unknown commands', async () => {
770
+ const result = setupProcessorHook(loggingTestCommands);
771
+ await waitFor(() => expect(result.current.slashCommands?.length).toBeGreaterThan(0));
772
+ await act(async () => {
773
+ await result.current.handleSlashCommand('/unknown');
774
+ });
775
+ expect(logSlashCommand).not.toHaveBeenCalled();
776
+ });
777
+ });
778
+ });
779
+ //# sourceMappingURL=slashCommandProcessor.test.js.map