@google/gemini-cli 0.24.0-nightly.20260103.30f5c4af4 → 0.25.0-nightly.20260112.15891721a

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 (391) hide show
  1. package/README.md +1 -1
  2. package/dist/google-gemini-cli-0.25.0-nightly.20260107.59a18e710.tgz +0 -0
  3. package/dist/package.json +3 -3
  4. package/dist/src/commands/extensions/configure.d.ts +13 -0
  5. package/dist/src/commands/extensions/configure.js +124 -0
  6. package/dist/src/commands/extensions/configure.js.map +1 -0
  7. package/dist/src/commands/extensions/configure.test.d.ts +1 -0
  8. package/dist/src/commands/extensions/configure.test.js +197 -0
  9. package/dist/src/commands/extensions/configure.test.js.map +1 -0
  10. package/dist/src/commands/extensions/examples/hooks/gemini-extension.json +4 -0
  11. package/dist/src/commands/extensions/examples/hooks/hooks/hooks.json +14 -0
  12. package/dist/src/commands/extensions/examples/hooks/scripts/on-start.js +8 -0
  13. package/dist/src/commands/extensions/examples/mcp-server/README.md +35 -0
  14. package/dist/src/commands/extensions/examples/mcp-server/example.js +36 -22
  15. package/dist/src/commands/extensions/examples/mcp-server/gemini-extension.json +1 -1
  16. package/dist/src/commands/extensions/examples/mcp-server/package.json +0 -7
  17. package/dist/src/commands/extensions/examples/skills/gemini-extension.json +4 -0
  18. package/dist/src/commands/extensions/examples/skills/skills/greeter/SKILL.md +7 -0
  19. package/dist/src/commands/extensions/utils.d.ts +1 -0
  20. package/dist/src/commands/extensions/utils.js +5 -1
  21. package/dist/src/commands/extensions/utils.js.map +1 -1
  22. package/dist/src/commands/extensions.js +2 -2
  23. package/dist/src/commands/extensions.js.map +1 -1
  24. package/dist/src/commands/hooks/migrate.js +1 -1
  25. package/dist/src/commands/hooks/migrate.js.map +1 -1
  26. package/dist/src/commands/hooks/migrate.test.js +1 -1
  27. package/dist/src/commands/hooks/migrate.test.js.map +1 -1
  28. package/dist/src/commands/skills/disable.d.ts +14 -0
  29. package/dist/src/commands/skills/disable.js +45 -0
  30. package/dist/src/commands/skills/disable.js.map +1 -0
  31. package/dist/src/commands/skills/disable.test.d.ts +6 -0
  32. package/dist/src/commands/skills/disable.test.js +80 -0
  33. package/dist/src/commands/skills/disable.test.js.map +1 -0
  34. package/dist/src/commands/skills/enable.d.ts +12 -0
  35. package/dist/src/commands/skills/enable.js +35 -0
  36. package/dist/src/commands/skills/enable.js.map +1 -0
  37. package/dist/src/commands/skills/enable.test.d.ts +6 -0
  38. package/dist/src/commands/skills/enable.test.js +107 -0
  39. package/dist/src/commands/skills/enable.test.js.map +1 -0
  40. package/dist/src/commands/skills/list.d.ts +10 -0
  41. package/dist/src/commands/skills/list.js +60 -0
  42. package/dist/src/commands/skills/list.js.map +1 -0
  43. package/dist/src/commands/skills/list.test.d.ts +6 -0
  44. package/dist/src/commands/skills/list.test.js +136 -0
  45. package/dist/src/commands/skills/list.test.js.map +1 -0
  46. package/dist/src/commands/{extensions/settings.d.ts → skills.d.ts} +2 -2
  47. package/dist/src/commands/skills.js +26 -0
  48. package/dist/src/commands/skills.js.map +1 -0
  49. package/dist/src/commands/skills.test.d.ts +6 -0
  50. package/dist/src/commands/skills.test.js +49 -0
  51. package/dist/src/commands/skills.test.js.map +1 -0
  52. package/dist/src/config/config.js +61 -23
  53. package/dist/src/config/config.js.map +1 -1
  54. package/dist/src/config/config.test.js +220 -1
  55. package/dist/src/config/config.test.js.map +1 -1
  56. package/dist/src/config/extension-manager-scope.test.js +144 -0
  57. package/dist/src/config/extension-manager-scope.test.js.map +1 -0
  58. package/dist/src/config/extension-manager-skills.test.js +146 -0
  59. package/dist/src/config/extension-manager-skills.test.js.map +1 -0
  60. package/dist/src/config/extension-manager.js +80 -28
  61. package/dist/src/config/extension-manager.js.map +1 -1
  62. package/dist/src/config/extension.test.js +81 -10
  63. package/dist/src/config/extension.test.js.map +1 -1
  64. package/dist/src/config/extensions/consent.d.ts +5 -3
  65. package/dist/src/config/extensions/consent.js +30 -7
  66. package/dist/src/config/extensions/consent.js.map +1 -1
  67. package/dist/src/config/extensions/consent.test.js +97 -3
  68. package/dist/src/config/extensions/consent.test.js.map +1 -1
  69. package/dist/src/config/extensions/extensionSettings.d.ts +5 -3
  70. package/dist/src/config/extensions/extensionSettings.js +32 -12
  71. package/dist/src/config/extensions/extensionSettings.js.map +1 -1
  72. package/dist/src/config/extensions/extensionSettings.test.js +8 -8
  73. package/dist/src/config/extensions/extensionSettings.test.js.map +1 -1
  74. package/dist/src/config/extensions/extensionUpdates.test.d.ts +6 -0
  75. package/dist/src/config/extensions/extensionUpdates.test.js +227 -0
  76. package/dist/src/config/extensions/extensionUpdates.test.js.map +1 -0
  77. package/dist/src/config/extensions/storage.js +2 -2
  78. package/dist/src/config/extensions/storage.js.map +1 -1
  79. package/dist/src/config/settings.d.ts +6 -7
  80. package/dist/src/config/settings.js +61 -238
  81. package/dist/src/config/settings.js.map +1 -1
  82. package/dist/src/config/settings.test.js +228 -706
  83. package/dist/src/config/settings.test.js.map +1 -1
  84. package/dist/src/config/settingsSchema.d.ts +119 -15
  85. package/dist/src/config/settingsSchema.js +123 -15
  86. package/dist/src/config/settingsSchema.js.map +1 -1
  87. package/dist/src/config/settingsSchema.test.js +8 -0
  88. package/dist/src/config/settingsSchema.test.js.map +1 -1
  89. package/dist/src/config/settings_validation_warning.test.js +10 -5
  90. package/dist/src/config/settings_validation_warning.test.js.map +1 -1
  91. package/dist/src/config/trustedFolders.js +1 -2
  92. package/dist/src/config/trustedFolders.js.map +1 -1
  93. package/dist/src/config/trustedFolders.test.js +7 -0
  94. package/dist/src/config/trustedFolders.test.js.map +1 -1
  95. package/dist/src/gemini.js +67 -60
  96. package/dist/src/gemini.js.map +1 -1
  97. package/dist/src/gemini.test.js +36 -83
  98. package/dist/src/gemini.test.js.map +1 -1
  99. package/dist/src/gemini_cleanup.test.js +2 -0
  100. package/dist/src/gemini_cleanup.test.js.map +1 -1
  101. package/dist/src/generated/git-commit.d.ts +2 -2
  102. package/dist/src/generated/git-commit.js +2 -2
  103. package/dist/src/nonInteractiveCli.js +24 -0
  104. package/dist/src/nonInteractiveCli.js.map +1 -1
  105. package/dist/src/nonInteractiveCli.test.js +46 -1
  106. package/dist/src/nonInteractiveCli.test.js.map +1 -1
  107. package/dist/src/services/BuiltinCommandLoader.d.ts +1 -1
  108. package/dist/src/services/BuiltinCommandLoader.js +45 -6
  109. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  110. package/dist/src/services/BuiltinCommandLoader.test.js +58 -1
  111. package/dist/src/services/BuiltinCommandLoader.test.js.map +1 -1
  112. package/dist/src/services/prompt-processors/shellProcessor.js +13 -11
  113. package/dist/src/services/prompt-processors/shellProcessor.js.map +1 -1
  114. package/dist/src/services/prompt-processors/shellProcessor.test.js +93 -61
  115. package/dist/src/services/prompt-processors/shellProcessor.test.js.map +1 -1
  116. package/dist/src/test-utils/render.js +2 -2
  117. package/dist/src/test-utils/render.js.map +1 -1
  118. package/dist/src/ui/App.test.js +2 -2
  119. package/dist/src/ui/App.test.js.map +1 -1
  120. package/dist/src/ui/AppContainer.js +47 -22
  121. package/dist/src/ui/AppContainer.js.map +1 -1
  122. package/dist/src/ui/AppContainer.test.js +16 -0
  123. package/dist/src/ui/AppContainer.test.js.map +1 -1
  124. package/dist/src/ui/commands/agentsCommand.d.ts +7 -0
  125. package/dist/src/ui/commands/agentsCommand.js +75 -0
  126. package/dist/src/ui/commands/agentsCommand.js.map +1 -0
  127. package/dist/src/ui/commands/agentsCommand.test.d.ts +6 -0
  128. package/dist/src/ui/commands/agentsCommand.test.js +91 -0
  129. package/dist/src/ui/commands/agentsCommand.test.js.map +1 -0
  130. package/dist/src/ui/commands/bugCommand.js +27 -4
  131. package/dist/src/ui/commands/bugCommand.js.map +1 -1
  132. package/dist/src/ui/commands/bugCommand.test.js +70 -2
  133. package/dist/src/ui/commands/bugCommand.test.js.map +1 -1
  134. package/dist/src/ui/commands/chatCommand.d.ts +1 -2
  135. package/dist/src/ui/commands/chatCommand.js +38 -30
  136. package/dist/src/ui/commands/chatCommand.js.map +1 -1
  137. package/dist/src/ui/commands/chatCommand.test.js +79 -52
  138. package/dist/src/ui/commands/chatCommand.test.js.map +1 -1
  139. package/dist/src/ui/commands/clearCommand.js +12 -8
  140. package/dist/src/ui/commands/clearCommand.js.map +1 -1
  141. package/dist/src/ui/commands/clearCommand.test.js +4 -0
  142. package/dist/src/ui/commands/clearCommand.test.js.map +1 -1
  143. package/dist/src/ui/commands/extensionsCommand.js +70 -2
  144. package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
  145. package/dist/src/ui/commands/extensionsCommand.test.js +70 -3
  146. package/dist/src/ui/commands/extensionsCommand.test.js.map +1 -1
  147. package/dist/src/ui/commands/hooksCommand.js +153 -18
  148. package/dist/src/ui/commands/hooksCommand.js.map +1 -1
  149. package/dist/src/ui/commands/hooksCommand.test.js +186 -15
  150. package/dist/src/ui/commands/hooksCommand.test.js.map +1 -1
  151. package/dist/src/ui/commands/mcpCommand.test.js +10 -2
  152. package/dist/src/ui/commands/mcpCommand.test.js.map +1 -1
  153. package/dist/src/ui/commands/skillsCommand.d.ts +1 -1
  154. package/dist/src/ui/commands/skillsCommand.js +104 -32
  155. package/dist/src/ui/commands/skillsCommand.js.map +1 -1
  156. package/dist/src/ui/commands/skillsCommand.test.d.ts +1 -1
  157. package/dist/src/ui/commands/skillsCommand.test.js +230 -8
  158. package/dist/src/ui/commands/skillsCommand.test.js.map +1 -1
  159. package/dist/src/ui/commands/types.d.ts +2 -1
  160. package/dist/src/ui/commands/types.js +1 -0
  161. package/dist/src/ui/commands/types.js.map +1 -1
  162. package/dist/src/ui/components/Composer.js +4 -6
  163. package/dist/src/ui/components/Composer.js.map +1 -1
  164. package/dist/src/ui/components/Composer.test.js +13 -0
  165. package/dist/src/ui/components/Composer.test.js.map +1 -1
  166. package/dist/src/ui/components/ContextSummaryDisplay.test.js +54 -26
  167. package/dist/src/ui/components/ContextSummaryDisplay.test.js.map +1 -1
  168. package/dist/src/ui/components/DialogManager.js +1 -1
  169. package/dist/src/ui/components/DialogManager.js.map +1 -1
  170. package/dist/src/ui/components/FolderTrustDialog.js +19 -14
  171. package/dist/src/ui/components/FolderTrustDialog.js.map +1 -1
  172. package/dist/src/ui/components/FolderTrustDialog.test.js +12 -2
  173. package/dist/src/ui/components/FolderTrustDialog.test.js.map +1 -1
  174. package/dist/src/ui/components/Footer.js +4 -4
  175. package/dist/src/ui/components/Footer.js.map +1 -1
  176. package/dist/src/ui/components/HistoryItemDisplay.js +2 -1
  177. package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
  178. package/dist/src/ui/components/HistoryItemDisplay.test.js +21 -0
  179. package/dist/src/ui/components/HistoryItemDisplay.test.js.map +1 -1
  180. package/dist/src/ui/components/HookStatusDisplay.d.ts +12 -0
  181. package/dist/src/ui/components/HookStatusDisplay.js +20 -0
  182. package/dist/src/ui/components/HookStatusDisplay.js.map +1 -0
  183. package/dist/src/ui/components/HookStatusDisplay.test.d.ts +6 -0
  184. package/dist/src/ui/components/HookStatusDisplay.test.js +51 -0
  185. package/dist/src/ui/components/HookStatusDisplay.test.js.map +1 -0
  186. package/dist/src/ui/components/InputPrompt.js +14 -6
  187. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  188. package/dist/src/ui/components/InputPrompt.test.js +19 -2
  189. package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
  190. package/dist/src/ui/components/MainContent.js +6 -1
  191. package/dist/src/ui/components/MainContent.js.map +1 -1
  192. package/dist/src/ui/components/ModelDialog.js +7 -3
  193. package/dist/src/ui/components/ModelDialog.js.map +1 -1
  194. package/dist/src/ui/components/ModelDialog.test.js +16 -3
  195. package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
  196. package/dist/src/ui/components/Notifications.js +2 -3
  197. package/dist/src/ui/components/Notifications.js.map +1 -1
  198. package/dist/src/ui/components/Notifications.test.js +1 -0
  199. package/dist/src/ui/components/Notifications.test.js.map +1 -1
  200. package/dist/src/ui/components/ProQuotaDialog.d.ts +1 -3
  201. package/dist/src/ui/components/ProQuotaDialog.js +8 -21
  202. package/dist/src/ui/components/ProQuotaDialog.js.map +1 -1
  203. package/dist/src/ui/components/ProQuotaDialog.test.js +31 -11
  204. package/dist/src/ui/components/ProQuotaDialog.test.js.map +1 -1
  205. package/dist/src/ui/components/SettingsDialog.js +33 -13
  206. package/dist/src/ui/components/SettingsDialog.js.map +1 -1
  207. package/dist/src/ui/components/SettingsDialog.test.js +15 -1
  208. package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
  209. package/dist/src/ui/components/StatusDisplay.d.ts +11 -0
  210. package/dist/src/ui/components/StatusDisplay.js +40 -0
  211. package/dist/src/ui/components/StatusDisplay.js.map +1 -0
  212. package/dist/src/ui/components/StatusDisplay.test.d.ts +6 -0
  213. package/dist/src/ui/components/StatusDisplay.test.js +144 -0
  214. package/dist/src/ui/components/StatusDisplay.test.js.map +1 -0
  215. package/dist/src/ui/components/SuggestionsDisplay.js +7 -2
  216. package/dist/src/ui/components/SuggestionsDisplay.js.map +1 -1
  217. package/dist/src/ui/components/ThemeDialog.test.js +1 -1
  218. package/dist/src/ui/components/ThemeDialog.test.js.map +1 -1
  219. package/dist/src/ui/components/messages/ToolMessage.test.js +1 -0
  220. package/dist/src/ui/components/messages/ToolMessage.test.js.map +1 -1
  221. package/dist/src/ui/components/views/AgentsStatus.d.ts +13 -0
  222. package/dist/src/ui/components/views/AgentsStatus.js +23 -0
  223. package/dist/src/ui/components/views/AgentsStatus.js.map +1 -0
  224. package/dist/src/ui/components/views/ExtensionsList.js +2 -1
  225. package/dist/src/ui/components/views/ExtensionsList.js.map +1 -1
  226. package/dist/src/ui/components/views/ExtensionsList.test.js +12 -1
  227. package/dist/src/ui/components/views/ExtensionsList.test.js.map +1 -1
  228. package/dist/src/ui/components/views/HooksList.js +25 -16
  229. package/dist/src/ui/components/views/HooksList.js.map +1 -1
  230. package/dist/src/ui/components/views/SkillsList.js +9 -7
  231. package/dist/src/ui/components/views/SkillsList.js.map +1 -1
  232. package/dist/src/ui/components/views/SkillsList.test.js +37 -4
  233. package/dist/src/ui/components/views/SkillsList.test.js.map +1 -1
  234. package/dist/src/ui/constants/tips.js +0 -1
  235. package/dist/src/ui/constants/tips.js.map +1 -1
  236. package/dist/src/ui/constants.d.ts +2 -0
  237. package/dist/src/ui/constants.js +2 -0
  238. package/dist/src/ui/constants.js.map +1 -1
  239. package/dist/src/ui/contexts/KeypressContext.d.ts +1 -0
  240. package/dist/src/ui/contexts/KeypressContext.js +56 -6
  241. package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
  242. package/dist/src/ui/contexts/KeypressContext.test.js +99 -8
  243. package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
  244. package/dist/src/ui/contexts/UIStateContext.d.ts +3 -1
  245. package/dist/src/ui/contexts/UIStateContext.js.map +1 -1
  246. package/dist/src/ui/hooks/atCommandProcessor.js +16 -2
  247. package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
  248. package/dist/src/ui/hooks/atCommandProcessor.test.js +9 -3
  249. package/dist/src/ui/hooks/atCommandProcessor.test.js.map +1 -1
  250. package/dist/src/ui/hooks/atCommandProcessor_agents.test.d.ts +6 -0
  251. package/dist/src/ui/hooks/atCommandProcessor_agents.test.js +183 -0
  252. package/dist/src/ui/hooks/atCommandProcessor_agents.test.js.map +1 -0
  253. package/dist/src/ui/hooks/slashCommandProcessor.test.js +1 -0
  254. package/dist/src/ui/hooks/slashCommandProcessor.test.js.map +1 -1
  255. package/dist/src/ui/hooks/useAtCompletion.js +31 -0
  256. package/dist/src/ui/hooks/useAtCompletion.js.map +1 -1
  257. package/dist/src/ui/hooks/useAtCompletion_agents.test.d.ts +6 -0
  258. package/dist/src/ui/hooks/useAtCompletion_agents.test.js +85 -0
  259. package/dist/src/ui/hooks/useAtCompletion_agents.test.js.map +1 -0
  260. package/dist/src/ui/hooks/useCommandCompletion.js +12 -8
  261. package/dist/src/ui/hooks/useCommandCompletion.js.map +1 -1
  262. package/dist/src/ui/hooks/useCommandCompletion.test.js +81 -0
  263. package/dist/src/ui/hooks/useCommandCompletion.test.js.map +1 -1
  264. package/dist/src/ui/hooks/useExtensionUpdates.test.js +7 -0
  265. package/dist/src/ui/hooks/useExtensionUpdates.test.js.map +1 -1
  266. package/dist/src/ui/hooks/useFolderTrust.js +14 -19
  267. package/dist/src/ui/hooks/useFolderTrust.js.map +1 -1
  268. package/dist/src/ui/hooks/useFolderTrust.test.js +30 -22
  269. package/dist/src/ui/hooks/useFolderTrust.test.js.map +1 -1
  270. package/dist/src/ui/hooks/useGeminiStream.js +29 -0
  271. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  272. package/dist/src/ui/hooks/useGeminiStream.test.js +82 -0
  273. package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
  274. package/dist/src/ui/hooks/useHookDisplayState.d.ts +7 -0
  275. package/dist/src/ui/hooks/useHookDisplayState.js +83 -0
  276. package/dist/src/ui/hooks/useHookDisplayState.js.map +1 -0
  277. package/dist/src/ui/hooks/useHookDisplayState.test.d.ts +6 -0
  278. package/dist/src/ui/hooks/useHookDisplayState.test.js +180 -0
  279. package/dist/src/ui/hooks/useHookDisplayState.test.js.map +1 -0
  280. package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js +10 -3
  281. package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js.map +1 -1
  282. package/dist/src/ui/hooks/useQuotaAndFallback.js +15 -10
  283. package/dist/src/ui/hooks/useQuotaAndFallback.js.map +1 -1
  284. package/dist/src/ui/hooks/useQuotaAndFallback.test.js +13 -8
  285. package/dist/src/ui/hooks/useQuotaAndFallback.test.js.map +1 -1
  286. package/dist/src/ui/hooks/useRewind.d.ts +14 -0
  287. package/dist/src/ui/hooks/useRewind.js +31 -0
  288. package/dist/src/ui/hooks/useRewind.js.map +1 -0
  289. package/dist/src/ui/hooks/useRewind.test.d.ts +6 -0
  290. package/dist/src/ui/hooks/useRewind.test.js +100 -0
  291. package/dist/src/ui/hooks/useRewind.test.js.map +1 -0
  292. package/dist/src/ui/hooks/useToolScheduler.test.js +11 -2
  293. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
  294. package/dist/src/ui/themes/theme-manager.js +2 -3
  295. package/dist/src/ui/themes/theme-manager.js.map +1 -1
  296. package/dist/src/ui/themes/theme-manager.test.js +7 -0
  297. package/dist/src/ui/themes/theme-manager.test.js.map +1 -1
  298. package/dist/src/ui/types.d.ts +15 -8
  299. package/dist/src/ui/types.js +1 -0
  300. package/dist/src/ui/types.js.map +1 -1
  301. package/dist/src/ui/utils/CodeColorizer.test.js +1 -1
  302. package/dist/src/ui/utils/CodeColorizer.test.js.map +1 -1
  303. package/dist/src/ui/utils/MarkdownDisplay.test.js +1 -1
  304. package/dist/src/ui/utils/MarkdownDisplay.test.js.map +1 -1
  305. package/dist/src/ui/utils/commandUtils.js +12 -7
  306. package/dist/src/ui/utils/commandUtils.js.map +1 -1
  307. package/dist/src/ui/utils/commandUtils.test.js +24 -0
  308. package/dist/src/ui/utils/commandUtils.test.js.map +1 -1
  309. package/dist/src/ui/utils/directoryUtils.js +4 -4
  310. package/dist/src/ui/utils/directoryUtils.js.map +1 -1
  311. package/dist/src/ui/utils/directoryUtils.test.js +1 -0
  312. package/dist/src/ui/utils/directoryUtils.test.js.map +1 -1
  313. package/dist/src/ui/utils/historyExportUtils.d.ts +21 -0
  314. package/dist/src/ui/utils/historyExportUtils.js +59 -0
  315. package/dist/src/ui/utils/historyExportUtils.js.map +1 -0
  316. package/dist/src/ui/utils/rewindFileOps.d.ts +47 -0
  317. package/dist/src/ui/utils/rewindFileOps.js +190 -0
  318. package/dist/src/ui/utils/rewindFileOps.js.map +1 -0
  319. package/dist/src/ui/utils/rewindFileOps.test.d.ts +6 -0
  320. package/dist/src/ui/utils/rewindFileOps.test.js +375 -0
  321. package/dist/src/ui/utils/rewindFileOps.test.js.map +1 -0
  322. package/dist/src/ui/utils/terminalCapabilityManager.d.ts +4 -8
  323. package/dist/src/ui/utils/terminalCapabilityManager.js +43 -72
  324. package/dist/src/ui/utils/terminalCapabilityManager.js.map +1 -1
  325. package/dist/src/ui/utils/terminalCapabilityManager.test.js +20 -6
  326. package/dist/src/ui/utils/terminalCapabilityManager.test.js.map +1 -1
  327. package/dist/src/ui/utils/terminalSetup.d.ts +1 -1
  328. package/dist/src/ui/utils/terminalSetup.js +19 -4
  329. package/dist/src/ui/utils/terminalSetup.js.map +1 -1
  330. package/dist/src/ui/utils/terminalSetup.test.js +7 -0
  331. package/dist/src/ui/utils/terminalSetup.test.js.map +1 -1
  332. package/dist/src/ui/utils/textUtils.js +9 -1
  333. package/dist/src/ui/utils/textUtils.js.map +1 -1
  334. package/dist/src/ui/utils/textUtils.test.js +12 -1
  335. package/dist/src/ui/utils/textUtils.test.js.map +1 -1
  336. package/dist/src/ui/utils/ui-sizing.js +1 -1
  337. package/dist/src/ui/utils/ui-sizing.js.map +1 -1
  338. package/dist/src/ui/utils/ui-sizing.test.js +2 -2
  339. package/dist/src/ui/utils/ui-sizing.test.js.map +1 -1
  340. package/dist/src/utils/activityLogger.d.ts +47 -0
  341. package/dist/src/utils/activityLogger.js +297 -0
  342. package/dist/src/utils/activityLogger.js.map +1 -0
  343. package/dist/src/utils/cleanup.d.ts +5 -0
  344. package/dist/src/utils/cleanup.js +17 -0
  345. package/dist/src/utils/cleanup.js.map +1 -1
  346. package/dist/src/utils/cleanup.test.js +21 -31
  347. package/dist/src/utils/cleanup.test.js.map +1 -1
  348. package/dist/src/utils/resolvePath.js +3 -3
  349. package/dist/src/utils/resolvePath.js.map +1 -1
  350. package/dist/src/utils/resolvePath.test.js +3 -0
  351. package/dist/src/utils/resolvePath.test.js.map +1 -1
  352. package/dist/src/utils/sandbox.js +17 -10
  353. package/dist/src/utils/sandbox.js.map +1 -1
  354. package/dist/src/utils/sandbox.test.js +17 -5
  355. package/dist/src/utils/sandbox.test.js.map +1 -1
  356. package/dist/src/utils/settingsUtils.js +0 -5
  357. package/dist/src/utils/settingsUtils.js.map +1 -1
  358. package/dist/src/utils/skillSettings.d.ts +33 -0
  359. package/dist/src/utils/skillSettings.js +101 -0
  360. package/dist/src/utils/skillSettings.js.map +1 -0
  361. package/dist/src/utils/skillUtils.d.ts +16 -0
  362. package/dist/src/utils/skillUtils.js +51 -0
  363. package/dist/src/utils/skillUtils.js.map +1 -0
  364. package/dist/src/utils/userStartupWarnings.d.ts +2 -1
  365. package/dist/src/utils/userStartupWarnings.js +17 -7
  366. package/dist/src/utils/userStartupWarnings.js.map +1 -1
  367. package/dist/src/utils/userStartupWarnings.test.js +37 -6
  368. package/dist/src/utils/userStartupWarnings.test.js.map +1 -1
  369. package/dist/src/zed-integration/zedIntegration.js +1 -1
  370. package/dist/src/zed-integration/zedIntegration.js.map +1 -1
  371. package/dist/src/zed-integration/zedIntegration.test.js +12 -0
  372. package/dist/src/zed-integration/zedIntegration.test.js.map +1 -1
  373. package/dist/tsconfig.tsbuildinfo +1 -1
  374. package/package.json +4 -4
  375. package/dist/google-gemini-cli-0.24.0-nightly.20251227.37be16243.tgz +0 -0
  376. package/dist/src/commands/extensions/examples/mcp-server/example.js.map +0 -1
  377. package/dist/src/commands/extensions/examples/mcp-server/example.test.js +0 -111
  378. package/dist/src/commands/extensions/examples/mcp-server/example.test.js.map +0 -1
  379. package/dist/src/commands/extensions/examples/mcp-server/example.test.ts +0 -135
  380. package/dist/src/commands/extensions/examples/mcp-server/example.ts +0 -60
  381. package/dist/src/commands/extensions/examples/mcp-server/tsconfig.json +0 -13
  382. package/dist/src/commands/extensions/settings.js +0 -111
  383. package/dist/src/commands/extensions/settings.js.map +0 -1
  384. package/dist/src/ui/hooks/useBracketedPaste.d.ts +0 -12
  385. package/dist/src/ui/hooks/useBracketedPaste.js +0 -31
  386. package/dist/src/ui/hooks/useBracketedPaste.js.map +0 -1
  387. package/dist/src/ui/utils/bracketedPaste.d.ts +0 -7
  388. package/dist/src/ui/utils/bracketedPaste.js +0 -15
  389. package/dist/src/ui/utils/bracketedPaste.js.map +0 -1
  390. /package/dist/src/{commands/extensions/examples/mcp-server/example.d.ts → config/extension-manager-scope.test.d.ts} +0 -0
  391. /package/dist/src/{commands/extensions/examples/mcp-server/example.test.d.ts → config/extension-manager-skills.test.d.ts} +0 -0
@@ -0,0 +1,144 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7
+ import * as fs from 'node:fs';
8
+ import * as path from 'node:path';
9
+ import * as os from 'node:os';
10
+ import { ExtensionManager } from './extension-manager.js';
11
+ let currentTempHome = '';
12
+ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
13
+ const actual = await importOriginal();
14
+ return {
15
+ ...actual,
16
+ homedir: () => currentTempHome,
17
+ debugLogger: {
18
+ log: vi.fn(),
19
+ error: vi.fn(),
20
+ warn: vi.fn(),
21
+ },
22
+ };
23
+ });
24
+ describe('ExtensionManager Settings Scope', () => {
25
+ const extensionName = 'test-extension';
26
+ let tempWorkspace;
27
+ let extensionsDir;
28
+ let extensionDir;
29
+ beforeEach(async () => {
30
+ currentTempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-cli-test-home-'));
31
+ tempWorkspace = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-cli-test-workspace-'));
32
+ extensionsDir = path.join(currentTempHome, '.gemini', 'extensions');
33
+ extensionDir = path.join(extensionsDir, extensionName);
34
+ fs.mkdirSync(extensionDir, { recursive: true });
35
+ // Create gemini-extension.json
36
+ const extensionConfig = {
37
+ name: extensionName,
38
+ version: '1.0.0',
39
+ settings: [
40
+ {
41
+ name: 'Test Setting',
42
+ envVar: 'TEST_SETTING',
43
+ description: 'A test setting',
44
+ },
45
+ ],
46
+ };
47
+ fs.writeFileSync(path.join(extensionDir, 'gemini-extension.json'), JSON.stringify(extensionConfig));
48
+ // Create install metadata
49
+ const installMetadata = {
50
+ source: extensionDir,
51
+ type: 'local',
52
+ };
53
+ fs.writeFileSync(path.join(extensionDir, 'install-metadata.json'), JSON.stringify(installMetadata));
54
+ });
55
+ afterEach(() => {
56
+ // Clean up files if needed, or rely on temp dir cleanup
57
+ vi.clearAllMocks();
58
+ });
59
+ it('should prioritize workspace settings over user settings and report correct scope', async () => {
60
+ // 1. Set User Setting
61
+ const userSettingsPath = path.join(extensionDir, '.env');
62
+ fs.writeFileSync(userSettingsPath, 'TEST_SETTING=user-value');
63
+ // 2. Set Workspace Setting
64
+ const workspaceSettingsPath = path.join(tempWorkspace, '.env');
65
+ fs.writeFileSync(workspaceSettingsPath, 'TEST_SETTING=workspace-value');
66
+ const extensionManager = new ExtensionManager({
67
+ workspaceDir: tempWorkspace,
68
+ requestConsent: async () => true,
69
+ requestSetting: async () => '',
70
+ settings: {
71
+ telemetry: {
72
+ enabled: false,
73
+ },
74
+ },
75
+ });
76
+ const extensions = await extensionManager.loadExtensions();
77
+ const extension = extensions.find((e) => e.name === extensionName);
78
+ expect(extension).toBeDefined();
79
+ // Verify resolved settings
80
+ const setting = extension?.resolvedSettings?.find((s) => s.envVar === 'TEST_SETTING');
81
+ expect(setting).toBeDefined();
82
+ expect(setting?.value).toBe('workspace-value');
83
+ expect(setting?.scope).toBe('workspace');
84
+ expect(setting?.source).toBe(workspaceSettingsPath);
85
+ // Verify output string contains (Workspace - <path>)
86
+ const output = extensionManager.toOutputString(extension);
87
+ expect(output).toContain(`Test Setting: workspace-value (Workspace - ${workspaceSettingsPath})`);
88
+ });
89
+ it('should fallback to user settings if workspace setting is missing', async () => {
90
+ // 1. Set User Setting
91
+ const userSettingsPath = path.join(extensionDir, '.env');
92
+ fs.writeFileSync(userSettingsPath, 'TEST_SETTING=user-value');
93
+ // 2. No Workspace Setting
94
+ const extensionManager = new ExtensionManager({
95
+ workspaceDir: tempWorkspace,
96
+ requestConsent: async () => true,
97
+ requestSetting: async () => '',
98
+ settings: {
99
+ telemetry: {
100
+ enabled: false,
101
+ },
102
+ },
103
+ });
104
+ const extensions = await extensionManager.loadExtensions();
105
+ const extension = extensions.find((e) => e.name === extensionName);
106
+ expect(extension).toBeDefined();
107
+ // Verify resolved settings
108
+ const setting = extension?.resolvedSettings?.find((s) => s.envVar === 'TEST_SETTING');
109
+ expect(setting).toBeDefined();
110
+ expect(setting?.value).toBe('user-value');
111
+ expect(setting?.scope).toBe('user');
112
+ expect(setting?.source?.endsWith(path.join(extensionName, '.env'))).toBe(true);
113
+ // Verify output string contains (User - <path>)
114
+ const output = extensionManager.toOutputString(extension);
115
+ expect(output).toContain(`Test Setting: user-value (User - ${userSettingsPath})`);
116
+ });
117
+ it('should report unset if neither is present', async () => {
118
+ // No settings files
119
+ const extensionManager = new ExtensionManager({
120
+ workspaceDir: tempWorkspace,
121
+ requestConsent: async () => true,
122
+ requestSetting: async () => '',
123
+ settings: {
124
+ telemetry: {
125
+ enabled: false,
126
+ },
127
+ },
128
+ });
129
+ const extensions = await extensionManager.loadExtensions();
130
+ const extension = extensions.find((e) => e.name === extensionName);
131
+ expect(extension).toBeDefined();
132
+ // Verify resolved settings
133
+ const setting = extension?.resolvedSettings?.find((s) => s.envVar === 'TEST_SETTING');
134
+ expect(setting).toBeDefined();
135
+ expect(setting?.value).toBe('[not set]');
136
+ expect(setting?.scope).toBeUndefined();
137
+ // Verify output string does not contain scope
138
+ const output = extensionManager.toOutputString(extension);
139
+ expect(output).toContain('Test Setting: [not set]');
140
+ expect(output).not.toContain('Test Setting: [not set] (User)');
141
+ expect(output).not.toContain('Test Setting: [not set] (Workspace)');
142
+ });
143
+ });
144
+ //# sourceMappingURL=extension-manager-scope.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension-manager-scope.test.js","sourceRoot":"","sources":["../../../src/config/extension-manager-scope.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7
+ import * as fs from 'node:fs';
8
+ import * as path from 'node:path';
9
+ import * as os from 'node:os';
10
+ import { ExtensionManager } from './extension-manager.js';
11
+ import { debugLogger, coreEvents } from '@google/gemini-cli-core';
12
+ import {} from './settings.js';
13
+ import { createExtension } from '../test-utils/createExtension.js';
14
+ import { EXTENSIONS_DIRECTORY_NAME } from './extensions/variables.js';
15
+ const mockHomedir = vi.hoisted(() => vi.fn(() => '/tmp/mock-home'));
16
+ vi.mock('node:os', async (importOriginal) => {
17
+ const actual = await importOriginal();
18
+ return {
19
+ ...actual,
20
+ homedir: mockHomedir,
21
+ };
22
+ });
23
+ // Mock @google/gemini-cli-core
24
+ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
25
+ const actual = await importOriginal();
26
+ return {
27
+ ...actual,
28
+ homedir: mockHomedir,
29
+ };
30
+ });
31
+ describe('ExtensionManager skills validation', () => {
32
+ let extensionManager;
33
+ let tempDir;
34
+ let extensionsDir;
35
+ beforeEach(() => {
36
+ vi.clearAllMocks();
37
+ vi.spyOn(coreEvents, 'emitFeedback');
38
+ vi.spyOn(debugLogger, 'debug').mockImplementation(() => { });
39
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-test-'));
40
+ mockHomedir.mockReturnValue(tempDir);
41
+ // Create the extensions directory that ExtensionManager expects
42
+ extensionsDir = path.join(tempDir, '.gemini', EXTENSIONS_DIRECTORY_NAME);
43
+ fs.mkdirSync(extensionsDir, { recursive: true });
44
+ extensionManager = new ExtensionManager({
45
+ settings: {
46
+ telemetry: { enabled: false },
47
+ trustedFolders: [tempDir],
48
+ },
49
+ requestConsent: vi.fn().mockResolvedValue(true),
50
+ requestSetting: vi.fn(),
51
+ workspaceDir: tempDir,
52
+ });
53
+ });
54
+ afterEach(() => {
55
+ try {
56
+ fs.rmSync(tempDir, { recursive: true, force: true });
57
+ }
58
+ catch {
59
+ // ignore
60
+ }
61
+ });
62
+ it('should emit a warning during install if skills directory is not empty but no skills are loaded', async () => {
63
+ // Create a source extension
64
+ const sourceDir = path.join(tempDir, 'source-ext');
65
+ createExtension({
66
+ extensionsDir: sourceDir, // createExtension appends name
67
+ name: 'skills-ext',
68
+ version: '1.0.0',
69
+ installMetadata: {
70
+ type: 'local',
71
+ source: path.join(sourceDir, 'skills-ext'),
72
+ },
73
+ });
74
+ const extensionPath = path.join(sourceDir, 'skills-ext');
75
+ // Add invalid skills content
76
+ const skillsDir = path.join(extensionPath, 'skills');
77
+ fs.mkdirSync(skillsDir);
78
+ fs.writeFileSync(path.join(skillsDir, 'not-a-skill.txt'), 'hello');
79
+ await extensionManager.loadExtensions();
80
+ await extensionManager.installOrUpdateExtension({
81
+ type: 'local',
82
+ source: extensionPath,
83
+ });
84
+ expect(debugLogger.debug).toHaveBeenCalledWith(expect.stringContaining('Failed to load skills from'));
85
+ });
86
+ it('should emit a warning during load if skills directory is not empty but no skills are loaded', async () => {
87
+ // 1. Create a source extension
88
+ const sourceDir = path.join(tempDir, 'source-ext-load');
89
+ createExtension({
90
+ extensionsDir: sourceDir,
91
+ name: 'skills-ext-load',
92
+ version: '1.0.0',
93
+ });
94
+ const sourceExtPath = path.join(sourceDir, 'skills-ext-load');
95
+ // Add invalid skills content
96
+ const skillsDir = path.join(sourceExtPath, 'skills');
97
+ fs.mkdirSync(skillsDir);
98
+ fs.writeFileSync(path.join(skillsDir, 'not-a-skill.txt'), 'hello');
99
+ // 2. Install it to ensure correct disk state
100
+ await extensionManager.loadExtensions();
101
+ await extensionManager.installOrUpdateExtension({
102
+ type: 'local',
103
+ source: sourceExtPath,
104
+ });
105
+ // Clear the spy
106
+ vi.mocked(debugLogger.debug).mockClear();
107
+ // 3. Create a fresh ExtensionManager to force loading from disk
108
+ const newExtensionManager = new ExtensionManager({
109
+ settings: {
110
+ telemetry: { enabled: false },
111
+ trustedFolders: [tempDir],
112
+ },
113
+ requestConsent: vi.fn().mockResolvedValue(true),
114
+ requestSetting: vi.fn(),
115
+ workspaceDir: tempDir,
116
+ });
117
+ // 4. Load extensions
118
+ await newExtensionManager.loadExtensions();
119
+ expect(debugLogger.debug).toHaveBeenCalledWith(expect.stringContaining('Failed to load skills from'));
120
+ });
121
+ it('should succeed if skills are correctly loaded', async () => {
122
+ const sourceDir = path.join(tempDir, 'source-ext-good');
123
+ createExtension({
124
+ extensionsDir: sourceDir,
125
+ name: 'good-skills-ext',
126
+ version: '1.0.0',
127
+ installMetadata: {
128
+ type: 'local',
129
+ source: path.join(sourceDir, 'good-skills-ext'),
130
+ },
131
+ });
132
+ const extensionPath = path.join(sourceDir, 'good-skills-ext');
133
+ const skillsDir = path.join(extensionPath, 'skills');
134
+ const skillSubdir = path.join(skillsDir, 'test-skill');
135
+ fs.mkdirSync(skillSubdir, { recursive: true });
136
+ fs.writeFileSync(path.join(skillSubdir, 'SKILL.md'), '---\nname: test-skill\ndescription: test desc\n---\nbody');
137
+ await extensionManager.loadExtensions();
138
+ const extension = await extensionManager.installOrUpdateExtension({
139
+ type: 'local',
140
+ source: extensionPath,
141
+ });
142
+ expect(extension.name).toBe('good-skills-ext');
143
+ expect(debugLogger.debug).not.toHaveBeenCalledWith(expect.stringContaining('Failed to load skills from'));
144
+ });
145
+ });
146
+ //# sourceMappingURL=extension-manager-skills.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension-manager-skills.test.js","sourceRoot":"","sources":["../../../src/config/extension-manager-skills.test.ts"],"names":[],"mappings":"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"}
@@ -5,7 +5,6 @@
5
5
  */
6
6
  import * as fs from 'node:fs';
7
7
  import * as path from 'node:path';
8
- import * as os from 'node:os';
9
8
  import { stat } from 'node:fs/promises';
10
9
  import chalk from 'chalk';
11
10
  import { ExtensionEnablementManager } from './extensions/extensionEnablement.js';
@@ -14,12 +13,13 @@ import { createHash, randomUUID } from 'node:crypto';
14
13
  import { loadInstallMetadata } from './extension.js';
15
14
  import { isWorkspaceTrusted, loadTrustedFolders, TrustLevel, } from './trustedFolders.js';
16
15
  import { cloneFromGit, downloadFromGitHubRelease, tryParseGithubUrl, } from './extensions/github.js';
17
- import { Config, debugLogger, ExtensionDisableEvent, ExtensionEnableEvent, ExtensionInstallEvent, ExtensionLoader, ExtensionUninstallEvent, ExtensionUpdateEvent, getErrorMessage, logExtensionDisable, logExtensionEnable, logExtensionInstallEvent, logExtensionUninstall, logExtensionUpdateEvent, } from '@google/gemini-cli-core';
16
+ import { Config, debugLogger, ExtensionDisableEvent, ExtensionEnableEvent, ExtensionInstallEvent, ExtensionLoader, ExtensionUninstallEvent, ExtensionUpdateEvent, getErrorMessage, logExtensionDisable, logExtensionEnable, logExtensionInstallEvent, logExtensionUninstall, logExtensionUpdateEvent, loadSkillsFromDir, homedir, coreEvents, } from '@google/gemini-cli-core';
18
17
  import { maybeRequestConsentOrFail } from './extensions/consent.js';
19
18
  import { resolveEnvVarsInObject } from '../utils/envVarResolver.js';
20
19
  import { ExtensionStorage } from './extensions/storage.js';
21
20
  import { EXTENSIONS_CONFIG_FILENAME, INSTALL_METADATA_FILENAME, recursivelyHydrateStrings, } from './extensions/variables.js';
22
- import { getEnvContents, maybePromptForSettings, } from './extensions/extensionSettings.js';
21
+ import { getEnvContents, getEnvFilePath, maybePromptForSettings, getMissingSettings, getScopedEnvContents, ExtensionSettingScope, } from './extensions/extensionSettings.js';
22
+ import { getEnableHooks } from './settingsSchema.js';
23
23
  /**
24
24
  * Actual implementation of an ExtensionLoader.
25
25
  *
@@ -130,15 +130,6 @@ Would you like to attempt to install via "git clone" instead?`))) {
130
130
  }
131
131
  try {
132
132
  newExtensionConfig = await this.loadExtensionConfig(localSourcePath);
133
- if (isUpdate && installMetadata.autoUpdate) {
134
- const oldSettings = new Set(previousExtensionConfig.settings?.map((s) => s.name) || []);
135
- const newSettings = new Set(newExtensionConfig.settings?.map((s) => s.name) || []);
136
- const settingsAreEqual = oldSettings.size === newSettings.size &&
137
- [...oldSettings].every((value) => newSettings.has(value));
138
- if (!settingsAreEqual && installMetadata.autoUpdate) {
139
- throw new Error(`Extension "${newExtensionConfig.name}" has settings changes and cannot be auto-updated. Please update manually.`);
140
- }
141
- }
142
133
  const newExtensionName = newExtensionConfig.name;
143
134
  const previous = this.getExtensions().find((installed) => installed.name === newExtensionName);
144
135
  if (isUpdate && !previous) {
@@ -148,16 +139,18 @@ Would you like to attempt to install via "git clone" instead?`))) {
148
139
  throw new Error(`Extension "${newExtensionName}" is already installed. Please uninstall it first.`);
149
140
  }
150
141
  const newHasHooks = fs.existsSync(path.join(localSourcePath, 'hooks', 'hooks.json'));
151
- let previousHasHooks = false;
152
- if (isUpdate && previous && previous.hooks) {
153
- previousHasHooks = Object.keys(previous.hooks).length > 0;
154
- }
155
- await maybeRequestConsentOrFail(newExtensionConfig, this.requestConsent, newHasHooks, previousExtensionConfig, previousHasHooks);
142
+ const previousHasHooks = !!(isUpdate &&
143
+ previous &&
144
+ previous.hooks &&
145
+ Object.keys(previous.hooks).length > 0);
146
+ const newSkills = await loadSkillsFromDir(path.join(localSourcePath, 'skills'));
147
+ const previousSkills = previous?.skills ?? [];
148
+ await maybeRequestConsentOrFail(newExtensionConfig, this.requestConsent, newHasHooks, previousExtensionConfig, previousHasHooks, newSkills, previousSkills);
156
149
  const extensionId = getExtensionId(newExtensionConfig, installMetadata);
157
150
  const destinationPath = new ExtensionStorage(newExtensionName).getExtensionDir();
158
151
  let previousSettings;
159
152
  if (isUpdate) {
160
- previousSettings = await getEnvContents(previousExtensionConfig, extensionId);
153
+ previousSettings = await getEnvContents(previousExtensionConfig, extensionId, this.workspaceDir);
161
154
  await this.uninstallExtension(newExtensionName, isUpdate);
162
155
  }
163
156
  await fs.promises.mkdir(destinationPath, { recursive: true });
@@ -169,6 +162,14 @@ Would you like to attempt to install via "git clone" instead?`))) {
169
162
  await maybePromptForSettings(newExtensionConfig, extensionId, this.requestSetting);
170
163
  }
171
164
  }
165
+ const missingSettings = await getMissingSettings(newExtensionConfig, extensionId, this.workspaceDir);
166
+ if (missingSettings.length > 0) {
167
+ const message = `Extension "${newExtensionConfig.name}" has missing settings: ${missingSettings
168
+ .map((s) => s.name)
169
+ .join(', ')}. Please run "gemini extensions config ${newExtensionConfig.name} [setting-name]" to configure them.`;
170
+ debugLogger.warn(message);
171
+ coreEvents.emitFeedback('warning', message);
172
+ }
172
173
  if (installMetadata.type === 'local' ||
173
174
  installMetadata.type === 'git' ||
174
175
  installMetadata.type === 'github-release') {
@@ -252,6 +253,10 @@ Would you like to attempt to install via "git clone" instead?`))) {
252
253
  if (this.loadedExtensions) {
253
254
  throw new Error('Extensions already loaded, only load extensions once.');
254
255
  }
256
+ if (this.settings.admin?.extensions?.enabled === false) {
257
+ this.loadedExtensions = [];
258
+ return this.loadedExtensions;
259
+ }
255
260
  const extensionsDir = ExtensionStorage.getUserExtensionsDir();
256
261
  this.loadedExtensions = [];
257
262
  if (!fs.existsSync(extensionsDir)) {
@@ -286,12 +291,36 @@ Would you like to attempt to install via "git clone" instead?`))) {
286
291
  if (this.getExtensions().find((extension) => extension.name === config.name)) {
287
292
  throw new Error(`Extension with name ${config.name} already was loaded.`);
288
293
  }
289
- const customEnv = await getEnvContents(config, getExtensionId(config, installMetadata));
294
+ const extensionId = getExtensionId(config, installMetadata);
295
+ const userSettings = await getScopedEnvContents(config, extensionId, ExtensionSettingScope.USER);
296
+ const workspaceSettings = await getScopedEnvContents(config, extensionId, ExtensionSettingScope.WORKSPACE, this.workspaceDir);
297
+ const customEnv = { ...userSettings, ...workspaceSettings };
290
298
  config = resolveEnvVarsInObject(config, customEnv);
291
299
  const resolvedSettings = [];
292
300
  if (config.settings) {
293
301
  for (const setting of config.settings) {
294
302
  const value = customEnv[setting.envVar];
303
+ let scope;
304
+ let source;
305
+ // Note: strict check for undefined, as empty string is a valid value
306
+ if (workspaceSettings[setting.envVar] !== undefined) {
307
+ scope = 'workspace';
308
+ if (setting.sensitive) {
309
+ source = 'Keychain';
310
+ }
311
+ else {
312
+ source = getEnvFilePath(config.name, ExtensionSettingScope.WORKSPACE, this.workspaceDir);
313
+ }
314
+ }
315
+ else if (userSettings[setting.envVar] !== undefined) {
316
+ scope = 'user';
317
+ if (setting.sensitive) {
318
+ source = 'Keychain';
319
+ }
320
+ else {
321
+ source = getEnvFilePath(config.name, ExtensionSettingScope.USER);
322
+ }
323
+ }
295
324
  resolvedSettings.push({
296
325
  name: setting.name,
297
326
  envVar: setting.envVar,
@@ -301,25 +330,33 @@ Would you like to attempt to install via "git clone" instead?`))) {
301
330
  ? '***'
302
331
  : value,
303
332
  sensitive: setting.sensitive ?? false,
333
+ scope,
334
+ source,
304
335
  });
305
336
  }
306
337
  }
307
338
  if (config.mcpServers) {
308
- config.mcpServers = Object.fromEntries(Object.entries(config.mcpServers).map(([key, value]) => [
309
- key,
310
- filterMcpConfig(value),
311
- ]));
339
+ if (this.settings.admin?.mcp?.enabled === false) {
340
+ config.mcpServers = undefined;
341
+ }
342
+ else {
343
+ config.mcpServers = Object.fromEntries(Object.entries(config.mcpServers).map(([key, value]) => [
344
+ key,
345
+ filterMcpConfig(value),
346
+ ]));
347
+ }
312
348
  }
313
349
  const contextFiles = getContextFileNames(config)
314
350
  .map((contextFileName) => path.join(effectiveExtensionPath, contextFileName))
315
351
  .filter((contextFilePath) => fs.existsSync(contextFilePath));
316
352
  let hooks;
317
- if (this.settings.tools?.enableHooks) {
353
+ if (getEnableHooks(this.settings)) {
318
354
  hooks = await this.loadExtensionHooks(effectiveExtensionPath, {
319
355
  extensionPath: effectiveExtensionPath,
320
356
  workspacePath: this.workspaceDir,
321
357
  });
322
358
  }
359
+ const skills = await loadSkillsFromDir(path.join(effectiveExtensionPath, 'skills'));
323
360
  const extension = {
324
361
  name: config.name,
325
362
  version: config.version,
@@ -333,6 +370,7 @@ Would you like to attempt to install via "git clone" instead?`))) {
333
370
  id: getExtensionId(config, installMetadata),
334
371
  settings: config.settings,
335
372
  resolvedSettings,
373
+ skills,
336
374
  };
337
375
  this.loadedExtensions = [...this.loadedExtensions, extension];
338
376
  await this.maybeStartExtension(extension);
@@ -405,7 +443,7 @@ Would you like to attempt to install via "git clone" instead?`))) {
405
443
  }
406
444
  }
407
445
  toOutputString(extension) {
408
- const userEnabled = this.extensionEnablementManager.isEnabled(extension.name, os.homedir());
446
+ const userEnabled = this.extensionEnablementManager.isEnabled(extension.name, homedir());
409
447
  const workspaceEnabled = this.extensionEnablementManager.isEnabled(extension.name, this.workspaceDir);
410
448
  const status = workspaceEnabled ? chalk.green('✓') : chalk.red('✗');
411
449
  let output = `${status} ${extension.name} (${extension.version})`;
@@ -441,11 +479,25 @@ Would you like to attempt to install via "git clone" instead?`))) {
441
479
  output += `\n ${tool}`;
442
480
  });
443
481
  }
482
+ if (extension.skills && extension.skills.length > 0) {
483
+ output += `\n Agent skills:`;
484
+ extension.skills.forEach((skill) => {
485
+ output += `\n ${skill.name}: ${skill.description}`;
486
+ });
487
+ }
444
488
  const resolvedSettings = extension.resolvedSettings;
445
489
  if (resolvedSettings && resolvedSettings.length > 0) {
446
490
  output += `\n Settings:`;
447
491
  resolvedSettings.forEach((setting) => {
448
- output += `\n ${setting.name}: ${setting.value}`;
492
+ let scope = '';
493
+ if (setting.scope) {
494
+ scope = setting.scope === 'workspace' ? '(Workspace' : '(User';
495
+ if (setting.source) {
496
+ scope += ` - ${setting.source}`;
497
+ }
498
+ scope += ')';
499
+ }
500
+ output += `\n ${setting.name}: ${setting.value} ${scope}`;
449
501
  });
450
502
  }
451
503
  return output;
@@ -460,7 +512,7 @@ Would you like to attempt to install via "git clone" instead?`))) {
460
512
  throw new Error(`Extension with name ${name} does not exist.`);
461
513
  }
462
514
  if (scope !== SettingScope.Session) {
463
- const scopePath = scope === SettingScope.Workspace ? this.workspaceDir : os.homedir();
515
+ const scopePath = scope === SettingScope.Workspace ? this.workspaceDir : homedir();
464
516
  this.extensionEnablementManager.disable(name, true, scopePath);
465
517
  }
466
518
  await logExtensionDisable(this.telemetryConfig, new ExtensionDisableEvent(name, hashValue(name), extension.id, scope));
@@ -485,7 +537,7 @@ Would you like to attempt to install via "git clone" instead?`))) {
485
537
  throw new Error(`Extension with name ${name} does not exist.`);
486
538
  }
487
539
  if (scope !== SettingScope.Session) {
488
- const scopePath = scope === SettingScope.Workspace ? this.workspaceDir : os.homedir();
540
+ const scopePath = scope === SettingScope.Workspace ? this.workspaceDir : homedir();
489
541
  this.extensionEnablementManager.enable(name, true, scopePath);
490
542
  }
491
543
  await logExtensionEnable(this.telemetryConfig, new ExtensionEnableEvent(name, hashValue(name), extension.id, scope));