@google/gemini-cli-core 0.21.0-nightly.20251219.70696e364 → 0.21.0-nightly.20251220.41a1a3eed

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 (172) hide show
  1. package/dist/docs/CONTRIBUTING.md +546 -0
  2. package/dist/docs/architecture.md +80 -0
  3. package/dist/docs/assets/connected_devtools.png +0 -0
  4. package/dist/docs/assets/gemini-screenshot.png +0 -0
  5. package/dist/docs/assets/release_patch.png +0 -0
  6. package/dist/docs/assets/theme-ansi-light.png +0 -0
  7. package/dist/docs/assets/theme-ansi.png +0 -0
  8. package/dist/docs/assets/theme-atom-one.png +0 -0
  9. package/dist/docs/assets/theme-ayu-light.png +0 -0
  10. package/dist/docs/assets/theme-ayu.png +0 -0
  11. package/dist/docs/assets/theme-custom.png +0 -0
  12. package/dist/docs/assets/theme-default-light.png +0 -0
  13. package/dist/docs/assets/theme-default.png +0 -0
  14. package/dist/docs/assets/theme-dracula.png +0 -0
  15. package/dist/docs/assets/theme-github-light.png +0 -0
  16. package/dist/docs/assets/theme-github.png +0 -0
  17. package/dist/docs/assets/theme-google-light.png +0 -0
  18. package/dist/docs/assets/theme-xcode-light.png +0 -0
  19. package/dist/docs/changelogs/index.md +592 -0
  20. package/dist/docs/changelogs/latest.md +225 -0
  21. package/dist/docs/changelogs/preview.md +129 -0
  22. package/dist/docs/changelogs/releases.md +896 -0
  23. package/dist/docs/cli/authentication.md +3 -0
  24. package/dist/docs/cli/checkpointing.md +94 -0
  25. package/dist/docs/cli/commands.md +354 -0
  26. package/dist/docs/cli/configuration.md +780 -0
  27. package/dist/docs/cli/custom-commands.md +315 -0
  28. package/dist/docs/cli/enterprise.md +565 -0
  29. package/dist/docs/cli/gemini-ignore.md +71 -0
  30. package/dist/docs/cli/gemini-md.md +108 -0
  31. package/dist/docs/cli/generation-settings.md +210 -0
  32. package/dist/docs/cli/headless.md +388 -0
  33. package/dist/docs/cli/index.md +63 -0
  34. package/dist/docs/cli/keyboard-shortcuts.md +143 -0
  35. package/dist/docs/cli/model-routing.md +37 -0
  36. package/dist/docs/cli/model.md +62 -0
  37. package/dist/docs/cli/sandbox.md +171 -0
  38. package/dist/docs/cli/session-management.md +158 -0
  39. package/dist/docs/cli/settings.md +112 -0
  40. package/dist/docs/cli/system-prompt.md +93 -0
  41. package/dist/docs/cli/telemetry.md +791 -0
  42. package/dist/docs/cli/themes.md +237 -0
  43. package/dist/docs/cli/token-caching.md +20 -0
  44. package/dist/docs/cli/trusted-folders.md +95 -0
  45. package/dist/docs/cli/tutorials.md +83 -0
  46. package/dist/docs/cli/uninstall.md +47 -0
  47. package/dist/docs/core/index.md +101 -0
  48. package/dist/docs/core/memport.md +244 -0
  49. package/dist/docs/core/policy-engine.md +267 -0
  50. package/dist/docs/core/tools-api.md +131 -0
  51. package/dist/docs/examples/proxy-script.md +83 -0
  52. package/dist/docs/extensions/extension-releasing.md +183 -0
  53. package/dist/docs/extensions/getting-started-extensions.md +245 -0
  54. package/dist/docs/extensions/index.md +293 -0
  55. package/dist/docs/faq.md +154 -0
  56. package/dist/docs/get-started/authentication.md +321 -0
  57. package/dist/docs/get-started/configuration-v1.md +888 -0
  58. package/dist/docs/get-started/configuration.md +1444 -0
  59. package/dist/docs/get-started/deployment.md +143 -0
  60. package/dist/docs/get-started/examples.md +219 -0
  61. package/dist/docs/get-started/gemini-3.md +116 -0
  62. package/dist/docs/get-started/index.md +71 -0
  63. package/dist/docs/get-started/installation.md +141 -0
  64. package/dist/docs/hooks/best-practices.md +806 -0
  65. package/dist/docs/hooks/index.md +665 -0
  66. package/dist/docs/hooks/reference.md +168 -0
  67. package/dist/docs/hooks/writing-hooks.md +1026 -0
  68. package/dist/docs/ide-integration/ide-companion-spec.md +267 -0
  69. package/dist/docs/ide-integration/index.md +202 -0
  70. package/dist/docs/index.md +147 -0
  71. package/dist/docs/integration-tests.md +211 -0
  72. package/dist/docs/issue-and-pr-automation.md +134 -0
  73. package/dist/docs/local-development.md +128 -0
  74. package/dist/docs/mermaid/context.mmd +103 -0
  75. package/dist/docs/mermaid/render-path.mmd +64 -0
  76. package/dist/docs/npm.md +62 -0
  77. package/dist/docs/quota-and-pricing.md +158 -0
  78. package/dist/docs/release-confidence.md +164 -0
  79. package/dist/docs/releases.md +540 -0
  80. package/dist/docs/sidebar.json +297 -0
  81. package/dist/docs/tools/file-system.md +217 -0
  82. package/dist/docs/tools/index.md +95 -0
  83. package/dist/docs/tools/mcp-server.md +1044 -0
  84. package/dist/docs/tools/memory.md +54 -0
  85. package/dist/docs/tools/shell.md +260 -0
  86. package/dist/docs/tools/todos.md +57 -0
  87. package/dist/docs/tools/web-fetch.md +59 -0
  88. package/dist/docs/tools/web-search.md +42 -0
  89. package/dist/docs/tos-privacy.md +96 -0
  90. package/dist/docs/troubleshooting.md +158 -0
  91. package/dist/google-gemini-cli-core-0.21.0-nightly.20251219.70696e364.tgz +0 -0
  92. package/dist/src/agents/delegate-to-agent-tool.test.js +1 -0
  93. package/dist/src/agents/delegate-to-agent-tool.test.js.map +1 -1
  94. package/dist/src/agents/introspection-agent.d.ts +23 -0
  95. package/dist/src/agents/introspection-agent.js +72 -0
  96. package/dist/src/agents/introspection-agent.js.map +1 -0
  97. package/dist/src/agents/introspection-agent.test.d.ts +6 -0
  98. package/dist/src/agents/introspection-agent.test.js +47 -0
  99. package/dist/src/agents/introspection-agent.test.js.map +1 -0
  100. package/dist/src/agents/local-executor.js +14 -12
  101. package/dist/src/agents/local-executor.js.map +1 -1
  102. package/dist/src/agents/local-executor.test.js +3 -0
  103. package/dist/src/agents/local-executor.test.js.map +1 -1
  104. package/dist/src/agents/registry.js +6 -0
  105. package/dist/src/agents/registry.js.map +1 -1
  106. package/dist/src/agents/registry.test.js +16 -0
  107. package/dist/src/agents/registry.test.js.map +1 -1
  108. package/dist/src/config/config.d.ts +6 -0
  109. package/dist/src/config/config.js +22 -0
  110. package/dist/src/config/config.js.map +1 -1
  111. package/dist/src/config/config.test.js +59 -1
  112. package/dist/src/config/config.test.js.map +1 -1
  113. package/dist/src/core/client.js +8 -4
  114. package/dist/src/core/client.js.map +1 -1
  115. package/dist/src/core/client.test.js +20 -0
  116. package/dist/src/core/client.test.js.map +1 -1
  117. package/dist/src/core/clientHookTriggers.js +2 -2
  118. package/dist/src/core/clientHookTriggers.js.map +1 -1
  119. package/dist/src/core/coreToolHookTriggers.js +3 -3
  120. package/dist/src/core/coreToolHookTriggers.js.map +1 -1
  121. package/dist/src/core/geminiChatHookTriggers.js +3 -3
  122. package/dist/src/core/geminiChatHookTriggers.js.map +1 -1
  123. package/dist/src/core/sessionHookTriggers.js +3 -3
  124. package/dist/src/core/sessionHookTriggers.js.map +1 -1
  125. package/dist/src/generated/git-commit.d.ts +2 -2
  126. package/dist/src/generated/git-commit.js +2 -2
  127. package/dist/src/hooks/hookEventHandler.js +10 -4
  128. package/dist/src/hooks/hookEventHandler.js.map +1 -1
  129. package/dist/src/hooks/hookEventHandler.test.js +40 -0
  130. package/dist/src/hooks/hookEventHandler.test.js.map +1 -1
  131. package/dist/src/hooks/hookRunner.js +12 -8
  132. package/dist/src/hooks/hookRunner.js.map +1 -1
  133. package/dist/src/hooks/hookRunner.test.js +58 -33
  134. package/dist/src/hooks/hookRunner.test.js.map +1 -1
  135. package/dist/src/mcp/oauth-provider.js +6 -2
  136. package/dist/src/mcp/oauth-provider.js.map +1 -1
  137. package/dist/src/mcp/oauth-provider.test.js +4 -1
  138. package/dist/src/mcp/oauth-provider.test.js.map +1 -1
  139. package/dist/src/mcp/oauth-utils.d.ts +8 -1
  140. package/dist/src/mcp/oauth-utils.js +30 -1
  141. package/dist/src/mcp/oauth-utils.js.map +1 -1
  142. package/dist/src/mcp/oauth-utils.test.js +42 -0
  143. package/dist/src/mcp/oauth-utils.test.js.map +1 -1
  144. package/dist/src/services/contextManager.d.ts +5 -11
  145. package/dist/src/services/contextManager.js +20 -17
  146. package/dist/src/services/contextManager.js.map +1 -1
  147. package/dist/src/services/contextManager.test.js +40 -41
  148. package/dist/src/services/contextManager.test.js.map +1 -1
  149. package/dist/src/tools/get-internal-docs.d.ts +27 -0
  150. package/dist/src/tools/get-internal-docs.js +129 -0
  151. package/dist/src/tools/get-internal-docs.js.map +1 -0
  152. package/dist/src/tools/get-internal-docs.test.d.ts +6 -0
  153. package/dist/src/tools/get-internal-docs.test.js +56 -0
  154. package/dist/src/tools/get-internal-docs.test.js.map +1 -0
  155. package/dist/src/tools/tool-names.d.ts +1 -0
  156. package/dist/src/tools/tool-names.js +1 -0
  157. package/dist/src/tools/tool-names.js.map +1 -1
  158. package/dist/src/utils/environmentContext.js +3 -0
  159. package/dist/src/utils/environmentContext.js.map +1 -1
  160. package/dist/src/utils/environmentContext.test.js +2 -0
  161. package/dist/src/utils/environmentContext.test.js.map +1 -1
  162. package/dist/src/utils/events.d.ts +3 -2
  163. package/dist/src/utils/events.js.map +1 -1
  164. package/dist/src/utils/memoryDiscovery.js +1 -1
  165. package/dist/src/utils/memoryDiscovery.js.map +1 -1
  166. package/dist/src/utils/memoryDiscovery.test.js +3 -1
  167. package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
  168. package/dist/src/utils/shell-utils.js +25 -4
  169. package/dist/src/utils/shell-utils.js.map +1 -1
  170. package/dist/tsconfig.tsbuildinfo +1 -1
  171. package/package.json +1 -1
  172. package/dist/google-gemini-cli-core-0.21.0-nightly.20251218.739c02bd6.tgz +0 -0
@@ -0,0 +1,1026 @@
1
+ # Writing hooks for Gemini CLI
2
+
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.
6
+
7
+ ## Prerequisites
8
+
9
+ Before you start, make sure you have:
10
+
11
+ - Gemini CLI installed and configured
12
+ - Basic understanding of shell scripting or JavaScript/Node.js
13
+ - Familiarity with JSON for hook input/output
14
+
15
+ ## Quick start
16
+
17
+ Let's create a simple hook that logs all tool executions to understand the
18
+ basics.
19
+
20
+ ### Step 1: Create your hook script
21
+
22
+ Create a directory for hooks and a simple logging script:
23
+
24
+ ```bash
25
+ mkdir -p .gemini/hooks
26
+ cat > .gemini/hooks/log-tools.sh << 'EOF'
27
+ #!/usr/bin/env bash
28
+ # Read hook input from stdin
29
+ input=$(cat)
30
+
31
+ # Extract tool name
32
+ tool_name=$(echo "$input" | jq -r '.tool_name')
33
+
34
+ # Log to file
35
+ echo "[$(date)] Tool executed: $tool_name" >> .gemini/tool-log.txt
36
+
37
+ # Return success (exit 0) - output goes to user in transcript mode
38
+ echo "Logged: $tool_name"
39
+ EOF
40
+
41
+ chmod +x .gemini/hooks/log-tools.sh
42
+ ```
43
+
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]
76
+
77
+ Logged: read_file
78
+ ```
79
+
80
+ Check `.gemini/tool-log.txt` to see the logged tool executions.
81
+
82
+ ## Practical examples
83
+
84
+ ### Security: Block secrets in commits
85
+
86
+ Prevent committing files containing API keys or passwords.
87
+
88
+ **`.gemini/hooks/block-secrets.sh`:**
89
+
90
+ ```bash
91
+ #!/usr/bin/env bash
92
+ input=$(cat)
93
+
94
+ # Extract content being written
95
+ content=$(echo "$input" | jq -r '.tool_input.content // .tool_input.new_string // ""')
96
+
97
+ # Check for secrets
98
+ if echo "$content" | grep -qE 'api[_-]?key|password|secret'; then
99
+ echo '{"decision":"deny","reason":"Potential secret detected"}' >&2
100
+ exit 2
101
+ fi
102
+
103
+ exit 0
104
+ ```
105
+
106
+ **`.gemini/settings.json`:**
107
+
108
+ ```json
109
+ {
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
+ }
125
+ }
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"
150
+ exit 0
151
+ fi
152
+
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
+
160
+ exit 0
161
+ ```
162
+
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
186
+
187
+ Add relevant project context before each agent interaction.
188
+
189
+ **`.gemini/hooks/inject-context.sh`:**
190
+
191
+ ```bash
192
+ #!/usr/bin/env bash
193
+
194
+ # Get recent git commits for context
195
+ context=$(git log -5 --oneline 2>/dev/null || echo "No git history")
196
+
197
+ # Return as JSON
198
+ cat <<EOF
199
+ {
200
+ "hookSpecificOutput": {
201
+ "hookEventName": "BeforeAgent",
202
+ "additionalContext": "Recent commits:\n$context"
203
+ }
204
+ }
205
+ EOF
206
+ ```
207
+
208
+ **`.gemini/settings.json`:**
209
+
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
231
+
232
+ ### RAG-based tool filtering
233
+
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.
237
+
238
+ This improves:
239
+
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
243
+
244
+ ### Cross-session memory
245
+
246
+ Use `SessionStart` and `SessionEnd` hooks to maintain persistent knowledge
247
+ across sessions:
248
+
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
252
+
253
+ This enables the assistant to learn project conventions, remember important
254
+ decisions, and share knowledge across team members.
255
+
256
+ ### Hook chaining
257
+
258
+ Multiple hooks for the same event run in the order declared. Each hook can build
259
+ upon previous hooks' outputs:
260
+
261
+ ```json
262
+ {
263
+ "hooks": {
264
+ "BeforeAgent": [
265
+ {
266
+ "matcher": "*",
267
+ "hooks": [
268
+ {
269
+ "name": "load-memories",
270
+ "type": "command",
271
+ "command": "./hooks/load-memories.sh"
272
+ },
273
+ {
274
+ "name": "analyze-sentiment",
275
+ "type": "command",
276
+ "command": "./hooks/analyze-sentiment.sh"
277
+ }
278
+ ]
279
+ }
280
+ ]
281
+ }
282
+ }
283
+ ```
284
+
285
+ ## Complete example: Smart Development Workflow Assistant
286
+
287
+ This comprehensive example demonstrates all hook events working together with
288
+ two advanced features:
289
+
290
+ - **RAG-based tool selection:** Reduces 100+ tools to ~15 relevant ones per task
291
+ - **Cross-session memory:** Learns and persists project knowledge
292
+
293
+ ### Architecture
294
+
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
319
+
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`:**
337
+
338
+ ```json
339
+ {
340
+ "hooks": {
341
+ "SessionStart": [
342
+ {
343
+ "matcher": "startup",
344
+ "hooks": [
345
+ {
346
+ "name": "init-assistant",
347
+ "type": "command",
348
+ "command": "node $GEMINI_PROJECT_DIR/.gemini/hooks/init.js",
349
+ "description": "Initialize Smart Workflow Assistant"
350
+ }
351
+ ]
352
+ }
353
+ ],
354
+ "BeforeAgent": [
355
+ {
356
+ "matcher": "*",
357
+ "hooks": [
358
+ {
359
+ "name": "inject-memories",
360
+ "type": "command",
361
+ "command": "node $GEMINI_PROJECT_DIR/.gemini/hooks/inject-memories.js",
362
+ "description": "Inject relevant project memories"
363
+ }
364
+ ]
365
+ }
366
+ ],
367
+ "BeforeToolSelection": [
368
+ {
369
+ "matcher": "*",
370
+ "hooks": [
371
+ {
372
+ "name": "rag-filter",
373
+ "type": "command",
374
+ "command": "node $GEMINI_PROJECT_DIR/.gemini/hooks/rag-filter.js",
375
+ "description": "Filter tools using RAG"
376
+ }
377
+ ]
378
+ }
379
+ ],
380
+ "BeforeTool": [
381
+ {
382
+ "matcher": "write_file|replace",
383
+ "hooks": [
384
+ {
385
+ "name": "security-check",
386
+ "type": "command",
387
+ "command": "node $GEMINI_PROJECT_DIR/.gemini/hooks/security.js",
388
+ "description": "Prevent committing secrets"
389
+ }
390
+ ]
391
+ }
392
+ ],
393
+ "AfterTool": [
394
+ {
395
+ "matcher": "write_file|replace",
396
+ "hooks": [
397
+ {
398
+ "name": "auto-test",
399
+ "type": "command",
400
+ "command": "node $GEMINI_PROJECT_DIR/.gemini/hooks/auto-test.js",
401
+ "description": "Run tests after code changes"
402
+ }
403
+ ]
404
+ }
405
+ ],
406
+ "AfterModel": [
407
+ {
408
+ "matcher": "*",
409
+ "hooks": [
410
+ {
411
+ "name": "record-interaction",
412
+ "type": "command",
413
+ "command": "node $GEMINI_PROJECT_DIR/.gemini/hooks/record.js",
414
+ "description": "Record interaction for learning"
415
+ }
416
+ ]
417
+ }
418
+ ],
419
+ "SessionEnd": [
420
+ {
421
+ "matcher": "exit|logout",
422
+ "hooks": [
423
+ {
424
+ "name": "consolidate-memories",
425
+ "type": "command",
426
+ "command": "node $GEMINI_PROJECT_DIR/.gemini/hooks/consolidate.js",
427
+ "description": "Extract and store session learnings"
428
+ }
429
+ ]
430
+ }
431
+ ]
432
+ }
433
+ }
434
+ ```
435
+
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
+ ```
488
+
489
+ #### 2. Inject memories (BeforeAgent)
490
+
491
+ **`.gemini/hooks/inject-memories.js`:**
492
+
493
+ ```javascript
494
+ #!/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);
560
+ ```
561
+
562
+ #### 3. RAG tool filter (BeforeToolSelection)
563
+
564
+ **`.gemini/hooks/rag-filter.js`:**
565
+
566
+ ```javascript
567
+ #!/usr/bin/env node
568
+ const { GoogleGenerativeAI } = require('@google/generative-ai');
569
+
570
+ 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
+ });
612
+
613
+ console.log(
614
+ JSON.stringify({
615
+ hookSpecificOutput: {
616
+ hookEventName: 'BeforeToolSelection',
617
+ toolConfig: {
618
+ functionCallingConfig: {
619
+ mode: 'ANY',
620
+ allowedFunctionNames: filtered.slice(0, 20),
621
+ },
622
+ },
623
+ },
624
+ systemMessage: `🎯 Filtered ${candidateTools.length} → ${Math.min(filtered.length, 20)} tools`,
625
+ }),
626
+ );
627
+ }
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);
638
+ ```
639
+
640
+ #### 4. Security validation (BeforeTool)
641
+
642
+ **`.gemini/hooks/security.js`:**
643
+
644
+ ```javascript
645
+ #!/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
+ 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;
703
+
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
+ });
751
+ }
752
+
753
+ readStdin().then(main).catch(console.error);
754
+ ```
755
+
756
+ #### 6. Record interaction (AfterModel)
757
+
758
+ **`.gemini/hooks/record.js`:**
759
+
760
+ ```javascript
761
+ #!/usr/bin/env node
762
+ const fs = require('fs');
763
+ const path = require('path');
764
+
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);
813
+ ```
814
+
815
+ #### 7. Consolidate memories (SessionEnd)
816
+
817
+ **`.gemini/hooks/consolidate.js`:**
818
+
819
+ ````javascript
820
+ #!/usr/bin/env node
821
+ const fs = require('fs');
822
+ const path = require('path');
823
+ const { GoogleGenerativeAI } = require('@google/generative-ai');
824
+ const { ChromaClient } = require('chromadb');
825
+
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`,
836
+ );
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
+ });
924
+ }
925
+
926
+ readStdin().then(main).catch(console.error);
927
+ ````
928
+
929
+ ### Example session
930
+
931
+ ```
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
+
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:**
1004
+
1005
+ ```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
+ ];
1019
+ ```
1020
+
1021
+ ## Learn more
1022
+
1023
+ - [Hooks Reference](index.md) - Complete API reference and configuration
1024
+ - [Best Practices](best-practices.md) - Security, performance, and debugging
1025
+ - [Configuration](../cli/configuration.md) - Gemini CLI settings
1026
+ - [Custom Commands](../cli/custom-commands.md) - Create custom commands