@google/gemini-cli-core 0.26.0-preview.4 → 0.27.0-nightly.20260126.cb772a5b7

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 (325) hide show
  1. package/README.md +18 -1
  2. package/dist/docs/changelogs/index.md +87 -0
  3. package/dist/docs/changelogs/latest.md +355 -150
  4. package/dist/docs/changelogs/preview.md +318 -210
  5. package/dist/docs/cli/commands.md +0 -6
  6. package/dist/docs/cli/keyboard-shortcuts.md +32 -31
  7. package/dist/docs/cli/settings.md +16 -18
  8. package/dist/docs/cli/system-prompt.md +32 -0
  9. package/dist/docs/cli/uninstall.md +18 -0
  10. package/dist/docs/extensions/index.md +1 -1
  11. package/dist/docs/get-started/configuration.md +9 -34
  12. package/dist/docs/hooks/best-practices.md +219 -398
  13. package/dist/docs/hooks/index.md +111 -656
  14. package/dist/docs/hooks/reference.md +280 -136
  15. package/dist/docs/hooks/writing-hooks.md +202 -796
  16. package/dist/docs/sidebar.json +1 -5
  17. package/dist/docs/tools/mcp-server.md +24 -1
  18. package/dist/google-gemini-cli-core-0.27.0-nightly.20260121.97aac696f.tgz +0 -0
  19. package/dist/src/agents/agentLoader.js +10 -10
  20. package/dist/src/agents/agentLoader.js.map +1 -1
  21. package/dist/src/agents/agentLoader.test.js +16 -19
  22. package/dist/src/agents/agentLoader.test.js.map +1 -1
  23. package/dist/src/agents/cli-help-agent.js +8 -5
  24. package/dist/src/agents/cli-help-agent.js.map +1 -1
  25. package/dist/src/agents/cli-help-agent.test.js +4 -2
  26. package/dist/src/agents/cli-help-agent.test.js.map +1 -1
  27. package/dist/src/agents/codebase-investigator.d.ts +2 -1
  28. package/dist/src/agents/codebase-investigator.js +62 -41
  29. package/dist/src/agents/codebase-investigator.js.map +1 -1
  30. package/dist/src/agents/codebase-investigator.test.js +15 -8
  31. package/dist/src/agents/codebase-investigator.test.js.map +1 -1
  32. package/dist/src/agents/generalist-agent.js +9 -11
  33. package/dist/src/agents/generalist-agent.js.map +1 -1
  34. package/dist/src/agents/generalist-agent.test.js +3 -3
  35. package/dist/src/agents/generalist-agent.test.js.map +1 -1
  36. package/dist/src/agents/local-executor.js +25 -9
  37. package/dist/src/agents/local-executor.js.map +1 -1
  38. package/dist/src/agents/local-executor.test.js +74 -7
  39. package/dist/src/agents/local-executor.test.js.map +1 -1
  40. package/dist/src/agents/local-invocation.test.js +7 -3
  41. package/dist/src/agents/local-invocation.test.js.map +1 -1
  42. package/dist/src/agents/registry.d.ts +8 -5
  43. package/dist/src/agents/registry.js +31 -75
  44. package/dist/src/agents/registry.js.map +1 -1
  45. package/dist/src/agents/registry.test.js +93 -30
  46. package/dist/src/agents/registry.test.js.map +1 -1
  47. package/dist/src/agents/remote-invocation.js +2 -1
  48. package/dist/src/agents/remote-invocation.js.map +1 -1
  49. package/dist/src/agents/remote-invocation.test.js +15 -3
  50. package/dist/src/agents/remote-invocation.test.js.map +1 -1
  51. package/dist/src/agents/subagent-tool-wrapper.js +1 -3
  52. package/dist/src/agents/subagent-tool-wrapper.js.map +1 -1
  53. package/dist/src/agents/subagent-tool-wrapper.test.js +10 -25
  54. package/dist/src/agents/subagent-tool-wrapper.test.js.map +1 -1
  55. package/dist/src/agents/subagent-tool.d.ts +15 -0
  56. package/dist/src/agents/subagent-tool.js +61 -0
  57. package/dist/src/agents/subagent-tool.js.map +1 -0
  58. package/dist/src/agents/types.d.ts +6 -9
  59. package/dist/src/agents/types.js +4 -0
  60. package/dist/src/agents/types.js.map +1 -1
  61. package/dist/src/code_assist/codeAssist.js +1 -1
  62. package/dist/src/code_assist/codeAssist.js.map +1 -1
  63. package/dist/src/code_assist/codeAssist.test.js +2 -2
  64. package/dist/src/code_assist/codeAssist.test.js.map +1 -1
  65. package/dist/src/code_assist/experiments/experiments.test.js +1 -0
  66. package/dist/src/code_assist/experiments/experiments.test.js.map +1 -1
  67. package/dist/src/code_assist/experiments/experiments_local.test.js +11 -6
  68. package/dist/src/code_assist/experiments/experiments_local.test.js.map +1 -1
  69. package/dist/src/code_assist/experiments/flagNames.d.ts +2 -0
  70. package/dist/src/code_assist/experiments/flagNames.js +2 -0
  71. package/dist/src/code_assist/experiments/flagNames.js.map +1 -1
  72. package/dist/src/code_assist/oauth2.js +3 -3
  73. package/dist/src/code_assist/oauth2.test.js +1 -1
  74. package/dist/src/code_assist/server.d.ts +2 -1
  75. package/dist/src/code_assist/server.js +3 -1
  76. package/dist/src/code_assist/server.js.map +1 -1
  77. package/dist/src/code_assist/setup.d.ts +1 -0
  78. package/dist/src/code_assist/setup.js +5 -1
  79. package/dist/src/code_assist/setup.js.map +1 -1
  80. package/dist/src/code_assist/setup.test.js +10 -4
  81. package/dist/src/code_assist/setup.test.js.map +1 -1
  82. package/dist/src/config/config.d.ts +25 -18
  83. package/dist/src/config/config.js +70 -40
  84. package/dist/src/config/config.js.map +1 -1
  85. package/dist/src/config/config.test.js +48 -21
  86. package/dist/src/config/config.test.js.map +1 -1
  87. package/dist/src/confirmation-bus/message-bus.js +2 -32
  88. package/dist/src/confirmation-bus/message-bus.js.map +1 -1
  89. package/dist/src/confirmation-bus/types.d.ts +35 -21
  90. package/dist/src/confirmation-bus/types.js +8 -3
  91. package/dist/src/confirmation-bus/types.js.map +1 -1
  92. package/dist/src/core/client.js +25 -7
  93. package/dist/src/core/client.js.map +1 -1
  94. package/dist/src/core/client.test.js +52 -6
  95. package/dist/src/core/client.test.js.map +1 -1
  96. package/dist/src/core/contentGenerator.d.ts +1 -0
  97. package/dist/src/core/contentGenerator.js.map +1 -1
  98. package/dist/src/core/coreToolHookTriggers.d.ts +2 -38
  99. package/dist/src/core/coreToolHookTriggers.js +12 -159
  100. package/dist/src/core/coreToolHookTriggers.js.map +1 -1
  101. package/dist/src/core/coreToolHookTriggers.test.js +62 -94
  102. package/dist/src/core/coreToolHookTriggers.test.js.map +1 -1
  103. package/dist/src/core/coreToolScheduler.js +3 -5
  104. package/dist/src/core/coreToolScheduler.js.map +1 -1
  105. package/dist/src/core/coreToolScheduler.test.js +3 -0
  106. package/dist/src/core/coreToolScheduler.test.js.map +1 -1
  107. package/dist/src/core/fakeContentGenerator.d.ts +1 -0
  108. package/dist/src/core/fakeContentGenerator.js +1 -0
  109. package/dist/src/core/fakeContentGenerator.js.map +1 -1
  110. package/dist/src/core/geminiChat.js +1 -0
  111. package/dist/src/core/geminiChat.js.map +1 -1
  112. package/dist/src/core/geminiChat.test.js +21 -0
  113. package/dist/src/core/geminiChat.test.js.map +1 -1
  114. package/dist/src/core/geminiChat_network_retry.test.js +17 -12
  115. package/dist/src/core/geminiChat_network_retry.test.js.map +1 -1
  116. package/dist/src/core/loggingContentGenerator.d.ts +3 -0
  117. package/dist/src/core/loggingContentGenerator.js +6 -0
  118. package/dist/src/core/loggingContentGenerator.js.map +1 -1
  119. package/dist/src/core/loggingContentGenerator.test.js +11 -0
  120. package/dist/src/core/loggingContentGenerator.test.js.map +1 -1
  121. package/dist/src/core/prompts-substitution.test.js +101 -0
  122. package/dist/src/core/prompts-substitution.test.js.map +1 -0
  123. package/dist/src/core/prompts.js +115 -31
  124. package/dist/src/core/prompts.js.map +1 -1
  125. package/dist/src/core/prompts.test.js +32 -0
  126. package/dist/src/core/prompts.test.js.map +1 -1
  127. package/dist/src/core/recordingContentGenerator.d.ts +2 -1
  128. package/dist/src/core/recordingContentGenerator.js +6 -1
  129. package/dist/src/core/recordingContentGenerator.js.map +1 -1
  130. package/dist/src/core/turn.d.ts +2 -0
  131. package/dist/src/core/turn.js.map +1 -1
  132. package/dist/src/generated/git-commit.d.ts +2 -2
  133. package/dist/src/generated/git-commit.js +2 -2
  134. package/dist/src/generated/git-commit.js.map +1 -1
  135. package/dist/src/hooks/hookAggregator.js +13 -3
  136. package/dist/src/hooks/hookAggregator.js.map +1 -1
  137. package/dist/src/hooks/hookEventHandler.d.ts +1 -13
  138. package/dist/src/hooks/hookEventHandler.js +1 -289
  139. package/dist/src/hooks/hookEventHandler.js.map +1 -1
  140. package/dist/src/hooks/hookEventHandler.test.js +1 -4
  141. package/dist/src/hooks/hookEventHandler.test.js.map +1 -1
  142. package/dist/src/hooks/hookSystem.d.ts +47 -3
  143. package/dist/src/hooks/hookSystem.js +90 -6
  144. package/dist/src/hooks/hookSystem.js.map +1 -1
  145. package/dist/src/hooks/hookSystem.test.js +19 -8
  146. package/dist/src/hooks/hookSystem.test.js.map +1 -1
  147. package/dist/src/hooks/types.d.ts +23 -1
  148. package/dist/src/hooks/types.js +28 -2
  149. package/dist/src/hooks/types.js.map +1 -1
  150. package/dist/src/hooks/types.test.js +9 -1
  151. package/dist/src/hooks/types.test.js.map +1 -1
  152. package/dist/src/ide/detect-ide.d.ts +41 -0
  153. package/dist/src/ide/detect-ide.js +47 -3
  154. package/dist/src/ide/detect-ide.js.map +1 -1
  155. package/dist/src/ide/detect-ide.test.js +46 -0
  156. package/dist/src/ide/detect-ide.test.js.map +1 -1
  157. package/dist/src/ide/ide-client.d.ts +1 -0
  158. package/dist/src/ide/ide-client.js +36 -9
  159. package/dist/src/ide/ide-client.js.map +1 -1
  160. package/dist/src/ide/ide-client.test.js +154 -6
  161. package/dist/src/ide/ide-client.test.js.map +1 -1
  162. package/dist/src/index.d.ts +2 -0
  163. package/dist/src/index.js +2 -0
  164. package/dist/src/index.js.map +1 -1
  165. package/dist/src/mcp/oauth-provider.d.ts +1 -2
  166. package/dist/src/mcp/oauth-provider.js +4 -8
  167. package/dist/src/mcp/oauth-provider.js.map +1 -1
  168. package/dist/src/mcp/oauth-provider.test.js +32 -0
  169. package/dist/src/mcp/oauth-provider.test.js.map +1 -1
  170. package/dist/src/mcp/oauth-utils.d.ts +13 -4
  171. package/dist/src/mcp/oauth-utils.js +23 -19
  172. package/dist/src/mcp/oauth-utils.js.map +1 -1
  173. package/dist/src/mcp/oauth-utils.test.js +13 -8
  174. package/dist/src/mcp/oauth-utils.test.js.map +1 -1
  175. package/dist/src/policy/policy-engine.d.ts +1 -8
  176. package/dist/src/policy/policy-engine.js +1 -75
  177. package/dist/src/policy/policy-engine.js.map +1 -1
  178. package/dist/src/policy/policy-engine.test.js +0 -222
  179. package/dist/src/policy/policy-engine.test.js.map +1 -1
  180. package/dist/src/routing/modelRouterService.js +18 -8
  181. package/dist/src/routing/modelRouterService.js.map +1 -1
  182. package/dist/src/routing/modelRouterService.test.js +14 -6
  183. package/dist/src/routing/modelRouterService.test.js.map +1 -1
  184. package/dist/src/routing/strategies/classifierStrategy.js +4 -7
  185. package/dist/src/routing/strategies/classifierStrategy.js.map +1 -1
  186. package/dist/src/routing/strategies/classifierStrategy.test.js +10 -4
  187. package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -1
  188. package/dist/src/routing/strategies/numericalClassifierStrategy.d.ts +13 -0
  189. package/dist/src/routing/strategies/numericalClassifierStrategy.js +178 -0
  190. package/dist/src/routing/strategies/numericalClassifierStrategy.js.map +1 -0
  191. package/dist/src/routing/strategies/numericalClassifierStrategy.test.js +347 -0
  192. package/dist/src/routing/strategies/numericalClassifierStrategy.test.js.map +1 -0
  193. package/dist/src/scheduler/confirmation.js +2 -3
  194. package/dist/src/scheduler/confirmation.js.map +1 -1
  195. package/dist/src/scheduler/confirmation.test.js +8 -8
  196. package/dist/src/scheduler/confirmation.test.js.map +1 -1
  197. package/dist/src/scheduler/scheduler.test.js +0 -3
  198. package/dist/src/scheduler/scheduler.test.js.map +1 -1
  199. package/dist/src/scheduler/state-manager.js +4 -0
  200. package/dist/src/scheduler/state-manager.js.map +1 -1
  201. package/dist/src/scheduler/state-manager.test.js +11 -0
  202. package/dist/src/scheduler/state-manager.test.js.map +1 -1
  203. package/dist/src/scheduler/tool-executor.js +2 -4
  204. package/dist/src/scheduler/tool-executor.js.map +1 -1
  205. package/dist/src/scheduler/tool-executor.test.js +1 -1
  206. package/dist/src/scheduler/tool-executor.test.js.map +1 -1
  207. package/dist/src/services/chatRecordingService.d.ts +1 -1
  208. package/dist/src/services/chatRecordingService.js +23 -1
  209. package/dist/src/services/chatRecordingService.js.map +1 -1
  210. package/dist/src/services/chatRecordingService.test.js +111 -0
  211. package/dist/src/services/chatRecordingService.test.js.map +1 -1
  212. package/dist/src/services/sessionSummaryService.test.js +1 -1
  213. package/dist/src/services/sessionSummaryService.test.js.map +1 -1
  214. package/dist/src/telemetry/metrics.js +15 -4
  215. package/dist/src/telemetry/metrics.js.map +1 -1
  216. package/dist/src/telemetry/metrics.test.js +7 -0
  217. package/dist/src/telemetry/metrics.test.js.map +1 -1
  218. package/dist/src/telemetry/types.d.ts +3 -1
  219. package/dist/src/telemetry/types.js +19 -4
  220. package/dist/src/telemetry/types.js.map +1 -1
  221. package/dist/src/test-utils/mock-message-bus.d.ts +1 -19
  222. package/dist/src/test-utils/mock-message-bus.js +1 -65
  223. package/dist/src/test-utils/mock-message-bus.js.map +1 -1
  224. package/dist/src/tools/ask-user.d.ts +20 -0
  225. package/dist/src/tools/ask-user.js +142 -0
  226. package/dist/src/tools/ask-user.js.map +1 -0
  227. package/dist/src/{core/geminiChatHookTriggers.test.d.ts → tools/ask-user.test.d.ts} +1 -1
  228. package/dist/src/tools/ask-user.test.js +187 -0
  229. package/dist/src/tools/ask-user.test.js.map +1 -0
  230. package/dist/src/tools/confirmation-policy.test.js +1 -1
  231. package/dist/src/tools/confirmation-policy.test.js.map +1 -1
  232. package/dist/src/tools/diffOptions.d.ts +1 -1
  233. package/dist/src/tools/diffOptions.js +7 -3
  234. package/dist/src/tools/diffOptions.js.map +1 -1
  235. package/dist/src/tools/diffOptions.test.js +17 -0
  236. package/dist/src/tools/diffOptions.test.js.map +1 -1
  237. package/dist/src/tools/edit.js +1 -1
  238. package/dist/src/tools/edit.js.map +1 -1
  239. package/dist/src/tools/edit.test.js +11 -3
  240. package/dist/src/tools/edit.test.js.map +1 -1
  241. package/dist/src/tools/mcp-client-manager.d.ts +13 -4
  242. package/dist/src/tools/mcp-client-manager.js +53 -21
  243. package/dist/src/tools/mcp-client-manager.js.map +1 -1
  244. package/dist/src/tools/mcp-client-manager.test.js +1 -0
  245. package/dist/src/tools/mcp-client-manager.test.js.map +1 -1
  246. package/dist/src/tools/mcp-client.test.js +1 -2
  247. package/dist/src/tools/mcp-client.test.js.map +1 -1
  248. package/dist/src/tools/modifiable-tool.test.js +3 -3
  249. package/dist/src/tools/modifiable-tool.test.js.map +1 -1
  250. package/dist/src/tools/read-many-files.js +3 -2
  251. package/dist/src/tools/read-many-files.js.map +1 -1
  252. package/dist/src/tools/ripGrep.js +7 -2
  253. package/dist/src/tools/ripGrep.js.map +1 -1
  254. package/dist/src/tools/ripGrep.test.js +2 -2
  255. package/dist/src/tools/ripGrep.test.js.map +1 -1
  256. package/dist/src/tools/tool-names.d.ts +8 -2
  257. package/dist/src/tools/tool-names.js +14 -2
  258. package/dist/src/tools/tool-names.js.map +1 -1
  259. package/dist/src/tools/tools.d.ts +1 -0
  260. package/dist/src/tools/tools.js +2 -1
  261. package/dist/src/tools/tools.js.map +1 -1
  262. package/dist/src/tools/write-file.test.js +7 -7
  263. package/dist/src/tools/write-file.test.js.map +1 -1
  264. package/dist/src/utils/constants.d.ts +7 -0
  265. package/dist/src/utils/constants.js +8 -0
  266. package/dist/src/utils/constants.js.map +1 -0
  267. package/dist/src/utils/editCorrector.js +1 -1
  268. package/dist/src/utils/editCorrector.js.map +1 -1
  269. package/dist/src/utils/editor.js +4 -1
  270. package/dist/src/utils/editor.js.map +1 -1
  271. package/dist/src/utils/editor.test.js +6 -4
  272. package/dist/src/utils/editor.test.js.map +1 -1
  273. package/dist/src/utils/events.d.ts +2 -0
  274. package/dist/src/utils/events.js +1 -0
  275. package/dist/src/utils/events.js.map +1 -1
  276. package/dist/src/utils/extensionLoader.js +1 -0
  277. package/dist/src/utils/extensionLoader.js.map +1 -1
  278. package/dist/src/utils/extensionLoader.test.js +11 -0
  279. package/dist/src/utils/extensionLoader.test.js.map +1 -1
  280. package/dist/src/utils/googleQuotaErrors.d.ts +3 -3
  281. package/dist/src/utils/googleQuotaErrors.js +6 -17
  282. package/dist/src/utils/googleQuotaErrors.js.map +1 -1
  283. package/dist/src/utils/googleQuotaErrors.test.js +4 -22
  284. package/dist/src/utils/googleQuotaErrors.test.js.map +1 -1
  285. package/dist/src/utils/llm-edit-fixer.js +4 -7
  286. package/dist/src/utils/llm-edit-fixer.js.map +1 -1
  287. package/dist/src/utils/llm-edit-fixer.test.js +2 -2
  288. package/dist/src/utils/llm-edit-fixer.test.js.map +1 -1
  289. package/dist/src/utils/memoryImportProcessor.js +7 -24
  290. package/dist/src/utils/memoryImportProcessor.js.map +1 -1
  291. package/dist/src/utils/promptIdContext.d.ts +6 -0
  292. package/dist/src/utils/promptIdContext.js +15 -0
  293. package/dist/src/utils/promptIdContext.js.map +1 -1
  294. package/dist/src/utils/schemaValidator.d.ts +6 -0
  295. package/dist/src/utils/schemaValidator.js +12 -1
  296. package/dist/src/utils/schemaValidator.js.map +1 -1
  297. package/dist/src/utils/workspaceContext.d.ts +15 -0
  298. package/dist/src/utils/workspaceContext.js +32 -11
  299. package/dist/src/utils/workspaceContext.js.map +1 -1
  300. package/dist/src/utils/workspaceContext.test.js +55 -0
  301. package/dist/src/utils/workspaceContext.test.js.map +1 -1
  302. package/dist/tsconfig.tsbuildinfo +1 -1
  303. package/package.json +2 -3
  304. package/dist/docs/changelogs/releases.md +0 -1543
  305. package/dist/google-gemini-cli-core-0.26.0-preview.3.tgz +0 -0
  306. package/dist/src/agents/delegate-to-agent-tool.d.ts +0 -19
  307. package/dist/src/agents/delegate-to-agent-tool.js +0 -178
  308. package/dist/src/agents/delegate-to-agent-tool.js.map +0 -1
  309. package/dist/src/agents/delegate-to-agent-tool.test.js +0 -213
  310. package/dist/src/agents/delegate-to-agent-tool.test.js.map +0 -1
  311. package/dist/src/agents/schema-utils.d.ts +0 -39
  312. package/dist/src/agents/schema-utils.js +0 -57
  313. package/dist/src/agents/schema-utils.js.map +0 -1
  314. package/dist/src/agents/schema-utils.test.js +0 -144
  315. package/dist/src/agents/schema-utils.test.js.map +0 -1
  316. package/dist/src/core/clientHookTriggers.d.ts +0 -36
  317. package/dist/src/core/clientHookTriggers.js +0 -76
  318. package/dist/src/core/clientHookTriggers.js.map +0 -1
  319. package/dist/src/core/geminiChatHookTriggers.d.ts +0 -68
  320. package/dist/src/core/geminiChatHookTriggers.js +0 -158
  321. package/dist/src/core/geminiChatHookTriggers.js.map +0 -1
  322. package/dist/src/core/geminiChatHookTriggers.test.js +0 -153
  323. package/dist/src/core/geminiChatHookTriggers.test.js.map +0 -1
  324. /package/dist/src/{agents/delegate-to-agent-tool.test.d.ts → core/prompts-substitution.test.d.ts} +0 -0
  325. /package/dist/src/{agents/schema-utils.test.d.ts → routing/strategies/numericalClassifierStrategy.test.d.ts} +0 -0
@@ -1,8 +1,7 @@
1
1
  # Writing hooks for Gemini CLI
2
2
 
3
3
  This guide will walk you through creating hooks for Gemini CLI, from a simple
4
- logging hook to a comprehensive workflow assistant that demonstrates all hook
5
- events working together.
4
+ logging hook to a comprehensive workflow assistant.
6
5
 
7
6
  ## Prerequisites
8
7
 
@@ -17,9 +16,17 @@ Before you start, make sure you have:
17
16
  Let's create a simple hook that logs all tool executions to understand the
18
17
  basics.
19
18
 
19
+ **Crucial Rule:** Always write logs to `stderr`. Write only the final JSON to
20
+ `stdout`.
21
+
20
22
  ### Step 1: Create your hook script
21
23
 
22
- Create a directory for hooks and a simple logging script:
24
+ Create a directory for hooks and a simple logging script.
25
+
26
+ > **Note**:
27
+ >
28
+ > This example uses `jq` to parse JSON. If you don't have it installed, you can
29
+ > perform similar logic using Node.js or Python.
23
30
 
24
31
  ```bash
25
32
  mkdir -p .gemini/hooks
@@ -28,62 +35,38 @@ cat > .gemini/hooks/log-tools.sh << 'EOF'
28
35
  # Read hook input from stdin
29
36
  input=$(cat)
30
37
 
31
- # Extract tool name
38
+ # Extract tool name (requires jq)
32
39
  tool_name=$(echo "$input" | jq -r '.tool_name')
33
40
 
41
+ # Log to stderr (visible in terminal if hook fails, or captured in logs)
42
+ echo "Logging tool: $tool_name" >&2
43
+
34
44
  # Log to file
35
45
  echo "[$(date)] Tool executed: $tool_name" >> .gemini/tool-log.txt
36
46
 
37
- # Return success (exit 0) - output goes to user in transcript mode
38
- echo "Logged: $tool_name"
47
+ # Return success (exit 0) with empty JSON
48
+ echo "{}"
49
+ exit 0
39
50
  EOF
40
51
 
41
52
  chmod +x .gemini/hooks/log-tools.sh
42
53
  ```
43
54
 
44
- ### Step 2: Configure the hook
45
-
46
- Add the hook configuration to `.gemini/settings.json`:
47
-
48
- ```json
49
- {
50
- "hooks": {
51
- "AfterTool": [
52
- {
53
- "matcher": "*",
54
- "hooks": [
55
- {
56
- "name": "tool-logger",
57
- "type": "command",
58
- "command": "$GEMINI_PROJECT_DIR/.gemini/hooks/log-tools.sh",
59
- "description": "Log all tool executions"
60
- }
61
- ]
62
- }
63
- ]
64
- }
65
- }
66
- ```
67
-
68
- ### Step 3: Test your hook
69
-
70
- Run Gemini CLI and execute any command that uses tools:
71
-
72
- ```
73
- > Read the README.md file
74
-
75
- [Agent uses read_file tool]
55
+ ## Exit Code Strategies
76
56
 
77
- Logged: read_file
78
- ```
57
+ There are two ways to control or block an action in Gemini CLI:
79
58
 
80
- Check `.gemini/tool-log.txt` to see the logged tool executions.
59
+ | Strategy | Exit Code | Implementation | Best For |
60
+ | :------------------------- | :-------- | :----------------------------------------------------------------- | :---------------------------------------------------------- |
61
+ | **Structured (Idiomatic)** | `0` | Return a JSON object like `{"decision": "deny", "reason": "..."}`. | Production hooks, custom user feedback, and complex logic. |
62
+ | **Emergency Brake** | `2` | Print the error message to `stderr` and exit. | Simple security gates, script errors, or rapid prototyping. |
81
63
 
82
64
  ## Practical examples
83
65
 
84
66
  ### Security: Block secrets in commits
85
67
 
86
- Prevent committing files containing API keys or passwords.
68
+ Prevent committing files containing API keys or passwords. Note that we use
69
+ **Exit Code 0** to provide a structured denial message to the agent.
87
70
 
88
71
  **`.gemini/hooks/block-secrets.sh`:**
89
72
 
@@ -96,93 +79,26 @@ content=$(echo "$input" | jq -r '.tool_input.content // .tool_input.new_string /
96
79
 
97
80
  # Check for secrets
98
81
  if echo "$content" | grep -qE 'api[_-]?key|password|secret'; then
99
- echo '{"decision":"deny","reason":"Potential secret detected"}' >&2
100
- exit 2
101
- fi
82
+ # Log to stderr
83
+ echo "Blocked potential secret" >&2
102
84
 
103
- exit 0
104
- ```
105
-
106
- **`.gemini/settings.json`:**
107
-
108
- ```json
85
+ # Return structured denial to stdout
86
+ cat <<EOF
109
87
  {
110
- "hooks": {
111
- "BeforeTool": [
112
- {
113
- "matcher": "write_file|replace",
114
- "hooks": [
115
- {
116
- "name": "secret-scanner",
117
- "type": "command",
118
- "command": "$GEMINI_PROJECT_DIR/.gemini/hooks/block-secrets.sh",
119
- "description": "Prevent committing secrets"
120
- }
121
- ]
122
- }
123
- ]
124
- }
88
+ "decision": "deny",
89
+ "reason": "Security Policy: Potential secret detected in content.",
90
+ "systemMessage": "🔒 Security scanner blocked operation"
125
91
  }
126
- ```
127
-
128
- ### Auto-testing after code changes
129
-
130
- Automatically run tests when code files are modified.
131
-
132
- **`.gemini/hooks/auto-test.sh`:**
133
-
134
- ```bash
135
- #!/usr/bin/env bash
136
- input=$(cat)
137
-
138
- file_path=$(echo "$input" | jq -r '.tool_input.file_path')
139
-
140
- # Only test .ts files
141
- if [[ ! "$file_path" =~ \.ts$ ]]; then
142
- exit 0
143
- fi
144
-
145
- # Find corresponding test file
146
- test_file="${file_path%.ts}.test.ts"
147
-
148
- if [ ! -f "$test_file" ]; then
149
- echo "⚠️ No test file found"
92
+ EOF
150
93
  exit 0
151
94
  fi
152
95
 
153
- # Run tests
154
- if npx vitest run "$test_file" --silent 2>&1 | head -20; then
155
- echo "✅ Tests passed"
156
- else
157
- echo "❌ Tests failed"
158
- fi
159
-
96
+ # Allow
97
+ echo '{"decision": "allow"}'
160
98
  exit 0
161
99
  ```
162
100
 
163
- **`.gemini/settings.json`:**
164
-
165
- ```json
166
- {
167
- "hooks": {
168
- "AfterTool": [
169
- {
170
- "matcher": "write_file|replace",
171
- "hooks": [
172
- {
173
- "name": "auto-test",
174
- "type": "command",
175
- "command": "$GEMINI_PROJECT_DIR/.gemini/hooks/auto-test.sh",
176
- "description": "Run tests after code changes"
177
- }
178
- ]
179
- }
180
- ]
181
- }
182
- }
183
- ```
184
-
185
- ### Dynamic context injection
101
+ ### Dynamic context injection (Git History)
186
102
 
187
103
  Add relevant project context before each agent interaction.
188
104
 
@@ -205,75 +121,81 @@ cat <<EOF
205
121
  EOF
206
122
  ```
207
123
 
208
- **`.gemini/settings.json`:**
124
+ ### RAG-based Tool Filtering (BeforeToolSelection)
209
125
 
210
- ```json
211
- {
212
- "hooks": {
213
- "BeforeAgent": [
214
- {
215
- "matcher": "*",
216
- "hooks": [
217
- {
218
- "name": "git-context",
219
- "type": "command",
220
- "command": "$GEMINI_PROJECT_DIR/.gemini/hooks/inject-context.sh",
221
- "description": "Inject git commit history"
222
- }
223
- ]
224
- }
225
- ]
226
- }
227
- }
228
- ```
229
-
230
- ## Advanced features
126
+ Use `BeforeToolSelection` to intelligently reduce the tool space. This example
127
+ uses a Node.js script to check the user's prompt and allow only relevant tools.
231
128
 
232
- ### RAG-based tool filtering
129
+ **`.gemini/hooks/filter-tools.js`:**
233
130
 
234
- Use `BeforeToolSelection` to intelligently reduce the tool space based on the
235
- current task. Instead of sending all 100+ tools to the model, filter to the most
236
- relevant ~15 tools using semantic search or keyword matching.
131
+ ```javascript
132
+ #!/usr/bin/env node
133
+ const fs = require('fs');
237
134
 
238
- This improves:
135
+ async function main() {
136
+ const input = JSON.parse(fs.readFileSync(0, 'utf-8'));
137
+ const { llm_request } = input;
239
138
 
240
- - **Model accuracy:** Fewer similar tools reduce confusion
241
- - **Response speed:** Smaller tool space is faster to process
242
- - **Cost efficiency:** Less tokens used per request
139
+ // Decoupled API: Access messages from llm_request
140
+ const messages = llm_request.messages || [];
141
+ const lastUserMessage = messages
142
+ .slice()
143
+ .reverse()
144
+ .find((m) => m.role === 'user');
243
145
 
244
- ### Cross-session memory
146
+ if (!lastUserMessage) {
147
+ console.log(JSON.stringify({})); // Do nothing
148
+ return;
149
+ }
245
150
 
246
- Use `SessionStart` and `SessionEnd` hooks to maintain persistent knowledge
247
- across sessions:
151
+ const text = lastUserMessage.content;
152
+ const allowed = ['write_todos']; // Always allow memory
248
153
 
249
- - **SessionStart:** Load relevant memories from previous sessions
250
- - **AfterModel:** Record important interactions during the session
251
- - **SessionEnd:** Extract learnings and store for future use
154
+ // Simple keyword matching
155
+ if (text.includes('read') || text.includes('check')) {
156
+ allowed.push('read_file', 'list_directory');
157
+ }
158
+ if (text.includes('test')) {
159
+ allowed.push('run_shell_command');
160
+ }
252
161
 
253
- This enables the assistant to learn project conventions, remember important
254
- decisions, and share knowledge across team members.
162
+ // If we found specific intent, filter tools. Otherwise allow all.
163
+ if (allowed.length > 1) {
164
+ console.log(
165
+ JSON.stringify({
166
+ hookSpecificOutput: {
167
+ hookEventName: 'BeforeToolSelection',
168
+ toolConfig: {
169
+ mode: 'ANY', // Force usage of one of these tools (or AUTO)
170
+ allowedFunctionNames: allowed,
171
+ },
172
+ },
173
+ }),
174
+ );
175
+ } else {
176
+ console.log(JSON.stringify({}));
177
+ }
178
+ }
255
179
 
256
- ### Hook chaining
180
+ main().catch((err) => {
181
+ console.error(err);
182
+ process.exit(1);
183
+ });
184
+ ```
257
185
 
258
- Multiple hooks for the same event run in the order declared. Each hook can build
259
- upon previous hooks' outputs:
186
+ **`.gemini/settings.json`:**
260
187
 
261
188
  ```json
262
189
  {
263
190
  "hooks": {
264
- "BeforeAgent": [
191
+ "BeforeToolSelection": [
265
192
  {
266
193
  "matcher": "*",
267
194
  "hooks": [
268
195
  {
269
- "name": "load-memories",
196
+ "name": "intent-filter",
270
197
  "type": "command",
271
- "command": "./hooks/load-memories.sh"
272
- },
273
- {
274
- "name": "analyze-sentiment",
275
- "type": "command",
276
- "command": "./hooks/analyze-sentiment.sh"
198
+ "command": "node .gemini/hooks/filter-tools.js"
277
199
  }
278
200
  ]
279
201
  }
@@ -282,58 +204,30 @@ upon previous hooks' outputs:
282
204
  }
283
205
  ```
284
206
 
285
- ## Complete example: Smart Development Workflow Assistant
207
+ > **TIP**
208
+ >
209
+ > **Union Aggregation Strategy**: `BeforeToolSelection` is unique in that it
210
+ > combines the results of all matching hooks. If you have multiple filtering
211
+ > hooks, the agent will receive the **union** of all whitelisted tools. Only
212
+ > using `mode: "NONE"` will override other hooks to disable all tools.
286
213
 
287
- This comprehensive example demonstrates all hook events working together with
288
- two advanced features:
214
+ ## Complete example: Smart Development Workflow Assistant
289
215
 
290
- - **RAG-based tool selection:** Reduces 100+ tools to ~15 relevant ones per task
291
- - **Cross-session memory:** Learns and persists project knowledge
216
+ This comprehensive example demonstrates all hook events working together. We
217
+ will build a system that maintains memory, filters tools, and checks for
218
+ security.
292
219
 
293
220
  ### Architecture
294
221
 
295
- ```
296
- SessionStart Initialize memory & index tools
297
-
298
- BeforeAgent Inject relevant memories
299
-
300
- BeforeModel Add system instructions
301
-
302
- BeforeToolSelection → Filter tools via RAG
303
-
304
- BeforeTool → Validate security
305
-
306
- AfterTool → Run auto-tests
307
-
308
- AfterModel → Record interaction
309
-
310
- SessionEnd → Extract and store memories
311
- ```
312
-
313
- ### Installation
314
-
315
- **Prerequisites:**
316
-
317
- - Node.js 18+
318
- - Gemini CLI installed
222
+ 1. **SessionStart**: Load project memories.
223
+ 2. **BeforeAgent**: Inject memories into context.
224
+ 3. **BeforeToolSelection**: Filter tools based on intent.
225
+ 4. **BeforeTool**: Scan for secrets.
226
+ 5. **AfterModel**: Record interactions.
227
+ 6. **AfterAgent**: Validate final response quality (Retry).
228
+ 7. **SessionEnd**: Consolidate memories.
319
229
 
320
- **Setup:**
321
-
322
- ```bash
323
- # Create hooks directory
324
- mkdir -p .gemini/hooks .gemini/memory
325
-
326
- # Install dependencies
327
- npm install --save-dev chromadb @google/generative-ai
328
-
329
- # Copy hook scripts (shown below)
330
- # Make them executable
331
- chmod +x .gemini/hooks/*.js
332
- ```
333
-
334
- ### Configuration
335
-
336
- **`.gemini/settings.json`:**
230
+ ### Configuration (`.gemini/settings.json`)
337
231
 
338
232
  ```json
339
233
  {
@@ -343,10 +237,9 @@ chmod +x .gemini/hooks/*.js
343
237
  "matcher": "startup",
344
238
  "hooks": [
345
239
  {
346
- "name": "init-assistant",
240
+ "name": "init",
347
241
  "type": "command",
348
- "command": "node $GEMINI_PROJECT_DIR/.gemini/hooks/init.js",
349
- "description": "Initialize Smart Workflow Assistant"
242
+ "command": "node .gemini/hooks/init.js"
350
243
  }
351
244
  ]
352
245
  }
@@ -356,10 +249,9 @@ chmod +x .gemini/hooks/*.js
356
249
  "matcher": "*",
357
250
  "hooks": [
358
251
  {
359
- "name": "inject-memories",
252
+ "name": "memory",
360
253
  "type": "command",
361
- "command": "node $GEMINI_PROJECT_DIR/.gemini/hooks/inject-memories.js",
362
- "description": "Inject relevant project memories"
254
+ "command": "node .gemini/hooks/inject-memories.js"
363
255
  }
364
256
  ]
365
257
  }
@@ -369,62 +261,57 @@ chmod +x .gemini/hooks/*.js
369
261
  "matcher": "*",
370
262
  "hooks": [
371
263
  {
372
- "name": "rag-filter",
264
+ "name": "filter",
373
265
  "type": "command",
374
- "command": "node $GEMINI_PROJECT_DIR/.gemini/hooks/rag-filter.js",
375
- "description": "Filter tools using RAG"
266
+ "command": "node .gemini/hooks/rag-filter.js"
376
267
  }
377
268
  ]
378
269
  }
379
270
  ],
380
271
  "BeforeTool": [
381
272
  {
382
- "matcher": "write_file|replace",
273
+ "matcher": "write_file",
383
274
  "hooks": [
384
275
  {
385
- "name": "security-check",
276
+ "name": "security",
386
277
  "type": "command",
387
- "command": "node $GEMINI_PROJECT_DIR/.gemini/hooks/security.js",
388
- "description": "Prevent committing secrets"
278
+ "command": "node .gemini/hooks/security.js"
389
279
  }
390
280
  ]
391
281
  }
392
282
  ],
393
- "AfterTool": [
283
+ "AfterModel": [
394
284
  {
395
- "matcher": "write_file|replace",
285
+ "matcher": "*",
396
286
  "hooks": [
397
287
  {
398
- "name": "auto-test",
288
+ "name": "record",
399
289
  "type": "command",
400
- "command": "node $GEMINI_PROJECT_DIR/.gemini/hooks/auto-test.js",
401
- "description": "Run tests after code changes"
290
+ "command": "node .gemini/hooks/record.js"
402
291
  }
403
292
  ]
404
293
  }
405
294
  ],
406
- "AfterModel": [
295
+ "AfterAgent": [
407
296
  {
408
297
  "matcher": "*",
409
298
  "hooks": [
410
299
  {
411
- "name": "record-interaction",
300
+ "name": "validate",
412
301
  "type": "command",
413
- "command": "node $GEMINI_PROJECT_DIR/.gemini/hooks/record.js",
414
- "description": "Record interaction for learning"
302
+ "command": "node .gemini/hooks/validate.js"
415
303
  }
416
304
  ]
417
305
  }
418
306
  ],
419
307
  "SessionEnd": [
420
308
  {
421
- "matcher": "exit|logout",
309
+ "matcher": "exit",
422
310
  "hooks": [
423
311
  {
424
- "name": "consolidate-memories",
312
+ "name": "save",
425
313
  "type": "command",
426
- "command": "node $GEMINI_PROJECT_DIR/.gemini/hooks/consolidate.js",
427
- "description": "Extract and store session learnings"
314
+ "command": "node .gemini/hooks/consolidate.js"
428
315
  }
429
316
  ]
430
317
  }
@@ -433,612 +320,131 @@ chmod +x .gemini/hooks/*.js
433
320
  }
434
321
  ```
435
322
 
436
- ### Hook scripts
437
-
438
- #### 1. Initialize (SessionStart)
439
-
440
- **`.gemini/hooks/init.js`:**
441
-
442
- ```javascript
443
- #!/usr/bin/env node
444
- const { ChromaClient } = require('chromadb');
445
- const path = require('path');
446
- const fs = require('fs');
447
-
448
- async function main() {
449
- const projectDir = process.env.GEMINI_PROJECT_DIR;
450
- const chromaPath = path.join(projectDir, '.gemini', 'chroma');
451
-
452
- // Ensure chroma directory exists
453
- fs.mkdirSync(chromaPath, { recursive: true });
454
-
455
- const client = new ChromaClient({ path: chromaPath });
456
-
457
- // Initialize memory collection
458
- await client.getOrCreateCollection({
459
- name: 'project_memories',
460
- metadata: { 'hnsw:space': 'cosine' },
461
- });
462
-
463
- // Count existing memories
464
- const collection = await client.getCollection({ name: 'project_memories' });
465
- const memoryCount = await collection.count();
466
-
467
- console.log(
468
- JSON.stringify({
469
- hookSpecificOutput: {
470
- hookEventName: 'SessionStart',
471
- additionalContext: `Smart Workflow Assistant initialized with ${memoryCount} project memories.`,
472
- },
473
- systemMessage: `🧠 ${memoryCount} memories loaded`,
474
- }),
475
- );
476
- }
477
-
478
- function readStdin() {
479
- return new Promise((resolve) => {
480
- const chunks = [];
481
- process.stdin.on('data', (chunk) => chunks.push(chunk));
482
- process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString()));
483
- });
484
- }
485
-
486
- readStdin().then(main).catch(console.error);
487
- ```
323
+ ### Hook Scripts
488
324
 
489
- #### 2. Inject memories (BeforeAgent)
325
+ > **Note**: For brevity, these scripts use `console.error` for logging and
326
+ > standard `console.log` for JSON output.
490
327
 
491
- **`.gemini/hooks/inject-memories.js`:**
328
+ #### 1. Initialize (`init.js`)
492
329
 
493
330
  ```javascript
494
331
  #!/usr/bin/env node
495
- const { GoogleGenerativeAI } = require('@google/generative-ai');
496
- const { ChromaClient } = require('chromadb');
497
- const path = require('path');
498
-
499
- async function main() {
500
- const input = JSON.parse(await readStdin());
501
- const { prompt } = input;
502
-
503
- if (!prompt?.trim()) {
504
- console.log(JSON.stringify({}));
505
- return;
506
- }
507
-
508
- // Embed the prompt
509
- const genai = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
510
- const model = genai.getGenerativeModel({ model: 'text-embedding-004' });
511
- const result = await model.embedContent(prompt);
512
-
513
- // Search memories
514
- const projectDir = process.env.GEMINI_PROJECT_DIR;
515
- const client = new ChromaClient({
516
- path: path.join(projectDir, '.gemini', 'chroma'),
517
- });
518
-
519
- try {
520
- const collection = await client.getCollection({ name: 'project_memories' });
521
- const results = await collection.query({
522
- queryEmbeddings: [result.embedding.values],
523
- nResults: 3,
524
- });
525
-
526
- if (results.documents[0]?.length > 0) {
527
- const memories = results.documents[0]
528
- .map((doc, i) => {
529
- const meta = results.metadatas[0][i];
530
- return `- [${meta.category}] ${meta.summary}`;
531
- })
532
- .join('\n');
533
-
534
- console.log(
535
- JSON.stringify({
536
- hookSpecificOutput: {
537
- hookEventName: 'BeforeAgent',
538
- additionalContext: `\n## Relevant Project Context\n\n${memories}\n`,
539
- },
540
- systemMessage: `💭 ${results.documents[0].length} memories recalled`,
541
- }),
542
- );
543
- } else {
544
- console.log(JSON.stringify({}));
545
- }
546
- } catch (error) {
547
- console.log(JSON.stringify({}));
548
- }
549
- }
550
-
551
- function readStdin() {
552
- return new Promise((resolve) => {
553
- const chunks = [];
554
- process.stdin.on('data', (chunk) => chunks.push(chunk));
555
- process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString()));
556
- });
557
- }
558
-
559
- readStdin().then(main).catch(console.error);
332
+ // Initialize DB or resources
333
+ console.error('Initializing assistant...');
334
+
335
+ // Output to user
336
+ console.log(
337
+ JSON.stringify({
338
+ systemMessage: '🧠 Smart Assistant Loaded',
339
+ }),
340
+ );
560
341
  ```
561
342
 
562
- #### 3. RAG tool filter (BeforeToolSelection)
563
-
564
- **`.gemini/hooks/rag-filter.js`:**
343
+ #### 2. Inject Memories (`inject-memories.js`)
565
344
 
566
345
  ```javascript
567
346
  #!/usr/bin/env node
568
- const { GoogleGenerativeAI } = require('@google/generative-ai');
347
+ const fs = require('fs');
569
348
 
570
349
  async function main() {
571
- const input = JSON.parse(await readStdin());
572
- const { llm_request } = input;
573
- const candidateTools =
574
- llm_request.toolConfig?.functionCallingConfig?.allowedFunctionNames || [];
575
-
576
- // Skip if already filtered
577
- if (candidateTools.length <= 20) {
578
- console.log(JSON.stringify({}));
579
- return;
580
- }
581
-
582
- // Extract recent user messages
583
- const recentMessages = llm_request.messages
584
- .slice(-3)
585
- .filter((m) => m.role === 'user')
586
- .map((m) => m.content)
587
- .join('\n');
588
-
589
- // Use fast model to extract task keywords
590
- const genai = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
591
- const model = genai.getGenerativeModel({ model: 'gemini-2.0-flash-exp' });
592
-
593
- const result = await model.generateContent(
594
- `Extract 3-5 keywords describing needed tool capabilities from this request:\n\n${recentMessages}\n\nKeywords (comma-separated):`,
595
- );
596
-
597
- const keywords = result.response
598
- .text()
599
- .toLowerCase()
600
- .split(',')
601
- .map((k) => k.trim());
602
-
603
- // Simple keyword-based filtering + core tools
604
- const coreTools = ['read_file', 'write_file', 'replace', 'run_shell_command'];
605
- const filtered = candidateTools.filter((tool) => {
606
- if (coreTools.includes(tool)) return true;
607
- const toolLower = tool.toLowerCase();
608
- return keywords.some(
609
- (kw) => toolLower.includes(kw) || kw.includes(toolLower),
610
- );
611
- });
350
+ const input = JSON.parse(fs.readFileSync(0, 'utf-8'));
351
+ // Assume we fetch memories from a DB here
352
+ const memories = '- [Memory] Always use TypeScript for this project.';
612
353
 
613
354
  console.log(
614
355
  JSON.stringify({
615
356
  hookSpecificOutput: {
616
- hookEventName: 'BeforeToolSelection',
617
- toolConfig: {
618
- functionCallingConfig: {
619
- mode: 'ANY',
620
- allowedFunctionNames: filtered.slice(0, 20),
621
- },
622
- },
357
+ hookEventName: 'BeforeAgent',
358
+ additionalContext: `\n## Relevant Memories\n${memories}`,
623
359
  },
624
- systemMessage: `🎯 Filtered ${candidateTools.length} → ${Math.min(filtered.length, 20)} tools`,
625
360
  }),
626
361
  );
627
362
  }
628
-
629
- function readStdin() {
630
- return new Promise((resolve) => {
631
- const chunks = [];
632
- process.stdin.on('data', (chunk) => chunks.push(chunk));
633
- process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString()));
634
- });
635
- }
636
-
637
- readStdin().then(main).catch(console.error);
363
+ main();
638
364
  ```
639
365
 
640
- #### 4. Security validation (BeforeTool)
641
-
642
- **`.gemini/hooks/security.js`:**
366
+ #### 3. Security Check (`security.js`)
643
367
 
644
368
  ```javascript
645
369
  #!/usr/bin/env node
646
-
647
- const SECRET_PATTERNS = [
648
- /api[_-]?key\s*[:=]\s*['"]?[a-zA-Z0-9_-]{20,}['"]?/i,
649
- /password\s*[:=]\s*['"]?[^\s'"]{8,}['"]?/i,
650
- /secret\s*[:=]\s*['"]?[a-zA-Z0-9_-]{20,}['"]?/i,
651
- /AKIA[0-9A-Z]{16}/, // AWS
652
- /ghp_[a-zA-Z0-9]{36}/, // GitHub
653
- ];
654
-
655
- async function main() {
656
- const input = JSON.parse(await readStdin());
657
- const { tool_input } = input;
658
-
659
- const content = tool_input.content || tool_input.new_string || '';
660
-
661
- for (const pattern of SECRET_PATTERNS) {
662
- if (pattern.test(content)) {
663
- console.log(
664
- JSON.stringify({
665
- decision: 'deny',
666
- reason:
667
- 'Potential secret detected in code. Please remove sensitive data.',
668
- systemMessage: '🚨 Secret scanner blocked operation',
669
- }),
670
- );
671
- process.exit(2);
672
- }
673
- }
674
-
675
- console.log(JSON.stringify({ decision: 'allow' }));
676
- }
677
-
678
- function readStdin() {
679
- return new Promise((resolve) => {
680
- const chunks = [];
681
- process.stdin.on('data', (chunk) => chunks.push(chunk));
682
- process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString()));
683
- });
684
- }
685
-
686
- readStdin().then(main).catch(console.error);
687
- ```
688
-
689
- #### 5. Auto-test (AfterTool)
690
-
691
- **`.gemini/hooks/auto-test.js`:**
692
-
693
- ```javascript
694
- #!/usr/bin/env node
695
- const { execSync } = require('child_process');
696
370
  const fs = require('fs');
697
- const path = require('path');
698
-
699
- async function main() {
700
- const input = JSON.parse(await readStdin());
701
- const { tool_input } = input;
702
- const filePath = tool_input.file_path;
371
+ const input = JSON.parse(fs.readFileSync(0));
372
+ const content = input.tool_input.content || '';
703
373
 
704
- if (!filePath?.match(/\.(ts|js|tsx|jsx)$/)) {
705
- console.log(JSON.stringify({}));
706
- return;
707
- }
708
-
709
- // Find test file
710
- const ext = path.extname(filePath);
711
- const base = filePath.slice(0, -ext.length);
712
- const testFile = `${base}.test${ext}`;
713
-
714
- if (!fs.existsSync(testFile)) {
715
- console.log(
716
- JSON.stringify({
717
- systemMessage: `⚠️ No test file: ${path.basename(testFile)}`,
718
- }),
719
- );
720
- return;
721
- }
722
-
723
- // Run tests
724
- try {
725
- execSync(`npx vitest run ${testFile} --silent`, {
726
- encoding: 'utf8',
727
- stdio: 'pipe',
728
- timeout: 30000,
729
- });
730
-
731
- console.log(
732
- JSON.stringify({
733
- systemMessage: `✅ Tests passed: ${path.basename(filePath)}`,
734
- }),
735
- );
736
- } catch (error) {
737
- console.log(
738
- JSON.stringify({
739
- systemMessage: `❌ Tests failed: ${path.basename(filePath)}`,
740
- }),
741
- );
742
- }
743
- }
744
-
745
- function readStdin() {
746
- return new Promise((resolve) => {
747
- const chunks = [];
748
- process.stdin.on('data', (chunk) => chunks.push(chunk));
749
- process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString()));
750
- });
374
+ if (content.includes('SECRET_KEY')) {
375
+ console.log(
376
+ JSON.stringify({
377
+ decision: 'deny',
378
+ reason: 'Found SECRET_KEY in content',
379
+ systemMessage: '🚨 Blocked sensitive commit',
380
+ }),
381
+ );
382
+ process.exit(0);
751
383
  }
752
384
 
753
- readStdin().then(main).catch(console.error);
385
+ console.log(JSON.stringify({ decision: 'allow' }));
754
386
  ```
755
387
 
756
- #### 6. Record interaction (AfterModel)
757
-
758
- **`.gemini/hooks/record.js`:**
388
+ #### 4. Record Interaction (`record.js`)
759
389
 
760
390
  ```javascript
761
391
  #!/usr/bin/env node
762
392
  const fs = require('fs');
763
393
  const path = require('path');
764
394
 
765
- async function main() {
766
- const input = JSON.parse(await readStdin());
767
- const { llm_request, llm_response } = input;
768
- const projectDir = process.env.GEMINI_PROJECT_DIR;
769
- const sessionId = process.env.GEMINI_SESSION_ID;
770
-
771
- const tempFile = path.join(
772
- projectDir,
773
- '.gemini',
774
- 'memory',
775
- `session-${sessionId}.jsonl`,
776
- );
777
-
778
- fs.mkdirSync(path.dirname(tempFile), { recursive: true });
779
-
780
- // Extract user message and model response
781
- const userMsg = llm_request.messages
782
- ?.filter((m) => m.role === 'user')
783
- .slice(-1)[0]?.content;
784
-
785
- const modelMsg = llm_response.candidates?.[0]?.content?.parts
786
- ?.map((p) => p.text)
787
- .filter(Boolean)
788
- .join('');
789
-
790
- if (userMsg && modelMsg) {
791
- const interaction = {
792
- timestamp: new Date().toISOString(),
793
- user: process.env.USER || 'unknown',
794
- request: userMsg.slice(0, 500), // Truncate for storage
795
- response: modelMsg.slice(0, 500),
796
- };
797
-
798
- fs.appendFileSync(tempFile, JSON.stringify(interaction) + '\n');
799
- }
800
-
801
- console.log(JSON.stringify({}));
802
- }
803
-
804
- function readStdin() {
805
- return new Promise((resolve) => {
806
- const chunks = [];
807
- process.stdin.on('data', (chunk) => chunks.push(chunk));
808
- process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString()));
809
- });
810
- }
811
-
812
- readStdin().then(main).catch(console.error);
395
+ const input = JSON.parse(fs.readFileSync(0));
396
+ const { llm_request, llm_response } = input;
397
+ const logFile = path.join(
398
+ process.env.GEMINI_PROJECT_DIR,
399
+ '.gemini/memory/session.jsonl',
400
+ );
401
+
402
+ fs.appendFileSync(
403
+ logFile,
404
+ JSON.stringify({
405
+ request: llm_request,
406
+ response: llm_response,
407
+ timestamp: new Date().toISOString(),
408
+ }) + '\n',
409
+ );
410
+
411
+ console.log(JSON.stringify({}));
813
412
  ```
814
413
 
815
- #### 7. Consolidate memories (SessionEnd)
816
-
817
- **`.gemini/hooks/consolidate.js`:**
414
+ #### 5. Validate Response (`validate.js`)
818
415
 
819
- ````javascript
416
+ ```javascript
820
417
  #!/usr/bin/env node
821
418
  const fs = require('fs');
822
- const path = require('path');
823
- const { GoogleGenerativeAI } = require('@google/generative-ai');
824
- const { ChromaClient } = require('chromadb');
419
+ const input = JSON.parse(fs.readFileSync(0));
420
+ const response = input.prompt_response;
825
421
 
826
- async function main() {
827
- const input = JSON.parse(await readStdin());
828
- const projectDir = process.env.GEMINI_PROJECT_DIR;
829
- const sessionId = process.env.GEMINI_SESSION_ID;
830
-
831
- const tempFile = path.join(
832
- projectDir,
833
- '.gemini',
834
- 'memory',
835
- `session-${sessionId}.jsonl`,
422
+ // Example: Check if the agent forgot to include a summary
423
+ if (!response.includes('Summary:')) {
424
+ console.log(
425
+ JSON.stringify({
426
+ decision: 'block', // Triggers an automatic retry turn
427
+ reason: 'Your response is missing a Summary section. Please add one.',
428
+ systemMessage: '🔄 Requesting missing summary...',
429
+ }),
836
430
  );
837
-
838
- if (!fs.existsSync(tempFile)) {
839
- console.log(JSON.stringify({}));
840
- return;
841
- }
842
-
843
- // Read interactions
844
- const interactions = fs
845
- .readFileSync(tempFile, 'utf8')
846
- .trim()
847
- .split('\n')
848
- .filter(Boolean)
849
- .map((line) => JSON.parse(line));
850
-
851
- if (interactions.length === 0) {
852
- fs.unlinkSync(tempFile);
853
- console.log(JSON.stringify({}));
854
- return;
855
- }
856
-
857
- // Extract memories using LLM
858
- const genai = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
859
- const model = genai.getGenerativeModel({ model: 'gemini-2.0-flash-exp' });
860
-
861
- const prompt = `Extract important project learnings from this session.
862
- Focus on: decisions, conventions, gotchas, patterns.
863
- Return JSON array with: category, summary, keywords
864
-
865
- Session interactions:
866
- ${JSON.stringify(interactions, null, 2)}
867
-
868
- JSON:`;
869
-
870
- try {
871
- const result = await model.generateContent(prompt);
872
- const text = result.response.text().replace(/```json\n?|\n?```/g, '');
873
- const memories = JSON.parse(text);
874
-
875
- // Store in ChromaDB
876
- const client = new ChromaClient({
877
- path: path.join(projectDir, '.gemini', 'chroma'),
878
- });
879
- const collection = await client.getCollection({ name: 'project_memories' });
880
- const embedModel = genai.getGenerativeModel({
881
- model: 'text-embedding-004',
882
- });
883
-
884
- for (const memory of memories) {
885
- const memoryText = `${memory.category}: ${memory.summary}`;
886
- const embedding = await embedModel.embedContent(memoryText);
887
- const id = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
888
-
889
- await collection.add({
890
- ids: [id],
891
- embeddings: [embedding.embedding.values],
892
- documents: [memoryText],
893
- metadatas: [
894
- {
895
- category: memory.category || 'general',
896
- summary: memory.summary,
897
- keywords: (memory.keywords || []).join(','),
898
- timestamp: new Date().toISOString(),
899
- },
900
- ],
901
- });
902
- }
903
-
904
- fs.unlinkSync(tempFile);
905
-
906
- console.log(
907
- JSON.stringify({
908
- systemMessage: `🧠 ${memories.length} new learnings saved for future sessions`,
909
- }),
910
- );
911
- } catch (error) {
912
- console.error('Error consolidating memories:', error);
913
- fs.unlinkSync(tempFile);
914
- console.log(JSON.stringify({}));
915
- }
916
- }
917
-
918
- function readStdin() {
919
- return new Promise((resolve) => {
920
- const chunks = [];
921
- process.stdin.on('data', (chunk) => chunks.push(chunk));
922
- process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString()));
923
- });
431
+ process.exit(0);
924
432
  }
925
433
 
926
- readStdin().then(main).catch(console.error);
927
- ````
928
-
929
- ### Example session
930
-
434
+ console.log(JSON.stringify({ decision: 'allow' }));
931
435
  ```
932
- > gemini
933
-
934
- 🧠 3 memories loaded
935
-
936
- > Fix the authentication bug in login.ts
937
-
938
- 💭 2 memories recalled:
939
- - [convention] Use middleware pattern for auth
940
- - [gotcha] Remember to update token types
941
-
942
- 🎯 Filtered 127 → 15 tools
943
-
944
- [Agent reads login.ts and proposes fix]
945
-
946
- ✅ Tests passed: login.ts
947
-
948
- ---
949
-
950
- > Add error logging to API endpoints
951
-
952
- 💭 3 memories recalled:
953
- - [convention] Use middleware pattern for auth
954
- - [pattern] Centralized error handling in middleware
955
- - [decision] Log errors to CloudWatch
956
-
957
- 🎯 Filtered 127 → 18 tools
958
-
959
- [Agent implements error logging]
960
-
961
- > /exit
962
-
963
- 🧠 2 new learnings saved for future sessions
964
- ```
965
-
966
- ### What makes this example special
967
-
968
- **RAG-based tool selection:**
969
-
970
- - Traditional: Send all 100+ tools causing confusion and context overflow
971
- - This example: Extract intent, filter to ~15 relevant tools
972
- - Benefits: Faster responses, better selection, lower costs
973
-
974
- **Cross-session memory:**
975
-
976
- - Traditional: Each session starts fresh
977
- - This example: Learns conventions, decisions, gotchas, patterns
978
- - Benefits: Shared knowledge across team members, persistent learnings
979
436
 
980
- **All hook events integrated:**
981
-
982
- Demonstrates every hook event with practical use cases in a cohesive workflow.
983
-
984
- ### Cost efficiency
985
-
986
- - Uses `gemini-2.0-flash-exp` for intent extraction (fast, cheap)
987
- - Uses `text-embedding-004` for RAG (inexpensive)
988
- - Caches tool descriptions (one-time cost)
989
- - Minimal overhead per request (<500ms typically)
990
-
991
- ### Customization
992
-
993
- **Adjust memory relevance:**
994
-
995
- ```javascript
996
- // In inject-memories.js, change nResults
997
- const results = await collection.query({
998
- queryEmbeddings: [result.embedding.values],
999
- nResults: 5, // More memories
1000
- });
1001
- ```
1002
-
1003
- **Modify tool filter count:**
437
+ #### 6. Consolidate Memories (`consolidate.js`)
1004
438
 
1005
439
  ```javascript
1006
- // In rag-filter.js, adjust the limit
1007
- allowedFunctionNames: filtered.slice(0, 30), // More tools
1008
- ```
1009
-
1010
- **Add custom security patterns:**
1011
-
1012
- ```javascript
1013
- // In security.js, add patterns
1014
- const SECRET_PATTERNS = [
1015
- // ... existing patterns
1016
- /private[_-]?key/i,
1017
- /auth[_-]?token/i,
1018
- ];
440
+ #!/usr/bin/env node
441
+ // Logic to save final session state
442
+ console.error('Consolidating memories for session end...');
1019
443
  ```
1020
444
 
1021
445
  ## Packaging as an extension
1022
446
 
1023
- While project-level hooks are great for specific repositories, you might want to
1024
- share your hooks across multiple projects or with other users. You can do this
1025
- by packaging your hooks as a [Gemini CLI extension](../extensions/index.md).
1026
-
1027
- Packaging as an extension provides:
1028
-
1029
- - **Easy distribution:** Share hooks via a git repository or GitHub release.
1030
- - **Centralized management:** Install, update, and disable hooks using
1031
- `gemini extensions` commands.
1032
- - **Version control:** Manage hook versions separately from your project code.
1033
- - **Variable substitution:** Use `${extensionPath}` and `${process.execPath}`
1034
- for portable, cross-platform scripts.
1035
-
1036
- To package hooks as an extension, follow the
1037
- [extensions hook documentation](../extensions/index.md#hooks).
1038
-
1039
- ## Learn more
1040
-
1041
- - [Hooks Reference](index.md) - Complete API reference and configuration
1042
- - [Best Practices](best-practices.md) - Security, performance, and debugging
1043
- - [Configuration](../get-started/configuration.md) - Gemini CLI settings
1044
- - [Custom Commands](../cli/custom-commands.md) - Create custom commands
447
+ While project-level hooks are great for specific repositories, you can share
448
+ your hooks across multiple projects by packaging them as a
449
+ [Gemini CLI extension](https://www.google.com/search?q=../extensions/index.md).
450
+ This provides version control, easy distribution, and centralized management.