@google/gemini-cli 0.10.0-preview.1 → 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 +19 -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.0.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,971 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import * as path from 'node:path';
7
+ import { GEMINI_DIR, Storage } from '@google/gemini-cli-core';
8
+ import mock from 'mock-fs';
9
+ import { FileCommandLoader } from './FileCommandLoader.js';
10
+ import { assert, vi } from 'vitest';
11
+ import { createMockCommandContext } from '../test-utils/mockCommandContext.js';
12
+ import { SHELL_INJECTION_TRIGGER, SHORTHAND_ARGS_PLACEHOLDER, } from './prompt-processors/types.js';
13
+ import { ConfirmationRequiredError, ShellProcessor, } from './prompt-processors/shellProcessor.js';
14
+ import { DefaultArgumentProcessor } from './prompt-processors/argumentProcessor.js';
15
+ import { AtFileProcessor } from './prompt-processors/atFileProcessor.js';
16
+ const mockShellProcess = vi.hoisted(() => vi.fn());
17
+ const mockAtFileProcess = vi.hoisted(() => vi.fn());
18
+ vi.mock('./prompt-processors/atFileProcessor.js', () => ({
19
+ AtFileProcessor: vi.fn().mockImplementation(() => ({
20
+ process: mockAtFileProcess,
21
+ })),
22
+ }));
23
+ vi.mock('./prompt-processors/shellProcessor.js', () => ({
24
+ ShellProcessor: vi.fn().mockImplementation(() => ({
25
+ process: mockShellProcess,
26
+ })),
27
+ ConfirmationRequiredError: class extends Error {
28
+ commandsToConfirm;
29
+ constructor(message, commandsToConfirm) {
30
+ super(message);
31
+ this.commandsToConfirm = commandsToConfirm;
32
+ this.name = 'ConfirmationRequiredError';
33
+ }
34
+ },
35
+ }));
36
+ vi.mock('./prompt-processors/argumentProcessor.js', async (importOriginal) => {
37
+ const original = await importOriginal();
38
+ return {
39
+ DefaultArgumentProcessor: vi
40
+ .fn()
41
+ .mockImplementation(() => new original.DefaultArgumentProcessor()),
42
+ };
43
+ });
44
+ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
45
+ const original = await importOriginal();
46
+ return {
47
+ ...original,
48
+ Storage: original.Storage,
49
+ isCommandAllowed: vi.fn(),
50
+ ShellExecutionService: {
51
+ execute: vi.fn(),
52
+ },
53
+ };
54
+ });
55
+ describe('FileCommandLoader', () => {
56
+ const signal = new AbortController().signal;
57
+ beforeEach(() => {
58
+ vi.clearAllMocks();
59
+ mockShellProcess.mockImplementation((prompt, context) => {
60
+ const userArgsRaw = context?.invocation?.args || '';
61
+ // This is a simplified mock. A real implementation would need to iterate
62
+ // through all parts and process only the text parts.
63
+ const firstTextPart = prompt.find((p) => typeof p === 'string' || 'text' in p);
64
+ let textContent = '';
65
+ if (typeof firstTextPart === 'string') {
66
+ textContent = firstTextPart;
67
+ }
68
+ else if (firstTextPart && 'text' in firstTextPart) {
69
+ textContent = firstTextPart.text ?? '';
70
+ }
71
+ const processedText = textContent.replaceAll(SHORTHAND_ARGS_PLACEHOLDER, userArgsRaw);
72
+ return Promise.resolve([{ text: processedText }]);
73
+ });
74
+ mockAtFileProcess.mockImplementation(async (prompt) => prompt);
75
+ });
76
+ afterEach(() => {
77
+ mock.restore();
78
+ });
79
+ it('loads a single command from a file', async () => {
80
+ const userCommandsDir = Storage.getUserCommandsDir();
81
+ mock({
82
+ [userCommandsDir]: {
83
+ 'test.toml': 'prompt = "This is a test prompt"',
84
+ },
85
+ });
86
+ const loader = new FileCommandLoader(null);
87
+ const commands = await loader.loadCommands(signal);
88
+ expect(commands).toHaveLength(1);
89
+ const command = commands[0];
90
+ expect(command).toBeDefined();
91
+ expect(command.name).toBe('test');
92
+ const result = await command.action?.(createMockCommandContext({
93
+ invocation: {
94
+ raw: '/test',
95
+ name: 'test',
96
+ args: '',
97
+ },
98
+ }), '');
99
+ if (result?.type === 'submit_prompt') {
100
+ expect(result.content).toEqual([{ text: 'This is a test prompt' }]);
101
+ }
102
+ else {
103
+ assert.fail('Incorrect action type');
104
+ }
105
+ });
106
+ // Symlink creation on Windows requires special permissions that are not
107
+ // available in the standard CI environment. Therefore, we skip these tests
108
+ // on Windows to prevent CI failures. The core functionality is still
109
+ // validated on Linux and macOS.
110
+ const itif = (condition) => (condition ? it : it.skip);
111
+ itif(process.platform !== 'win32')('loads commands from a symlinked directory', async () => {
112
+ const userCommandsDir = Storage.getUserCommandsDir();
113
+ const realCommandsDir = '/real/commands';
114
+ mock({
115
+ [realCommandsDir]: {
116
+ 'test.toml': 'prompt = "This is a test prompt"',
117
+ },
118
+ // Symlink the user commands directory to the real one
119
+ [userCommandsDir]: mock.symlink({
120
+ path: realCommandsDir,
121
+ }),
122
+ });
123
+ const loader = new FileCommandLoader(null);
124
+ const commands = await loader.loadCommands(signal);
125
+ expect(commands).toHaveLength(1);
126
+ const command = commands[0];
127
+ expect(command).toBeDefined();
128
+ expect(command.name).toBe('test');
129
+ });
130
+ itif(process.platform !== 'win32')('loads commands from a symlinked subdirectory', async () => {
131
+ const userCommandsDir = Storage.getUserCommandsDir();
132
+ const realNamespacedDir = '/real/namespaced-commands';
133
+ mock({
134
+ [userCommandsDir]: {
135
+ namespaced: mock.symlink({
136
+ path: realNamespacedDir,
137
+ }),
138
+ },
139
+ [realNamespacedDir]: {
140
+ 'my-test.toml': 'prompt = "This is a test prompt"',
141
+ },
142
+ });
143
+ const loader = new FileCommandLoader(null);
144
+ const commands = await loader.loadCommands(signal);
145
+ expect(commands).toHaveLength(1);
146
+ const command = commands[0];
147
+ expect(command).toBeDefined();
148
+ expect(command.name).toBe('namespaced:my-test');
149
+ });
150
+ it('loads multiple commands', async () => {
151
+ const userCommandsDir = Storage.getUserCommandsDir();
152
+ mock({
153
+ [userCommandsDir]: {
154
+ 'test1.toml': 'prompt = "Prompt 1"',
155
+ 'test2.toml': 'prompt = "Prompt 2"',
156
+ },
157
+ });
158
+ const loader = new FileCommandLoader(null);
159
+ const commands = await loader.loadCommands(signal);
160
+ expect(commands).toHaveLength(2);
161
+ });
162
+ it('creates deeply nested namespaces correctly', async () => {
163
+ const userCommandsDir = Storage.getUserCommandsDir();
164
+ mock({
165
+ [userCommandsDir]: {
166
+ gcp: {
167
+ pipelines: {
168
+ 'run.toml': 'prompt = "run pipeline"',
169
+ },
170
+ },
171
+ },
172
+ });
173
+ const mockConfig = {
174
+ getProjectRoot: vi.fn(() => '/path/to/project'),
175
+ getExtensions: vi.fn(() => []),
176
+ getFolderTrust: vi.fn(() => false),
177
+ isTrustedFolder: vi.fn(() => false),
178
+ };
179
+ const loader = new FileCommandLoader(mockConfig);
180
+ const commands = await loader.loadCommands(signal);
181
+ expect(commands).toHaveLength(1);
182
+ expect(commands[0].name).toBe('gcp:pipelines:run');
183
+ });
184
+ it('creates namespaces from nested directories', async () => {
185
+ const userCommandsDir = Storage.getUserCommandsDir();
186
+ mock({
187
+ [userCommandsDir]: {
188
+ git: {
189
+ 'commit.toml': 'prompt = "git commit prompt"',
190
+ },
191
+ },
192
+ });
193
+ const loader = new FileCommandLoader(null);
194
+ const commands = await loader.loadCommands(signal);
195
+ expect(commands).toHaveLength(1);
196
+ const command = commands[0];
197
+ expect(command).toBeDefined();
198
+ expect(command.name).toBe('git:commit');
199
+ });
200
+ it('returns both user and project commands in order', async () => {
201
+ const userCommandsDir = Storage.getUserCommandsDir();
202
+ const projectCommandsDir = new Storage(process.cwd()).getProjectCommandsDir();
203
+ mock({
204
+ [userCommandsDir]: {
205
+ 'test.toml': 'prompt = "User prompt"',
206
+ },
207
+ [projectCommandsDir]: {
208
+ 'test.toml': 'prompt = "Project prompt"',
209
+ },
210
+ });
211
+ const mockConfig = {
212
+ getProjectRoot: vi.fn(() => process.cwd()),
213
+ getExtensions: vi.fn(() => []),
214
+ getFolderTrust: vi.fn(() => false),
215
+ isTrustedFolder: vi.fn(() => false),
216
+ };
217
+ const loader = new FileCommandLoader(mockConfig);
218
+ const commands = await loader.loadCommands(signal);
219
+ expect(commands).toHaveLength(2);
220
+ const userResult = await commands[0].action?.(createMockCommandContext({
221
+ invocation: {
222
+ raw: '/test',
223
+ name: 'test',
224
+ args: '',
225
+ },
226
+ }), '');
227
+ if (userResult?.type === 'submit_prompt') {
228
+ expect(userResult.content).toEqual([{ text: 'User prompt' }]);
229
+ }
230
+ else {
231
+ assert.fail('Incorrect action type for user command');
232
+ }
233
+ const projectResult = await commands[1].action?.(createMockCommandContext({
234
+ invocation: {
235
+ raw: '/test',
236
+ name: 'test',
237
+ args: '',
238
+ },
239
+ }), '');
240
+ if (projectResult?.type === 'submit_prompt') {
241
+ expect(projectResult.content).toEqual([{ text: 'Project prompt' }]);
242
+ }
243
+ else {
244
+ assert.fail('Incorrect action type for project command');
245
+ }
246
+ });
247
+ it('ignores files with TOML syntax errors', async () => {
248
+ const userCommandsDir = Storage.getUserCommandsDir();
249
+ mock({
250
+ [userCommandsDir]: {
251
+ 'invalid.toml': 'this is not valid toml',
252
+ 'good.toml': 'prompt = "This one is fine"',
253
+ },
254
+ });
255
+ const loader = new FileCommandLoader(null);
256
+ const commands = await loader.loadCommands(signal);
257
+ expect(commands).toHaveLength(1);
258
+ expect(commands[0].name).toBe('good');
259
+ });
260
+ it('ignores files that are semantically invalid (missing prompt)', async () => {
261
+ const userCommandsDir = Storage.getUserCommandsDir();
262
+ mock({
263
+ [userCommandsDir]: {
264
+ 'no_prompt.toml': 'description = "This file is missing a prompt"',
265
+ 'good.toml': 'prompt = "This one is fine"',
266
+ },
267
+ });
268
+ const loader = new FileCommandLoader(null);
269
+ const commands = await loader.loadCommands(signal);
270
+ expect(commands).toHaveLength(1);
271
+ expect(commands[0].name).toBe('good');
272
+ });
273
+ it('handles filename edge cases correctly', async () => {
274
+ const userCommandsDir = Storage.getUserCommandsDir();
275
+ mock({
276
+ [userCommandsDir]: {
277
+ 'test.v1.toml': 'prompt = "Test prompt"',
278
+ },
279
+ });
280
+ const loader = new FileCommandLoader(null);
281
+ const commands = await loader.loadCommands(signal);
282
+ const command = commands[0];
283
+ expect(command).toBeDefined();
284
+ expect(command.name).toBe('test.v1');
285
+ });
286
+ it('handles file system errors gracefully', async () => {
287
+ mock({}); // Mock an empty file system
288
+ const loader = new FileCommandLoader(null);
289
+ const commands = await loader.loadCommands(signal);
290
+ expect(commands).toHaveLength(0);
291
+ });
292
+ it('uses a default description if not provided', async () => {
293
+ const userCommandsDir = Storage.getUserCommandsDir();
294
+ mock({
295
+ [userCommandsDir]: {
296
+ 'test.toml': 'prompt = "Test prompt"',
297
+ },
298
+ });
299
+ const loader = new FileCommandLoader(null);
300
+ const commands = await loader.loadCommands(signal);
301
+ const command = commands[0];
302
+ expect(command).toBeDefined();
303
+ expect(command.description).toBe('Custom command from test.toml');
304
+ });
305
+ it('uses the provided description', async () => {
306
+ const userCommandsDir = Storage.getUserCommandsDir();
307
+ mock({
308
+ [userCommandsDir]: {
309
+ 'test.toml': 'prompt = "Test prompt"\ndescription = "My test command"',
310
+ },
311
+ });
312
+ const loader = new FileCommandLoader(null);
313
+ const commands = await loader.loadCommands(signal);
314
+ const command = commands[0];
315
+ expect(command).toBeDefined();
316
+ expect(command.description).toBe('My test command');
317
+ });
318
+ it('should sanitize colons in filenames to prevent namespace conflicts', async () => {
319
+ const userCommandsDir = Storage.getUserCommandsDir();
320
+ mock({
321
+ [userCommandsDir]: {
322
+ 'legacy:command.toml': 'prompt = "This is a legacy command"',
323
+ },
324
+ });
325
+ const loader = new FileCommandLoader(null);
326
+ const commands = await loader.loadCommands(signal);
327
+ expect(commands).toHaveLength(1);
328
+ const command = commands[0];
329
+ expect(command).toBeDefined();
330
+ // Verify that the ':' in the filename was replaced with an '_'
331
+ expect(command.name).toBe('legacy_command');
332
+ });
333
+ describe('Processor Instantiation Logic', () => {
334
+ it('instantiates only DefaultArgumentProcessor if no {{args}} or !{} are present', async () => {
335
+ const userCommandsDir = Storage.getUserCommandsDir();
336
+ mock({
337
+ [userCommandsDir]: {
338
+ 'simple.toml': `prompt = "Just a regular prompt"`,
339
+ },
340
+ });
341
+ const loader = new FileCommandLoader(null);
342
+ await loader.loadCommands(signal);
343
+ expect(ShellProcessor).not.toHaveBeenCalled();
344
+ expect(DefaultArgumentProcessor).toHaveBeenCalledTimes(1);
345
+ });
346
+ it('instantiates only ShellProcessor if {{args}} is present (but not !{})', async () => {
347
+ const userCommandsDir = Storage.getUserCommandsDir();
348
+ mock({
349
+ [userCommandsDir]: {
350
+ 'args.toml': `prompt = "Prompt with {{args}}"`,
351
+ },
352
+ });
353
+ const loader = new FileCommandLoader(null);
354
+ await loader.loadCommands(signal);
355
+ expect(ShellProcessor).toHaveBeenCalledTimes(1);
356
+ expect(DefaultArgumentProcessor).not.toHaveBeenCalled();
357
+ });
358
+ it('instantiates ShellProcessor and DefaultArgumentProcessor if !{} is present (but not {{args}})', async () => {
359
+ const userCommandsDir = Storage.getUserCommandsDir();
360
+ mock({
361
+ [userCommandsDir]: {
362
+ 'shell.toml': `prompt = "Prompt with !{cmd}"`,
363
+ },
364
+ });
365
+ const loader = new FileCommandLoader(null);
366
+ await loader.loadCommands(signal);
367
+ expect(ShellProcessor).toHaveBeenCalledTimes(1);
368
+ expect(DefaultArgumentProcessor).toHaveBeenCalledTimes(1);
369
+ });
370
+ it('instantiates only ShellProcessor if both {{args}} and !{} are present', async () => {
371
+ const userCommandsDir = Storage.getUserCommandsDir();
372
+ mock({
373
+ [userCommandsDir]: {
374
+ 'both.toml': `prompt = "Prompt with {{args}} and !{cmd}"`,
375
+ },
376
+ });
377
+ const loader = new FileCommandLoader(null);
378
+ await loader.loadCommands(signal);
379
+ expect(ShellProcessor).toHaveBeenCalledTimes(1);
380
+ expect(DefaultArgumentProcessor).not.toHaveBeenCalled();
381
+ });
382
+ it('instantiates AtFileProcessor and DefaultArgumentProcessor if @{} is present', async () => {
383
+ const userCommandsDir = Storage.getUserCommandsDir();
384
+ mock({
385
+ [userCommandsDir]: {
386
+ 'at-file.toml': `prompt = "Context: @{./my-file.txt}"`,
387
+ },
388
+ });
389
+ const loader = new FileCommandLoader(null);
390
+ await loader.loadCommands(signal);
391
+ expect(AtFileProcessor).toHaveBeenCalledTimes(1);
392
+ expect(ShellProcessor).not.toHaveBeenCalled();
393
+ expect(DefaultArgumentProcessor).toHaveBeenCalledTimes(1);
394
+ });
395
+ it('instantiates ShellProcessor and AtFileProcessor if !{} and @{} are present', async () => {
396
+ const userCommandsDir = Storage.getUserCommandsDir();
397
+ mock({
398
+ [userCommandsDir]: {
399
+ 'shell-and-at.toml': `prompt = "Run !{cmd} with @{file.txt}"`,
400
+ },
401
+ });
402
+ const loader = new FileCommandLoader(null);
403
+ await loader.loadCommands(signal);
404
+ expect(ShellProcessor).toHaveBeenCalledTimes(1);
405
+ expect(AtFileProcessor).toHaveBeenCalledTimes(1);
406
+ expect(DefaultArgumentProcessor).toHaveBeenCalledTimes(1); // because no {{args}}
407
+ });
408
+ it('instantiates only ShellProcessor and AtFileProcessor if {{args}} and @{} are present', async () => {
409
+ const userCommandsDir = Storage.getUserCommandsDir();
410
+ mock({
411
+ [userCommandsDir]: {
412
+ 'args-and-at.toml': `prompt = "Run {{args}} with @{file.txt}"`,
413
+ },
414
+ });
415
+ const loader = new FileCommandLoader(null);
416
+ await loader.loadCommands(signal);
417
+ expect(ShellProcessor).toHaveBeenCalledTimes(1);
418
+ expect(AtFileProcessor).toHaveBeenCalledTimes(1);
419
+ expect(DefaultArgumentProcessor).not.toHaveBeenCalled();
420
+ });
421
+ });
422
+ describe('Extension Command Loading', () => {
423
+ it('loads commands from active extensions', async () => {
424
+ const userCommandsDir = Storage.getUserCommandsDir();
425
+ const projectCommandsDir = new Storage(process.cwd()).getProjectCommandsDir();
426
+ const extensionDir = path.join(process.cwd(), GEMINI_DIR, 'extensions', 'test-ext');
427
+ mock({
428
+ [userCommandsDir]: {
429
+ 'user.toml': 'prompt = "User command"',
430
+ },
431
+ [projectCommandsDir]: {
432
+ 'project.toml': 'prompt = "Project command"',
433
+ },
434
+ [extensionDir]: {
435
+ 'gemini-extension.json': JSON.stringify({
436
+ name: 'test-ext',
437
+ version: '1.0.0',
438
+ }),
439
+ commands: {
440
+ 'ext.toml': 'prompt = "Extension command"',
441
+ },
442
+ },
443
+ });
444
+ const mockConfig = {
445
+ getProjectRoot: vi.fn(() => process.cwd()),
446
+ getExtensions: vi.fn(() => [
447
+ {
448
+ name: 'test-ext',
449
+ version: '1.0.0',
450
+ isActive: true,
451
+ path: extensionDir,
452
+ },
453
+ ]),
454
+ getFolderTrust: vi.fn(() => false),
455
+ isTrustedFolder: vi.fn(() => false),
456
+ };
457
+ const loader = new FileCommandLoader(mockConfig);
458
+ const commands = await loader.loadCommands(signal);
459
+ expect(commands).toHaveLength(3);
460
+ const commandNames = commands.map((cmd) => cmd.name);
461
+ expect(commandNames).toEqual(['user', 'project', 'ext']);
462
+ const extCommand = commands.find((cmd) => cmd.name === 'ext');
463
+ expect(extCommand?.extensionName).toBe('test-ext');
464
+ expect(extCommand?.description).toMatch(/^\[test-ext\]/);
465
+ });
466
+ it('extension commands have extensionName metadata for conflict resolution', async () => {
467
+ const userCommandsDir = Storage.getUserCommandsDir();
468
+ const projectCommandsDir = new Storage(process.cwd()).getProjectCommandsDir();
469
+ const extensionDir = path.join(process.cwd(), GEMINI_DIR, 'extensions', 'test-ext');
470
+ mock({
471
+ [extensionDir]: {
472
+ 'gemini-extension.json': JSON.stringify({
473
+ name: 'test-ext',
474
+ version: '1.0.0',
475
+ }),
476
+ commands: {
477
+ 'deploy.toml': 'prompt = "Extension deploy command"',
478
+ },
479
+ },
480
+ [userCommandsDir]: {
481
+ 'deploy.toml': 'prompt = "User deploy command"',
482
+ },
483
+ [projectCommandsDir]: {
484
+ 'deploy.toml': 'prompt = "Project deploy command"',
485
+ },
486
+ });
487
+ const mockConfig = {
488
+ getProjectRoot: vi.fn(() => process.cwd()),
489
+ getExtensions: vi.fn(() => [
490
+ {
491
+ name: 'test-ext',
492
+ version: '1.0.0',
493
+ isActive: true,
494
+ path: extensionDir,
495
+ },
496
+ ]),
497
+ getFolderTrust: vi.fn(() => false),
498
+ isTrustedFolder: vi.fn(() => false),
499
+ };
500
+ const loader = new FileCommandLoader(mockConfig);
501
+ const commands = await loader.loadCommands(signal);
502
+ // Return all commands, even duplicates
503
+ expect(commands).toHaveLength(3);
504
+ expect(commands[0].name).toBe('deploy');
505
+ expect(commands[0].extensionName).toBeUndefined();
506
+ const result0 = await commands[0].action?.(createMockCommandContext({
507
+ invocation: {
508
+ raw: '/deploy',
509
+ name: 'deploy',
510
+ args: '',
511
+ },
512
+ }), '');
513
+ expect(result0?.type).toBe('submit_prompt');
514
+ if (result0?.type === 'submit_prompt') {
515
+ expect(result0.content).toEqual([{ text: 'User deploy command' }]);
516
+ }
517
+ expect(commands[1].name).toBe('deploy');
518
+ expect(commands[1].extensionName).toBeUndefined();
519
+ const result1 = await commands[1].action?.(createMockCommandContext({
520
+ invocation: {
521
+ raw: '/deploy',
522
+ name: 'deploy',
523
+ args: '',
524
+ },
525
+ }), '');
526
+ expect(result1?.type).toBe('submit_prompt');
527
+ if (result1?.type === 'submit_prompt') {
528
+ expect(result1.content).toEqual([{ text: 'Project deploy command' }]);
529
+ }
530
+ expect(commands[2].name).toBe('deploy');
531
+ expect(commands[2].extensionName).toBe('test-ext');
532
+ expect(commands[2].description).toMatch(/^\[test-ext\]/);
533
+ const result2 = await commands[2].action?.(createMockCommandContext({
534
+ invocation: {
535
+ raw: '/deploy',
536
+ name: 'deploy',
537
+ args: '',
538
+ },
539
+ }), '');
540
+ expect(result2?.type).toBe('submit_prompt');
541
+ if (result2?.type === 'submit_prompt') {
542
+ expect(result2.content).toEqual([{ text: 'Extension deploy command' }]);
543
+ }
544
+ });
545
+ it('only loads commands from active extensions', async () => {
546
+ const extensionDir1 = path.join(process.cwd(), GEMINI_DIR, 'extensions', 'active-ext');
547
+ const extensionDir2 = path.join(process.cwd(), GEMINI_DIR, 'extensions', 'inactive-ext');
548
+ mock({
549
+ [extensionDir1]: {
550
+ 'gemini-extension.json': JSON.stringify({
551
+ name: 'active-ext',
552
+ version: '1.0.0',
553
+ }),
554
+ commands: {
555
+ 'active.toml': 'prompt = "Active extension command"',
556
+ },
557
+ },
558
+ [extensionDir2]: {
559
+ 'gemini-extension.json': JSON.stringify({
560
+ name: 'inactive-ext',
561
+ version: '1.0.0',
562
+ }),
563
+ commands: {
564
+ 'inactive.toml': 'prompt = "Inactive extension command"',
565
+ },
566
+ },
567
+ });
568
+ const mockConfig = {
569
+ getProjectRoot: vi.fn(() => process.cwd()),
570
+ getExtensions: vi.fn(() => [
571
+ {
572
+ name: 'active-ext',
573
+ version: '1.0.0',
574
+ isActive: true,
575
+ path: extensionDir1,
576
+ },
577
+ {
578
+ name: 'inactive-ext',
579
+ version: '1.0.0',
580
+ isActive: false,
581
+ path: extensionDir2,
582
+ },
583
+ ]),
584
+ getFolderTrust: vi.fn(() => false),
585
+ isTrustedFolder: vi.fn(() => false),
586
+ };
587
+ const loader = new FileCommandLoader(mockConfig);
588
+ const commands = await loader.loadCommands(signal);
589
+ expect(commands).toHaveLength(1);
590
+ expect(commands[0].name).toBe('active');
591
+ expect(commands[0].extensionName).toBe('active-ext');
592
+ expect(commands[0].description).toMatch(/^\[active-ext\]/);
593
+ });
594
+ it('handles missing extension commands directory gracefully', async () => {
595
+ const extensionDir = path.join(process.cwd(), GEMINI_DIR, 'extensions', 'no-commands');
596
+ mock({
597
+ [extensionDir]: {
598
+ 'gemini-extension.json': JSON.stringify({
599
+ name: 'no-commands',
600
+ version: '1.0.0',
601
+ }),
602
+ // No commands directory
603
+ },
604
+ });
605
+ const mockConfig = {
606
+ getProjectRoot: vi.fn(() => process.cwd()),
607
+ getExtensions: vi.fn(() => [
608
+ {
609
+ name: 'no-commands',
610
+ version: '1.0.0',
611
+ isActive: true,
612
+ path: extensionDir,
613
+ },
614
+ ]),
615
+ getFolderTrust: vi.fn(() => false),
616
+ isTrustedFolder: vi.fn(() => false),
617
+ };
618
+ const loader = new FileCommandLoader(mockConfig);
619
+ const commands = await loader.loadCommands(signal);
620
+ expect(commands).toHaveLength(0);
621
+ });
622
+ it('handles nested command structure in extensions', async () => {
623
+ const extensionDir = path.join(process.cwd(), GEMINI_DIR, 'extensions', 'a');
624
+ mock({
625
+ [extensionDir]: {
626
+ 'gemini-extension.json': JSON.stringify({
627
+ name: 'a',
628
+ version: '1.0.0',
629
+ }),
630
+ commands: {
631
+ b: {
632
+ 'c.toml': 'prompt = "Nested command from extension a"',
633
+ d: {
634
+ 'e.toml': 'prompt = "Deeply nested command"',
635
+ },
636
+ },
637
+ 'simple.toml': 'prompt = "Simple command"',
638
+ },
639
+ },
640
+ });
641
+ const mockConfig = {
642
+ getProjectRoot: vi.fn(() => process.cwd()),
643
+ getExtensions: vi.fn(() => [
644
+ { name: 'a', version: '1.0.0', isActive: true, path: extensionDir },
645
+ ]),
646
+ getFolderTrust: vi.fn(() => false),
647
+ isTrustedFolder: vi.fn(() => false),
648
+ };
649
+ const loader = new FileCommandLoader(mockConfig);
650
+ const commands = await loader.loadCommands(signal);
651
+ expect(commands).toHaveLength(3);
652
+ const commandNames = commands.map((cmd) => cmd.name).sort();
653
+ expect(commandNames).toEqual(['b:c', 'b:d:e', 'simple']);
654
+ const nestedCmd = commands.find((cmd) => cmd.name === 'b:c');
655
+ expect(nestedCmd?.extensionName).toBe('a');
656
+ expect(nestedCmd?.description).toMatch(/^\[a\]/);
657
+ expect(nestedCmd).toBeDefined();
658
+ const result = await nestedCmd.action?.(createMockCommandContext({
659
+ invocation: {
660
+ raw: '/b:c',
661
+ name: 'b:c',
662
+ args: '',
663
+ },
664
+ }), '');
665
+ if (result?.type === 'submit_prompt') {
666
+ expect(result.content).toEqual([
667
+ { text: 'Nested command from extension a' },
668
+ ]);
669
+ }
670
+ else {
671
+ assert.fail('Incorrect action type');
672
+ }
673
+ });
674
+ });
675
+ describe('Argument Handling Integration (via ShellProcessor)', () => {
676
+ it('correctly processes a command with {{args}}', async () => {
677
+ const userCommandsDir = Storage.getUserCommandsDir();
678
+ mock({
679
+ [userCommandsDir]: {
680
+ 'shorthand.toml': 'prompt = "The user wants to: {{args}}"\ndescription = "Shorthand test"',
681
+ },
682
+ });
683
+ const loader = new FileCommandLoader(null);
684
+ const commands = await loader.loadCommands(signal);
685
+ const command = commands.find((c) => c.name === 'shorthand');
686
+ expect(command).toBeDefined();
687
+ const result = await command.action?.(createMockCommandContext({
688
+ invocation: {
689
+ raw: '/shorthand do something cool',
690
+ name: 'shorthand',
691
+ args: 'do something cool',
692
+ },
693
+ }), 'do something cool');
694
+ expect(result?.type).toBe('submit_prompt');
695
+ if (result?.type === 'submit_prompt') {
696
+ expect(result.content).toEqual([
697
+ { text: 'The user wants to: do something cool' },
698
+ ]);
699
+ }
700
+ });
701
+ });
702
+ describe('Default Argument Processor Integration', () => {
703
+ it('correctly processes a command without {{args}}', async () => {
704
+ const userCommandsDir = Storage.getUserCommandsDir();
705
+ mock({
706
+ [userCommandsDir]: {
707
+ 'model_led.toml': 'prompt = "This is the instruction."\ndescription = "Default processor test"',
708
+ },
709
+ });
710
+ const loader = new FileCommandLoader(null);
711
+ const commands = await loader.loadCommands(signal);
712
+ const command = commands.find((c) => c.name === 'model_led');
713
+ expect(command).toBeDefined();
714
+ const result = await command.action?.(createMockCommandContext({
715
+ invocation: {
716
+ raw: '/model_led 1.2.0 added "a feature"',
717
+ name: 'model_led',
718
+ args: '1.2.0 added "a feature"',
719
+ },
720
+ }), '1.2.0 added "a feature"');
721
+ expect(result?.type).toBe('submit_prompt');
722
+ if (result?.type === 'submit_prompt') {
723
+ const expectedContent = 'This is the instruction.\n\n/model_led 1.2.0 added "a feature"';
724
+ expect(result.content).toEqual([{ text: expectedContent }]);
725
+ }
726
+ });
727
+ });
728
+ describe('Shell Processor Integration', () => {
729
+ it('instantiates ShellProcessor if {{args}} is present (even without shell trigger)', async () => {
730
+ const userCommandsDir = Storage.getUserCommandsDir();
731
+ mock({
732
+ [userCommandsDir]: {
733
+ 'args_only.toml': `prompt = "Hello {{args}}"`,
734
+ },
735
+ });
736
+ const loader = new FileCommandLoader(null);
737
+ await loader.loadCommands(signal);
738
+ expect(ShellProcessor).toHaveBeenCalledWith('args_only');
739
+ });
740
+ it('instantiates ShellProcessor if the trigger is present', async () => {
741
+ const userCommandsDir = Storage.getUserCommandsDir();
742
+ mock({
743
+ [userCommandsDir]: {
744
+ 'shell.toml': `prompt = "Run this: ${SHELL_INJECTION_TRIGGER}echo hello}"`,
745
+ },
746
+ });
747
+ const loader = new FileCommandLoader(null);
748
+ await loader.loadCommands(signal);
749
+ expect(ShellProcessor).toHaveBeenCalledWith('shell');
750
+ });
751
+ it('does not instantiate ShellProcessor if no triggers ({{args}} or !{}) are present', async () => {
752
+ const userCommandsDir = Storage.getUserCommandsDir();
753
+ mock({
754
+ [userCommandsDir]: {
755
+ 'regular.toml': `prompt = "Just a regular prompt"`,
756
+ },
757
+ });
758
+ const loader = new FileCommandLoader(null);
759
+ await loader.loadCommands(signal);
760
+ expect(ShellProcessor).not.toHaveBeenCalled();
761
+ });
762
+ it('returns a "submit_prompt" action if shell processing succeeds', async () => {
763
+ const userCommandsDir = Storage.getUserCommandsDir();
764
+ mock({
765
+ [userCommandsDir]: {
766
+ 'shell.toml': `prompt = "Run !{echo 'hello'}"`,
767
+ },
768
+ });
769
+ mockShellProcess.mockResolvedValue([{ text: 'Run hello' }]);
770
+ const loader = new FileCommandLoader(null);
771
+ const commands = await loader.loadCommands(signal);
772
+ const command = commands.find((c) => c.name === 'shell');
773
+ expect(command).toBeDefined();
774
+ const result = await command.action(createMockCommandContext({
775
+ invocation: { raw: '/shell', name: 'shell', args: '' },
776
+ }), '');
777
+ expect(result?.type).toBe('submit_prompt');
778
+ if (result?.type === 'submit_prompt') {
779
+ expect(result.content).toEqual([{ text: 'Run hello' }]);
780
+ }
781
+ });
782
+ it('returns a "confirm_shell_commands" action if shell processing requires it', async () => {
783
+ const userCommandsDir = Storage.getUserCommandsDir();
784
+ const rawInvocation = '/shell rm -rf /';
785
+ mock({
786
+ [userCommandsDir]: {
787
+ 'shell.toml': `prompt = "Run !{rm -rf /}"`,
788
+ },
789
+ });
790
+ // Mock the processor to throw the specific error
791
+ const error = new ConfirmationRequiredError('Confirmation needed', [
792
+ 'rm -rf /',
793
+ ]);
794
+ mockShellProcess.mockRejectedValue(error);
795
+ const loader = new FileCommandLoader(null);
796
+ const commands = await loader.loadCommands(signal);
797
+ const command = commands.find((c) => c.name === 'shell');
798
+ expect(command).toBeDefined();
799
+ const result = await command.action(createMockCommandContext({
800
+ invocation: { raw: rawInvocation, name: 'shell', args: 'rm -rf /' },
801
+ }), 'rm -rf /');
802
+ expect(result?.type).toBe('confirm_shell_commands');
803
+ if (result?.type === 'confirm_shell_commands') {
804
+ expect(result.commandsToConfirm).toEqual(['rm -rf /']);
805
+ expect(result.originalInvocation.raw).toBe(rawInvocation);
806
+ }
807
+ });
808
+ it('re-throws other errors from the processor', async () => {
809
+ const userCommandsDir = Storage.getUserCommandsDir();
810
+ mock({
811
+ [userCommandsDir]: {
812
+ 'shell.toml': `prompt = "Run !{something}"`,
813
+ },
814
+ });
815
+ const genericError = new Error('Something else went wrong');
816
+ mockShellProcess.mockRejectedValue(genericError);
817
+ const loader = new FileCommandLoader(null);
818
+ const commands = await loader.loadCommands(signal);
819
+ const command = commands.find((c) => c.name === 'shell');
820
+ expect(command).toBeDefined();
821
+ await expect(command.action(createMockCommandContext({
822
+ invocation: { raw: '/shell', name: 'shell', args: '' },
823
+ }), '')).rejects.toThrow('Something else went wrong');
824
+ });
825
+ it('assembles the processor pipeline in the correct order (AtFile -> Shell -> Default)', async () => {
826
+ const userCommandsDir = Storage.getUserCommandsDir();
827
+ mock({
828
+ [userCommandsDir]: {
829
+ // This prompt uses !{}, @{}, but NOT {{args}}, so all processors should be active.
830
+ 'pipeline.toml': `
831
+ prompt = "Shell says: !{echo foo}. File says: @{./bar.txt}"
832
+ `,
833
+ },
834
+ './bar.txt': 'bar content',
835
+ });
836
+ const defaultProcessMock = vi
837
+ .fn()
838
+ .mockImplementation((p) => Promise.resolve([
839
+ { text: `${p[0].text}-default-processed` },
840
+ ]));
841
+ mockShellProcess.mockImplementation((p) => Promise.resolve([
842
+ { text: `${p[0].text}-shell-processed` },
843
+ ]));
844
+ mockAtFileProcess.mockImplementation((p) => Promise.resolve([
845
+ { text: `${p[0].text}-at-file-processed` },
846
+ ]));
847
+ vi.mocked(DefaultArgumentProcessor).mockImplementation(() => ({
848
+ process: defaultProcessMock,
849
+ }));
850
+ const loader = new FileCommandLoader(null);
851
+ const commands = await loader.loadCommands(signal);
852
+ const command = commands.find((c) => c.name === 'pipeline');
853
+ expect(command).toBeDefined();
854
+ const result = await command.action(createMockCommandContext({
855
+ invocation: {
856
+ raw: '/pipeline baz',
857
+ name: 'pipeline',
858
+ args: 'baz',
859
+ },
860
+ }), 'baz');
861
+ expect(mockAtFileProcess.mock.invocationCallOrder[0]).toBeLessThan(mockShellProcess.mock.invocationCallOrder[0]);
862
+ expect(mockShellProcess.mock.invocationCallOrder[0]).toBeLessThan(defaultProcessMock.mock.invocationCallOrder[0]);
863
+ // Verify the flow of the prompt through the processors
864
+ // 1. AtFile processor runs first
865
+ expect(mockAtFileProcess).toHaveBeenCalledWith([{ text: expect.stringContaining('@{./bar.txt}') }], expect.any(Object));
866
+ // 2. Shell processor runs second
867
+ expect(mockShellProcess).toHaveBeenCalledWith([{ text: expect.stringContaining('-at-file-processed') }], expect.any(Object));
868
+ // 3. Default processor runs third
869
+ expect(defaultProcessMock).toHaveBeenCalledWith([{ text: expect.stringContaining('-shell-processed') }], expect.any(Object));
870
+ if (result?.type === 'submit_prompt') {
871
+ const contentAsArray = Array.isArray(result.content)
872
+ ? result.content
873
+ : [result.content];
874
+ expect(contentAsArray.length).toBeGreaterThan(0);
875
+ const firstPart = contentAsArray[0];
876
+ if (typeof firstPart === 'object' && firstPart && 'text' in firstPart) {
877
+ expect(firstPart.text).toContain('-at-file-processed-shell-processed-default-processed');
878
+ }
879
+ else {
880
+ assert.fail('First part of content is not a text part or is a string');
881
+ }
882
+ }
883
+ else {
884
+ assert.fail('Incorrect action type');
885
+ }
886
+ });
887
+ });
888
+ describe('@-file Processor Integration', () => {
889
+ it('correctly processes a command with @{file}', async () => {
890
+ const userCommandsDir = Storage.getUserCommandsDir();
891
+ mock({
892
+ [userCommandsDir]: {
893
+ 'at-file.toml': 'prompt = "Context from file: @{./test.txt}"\ndescription = "@-file test"',
894
+ },
895
+ './test.txt': 'file content',
896
+ });
897
+ mockAtFileProcess.mockImplementation(async (prompt) => {
898
+ // A simplified mock of AtFileProcessor's behavior
899
+ const textContent = prompt[0].text;
900
+ if (textContent.includes('@{./test.txt}')) {
901
+ return [
902
+ {
903
+ text: textContent.replace('@{./test.txt}', 'file content'),
904
+ },
905
+ ];
906
+ }
907
+ return prompt;
908
+ });
909
+ // Prevent default processor from interfering
910
+ vi.mocked(DefaultArgumentProcessor).mockImplementation(() => ({
911
+ process: (p) => Promise.resolve(p),
912
+ }));
913
+ const loader = new FileCommandLoader(null);
914
+ const commands = await loader.loadCommands(signal);
915
+ const command = commands.find((c) => c.name === 'at-file');
916
+ expect(command).toBeDefined();
917
+ const result = await command.action?.(createMockCommandContext({
918
+ invocation: {
919
+ raw: '/at-file',
920
+ name: 'at-file',
921
+ args: '',
922
+ },
923
+ }), '');
924
+ expect(result?.type).toBe('submit_prompt');
925
+ if (result?.type === 'submit_prompt') {
926
+ expect(result.content).toEqual([
927
+ { text: 'Context from file: file content' },
928
+ ]);
929
+ }
930
+ });
931
+ });
932
+ describe('with folder trust enabled', () => {
933
+ it('loads multiple commands', async () => {
934
+ const mockConfig = {
935
+ getProjectRoot: vi.fn(() => '/path/to/project'),
936
+ getExtensions: vi.fn(() => []),
937
+ getFolderTrust: vi.fn(() => true),
938
+ isTrustedFolder: vi.fn(() => true),
939
+ };
940
+ const userCommandsDir = Storage.getUserCommandsDir();
941
+ mock({
942
+ [userCommandsDir]: {
943
+ 'test1.toml': 'prompt = "Prompt 1"',
944
+ 'test2.toml': 'prompt = "Prompt 2"',
945
+ },
946
+ });
947
+ const loader = new FileCommandLoader(mockConfig);
948
+ const commands = await loader.loadCommands(signal);
949
+ expect(commands).toHaveLength(2);
950
+ });
951
+ it('does not load when folder is not trusted', async () => {
952
+ const mockConfig = {
953
+ getProjectRoot: vi.fn(() => '/path/to/project'),
954
+ getExtensions: vi.fn(() => []),
955
+ getFolderTrust: vi.fn(() => true),
956
+ isTrustedFolder: vi.fn(() => false),
957
+ };
958
+ const userCommandsDir = Storage.getUserCommandsDir();
959
+ mock({
960
+ [userCommandsDir]: {
961
+ 'test1.toml': 'prompt = "Prompt 1"',
962
+ 'test2.toml': 'prompt = "Prompt 2"',
963
+ },
964
+ });
965
+ const loader = new FileCommandLoader(mockConfig);
966
+ const commands = await loader.loadCommands(signal);
967
+ expect(commands).toHaveLength(0);
968
+ });
969
+ });
970
+ });
971
+ //# sourceMappingURL=FileCommandLoader.test.js.map