@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.
- package/dist/docs/CONTRIBUTING.md +546 -0
- package/dist/docs/architecture.md +80 -0
- package/dist/docs/assets/connected_devtools.png +0 -0
- package/dist/docs/assets/gemini-screenshot.png +0 -0
- package/dist/docs/assets/release_patch.png +0 -0
- package/dist/docs/assets/theme-ansi-light.png +0 -0
- package/dist/docs/assets/theme-ansi.png +0 -0
- package/dist/docs/assets/theme-atom-one.png +0 -0
- package/dist/docs/assets/theme-ayu-light.png +0 -0
- package/dist/docs/assets/theme-ayu.png +0 -0
- package/dist/docs/assets/theme-custom.png +0 -0
- package/dist/docs/assets/theme-default-light.png +0 -0
- package/dist/docs/assets/theme-default.png +0 -0
- package/dist/docs/assets/theme-dracula.png +0 -0
- package/dist/docs/assets/theme-github-light.png +0 -0
- package/dist/docs/assets/theme-github.png +0 -0
- package/dist/docs/assets/theme-google-light.png +0 -0
- package/dist/docs/assets/theme-xcode-light.png +0 -0
- package/dist/docs/changelogs/index.md +592 -0
- package/dist/docs/changelogs/latest.md +225 -0
- package/dist/docs/changelogs/preview.md +129 -0
- package/dist/docs/changelogs/releases.md +896 -0
- package/dist/docs/cli/authentication.md +3 -0
- package/dist/docs/cli/checkpointing.md +94 -0
- package/dist/docs/cli/commands.md +354 -0
- package/dist/docs/cli/configuration.md +780 -0
- package/dist/docs/cli/custom-commands.md +315 -0
- package/dist/docs/cli/enterprise.md +565 -0
- package/dist/docs/cli/gemini-ignore.md +71 -0
- package/dist/docs/cli/gemini-md.md +108 -0
- package/dist/docs/cli/generation-settings.md +210 -0
- package/dist/docs/cli/headless.md +388 -0
- package/dist/docs/cli/index.md +63 -0
- package/dist/docs/cli/keyboard-shortcuts.md +143 -0
- package/dist/docs/cli/model-routing.md +37 -0
- package/dist/docs/cli/model.md +62 -0
- package/dist/docs/cli/sandbox.md +171 -0
- package/dist/docs/cli/session-management.md +158 -0
- package/dist/docs/cli/settings.md +112 -0
- package/dist/docs/cli/system-prompt.md +93 -0
- package/dist/docs/cli/telemetry.md +791 -0
- package/dist/docs/cli/themes.md +237 -0
- package/dist/docs/cli/token-caching.md +20 -0
- package/dist/docs/cli/trusted-folders.md +95 -0
- package/dist/docs/cli/tutorials.md +83 -0
- package/dist/docs/cli/uninstall.md +47 -0
- package/dist/docs/core/index.md +101 -0
- package/dist/docs/core/memport.md +244 -0
- package/dist/docs/core/policy-engine.md +267 -0
- package/dist/docs/core/tools-api.md +131 -0
- package/dist/docs/examples/proxy-script.md +83 -0
- package/dist/docs/extensions/extension-releasing.md +183 -0
- package/dist/docs/extensions/getting-started-extensions.md +245 -0
- package/dist/docs/extensions/index.md +293 -0
- package/dist/docs/faq.md +154 -0
- package/dist/docs/get-started/authentication.md +321 -0
- package/dist/docs/get-started/configuration-v1.md +888 -0
- package/dist/docs/get-started/configuration.md +1444 -0
- package/dist/docs/get-started/deployment.md +143 -0
- package/dist/docs/get-started/examples.md +219 -0
- package/dist/docs/get-started/gemini-3.md +116 -0
- package/dist/docs/get-started/index.md +71 -0
- package/dist/docs/get-started/installation.md +141 -0
- package/dist/docs/hooks/best-practices.md +806 -0
- package/dist/docs/hooks/index.md +665 -0
- package/dist/docs/hooks/reference.md +168 -0
- package/dist/docs/hooks/writing-hooks.md +1026 -0
- package/dist/docs/ide-integration/ide-companion-spec.md +267 -0
- package/dist/docs/ide-integration/index.md +202 -0
- package/dist/docs/index.md +147 -0
- package/dist/docs/integration-tests.md +211 -0
- package/dist/docs/issue-and-pr-automation.md +134 -0
- package/dist/docs/local-development.md +128 -0
- package/dist/docs/mermaid/context.mmd +103 -0
- package/dist/docs/mermaid/render-path.mmd +64 -0
- package/dist/docs/npm.md +62 -0
- package/dist/docs/quota-and-pricing.md +158 -0
- package/dist/docs/release-confidence.md +164 -0
- package/dist/docs/releases.md +540 -0
- package/dist/docs/sidebar.json +297 -0
- package/dist/docs/tools/file-system.md +217 -0
- package/dist/docs/tools/index.md +95 -0
- package/dist/docs/tools/mcp-server.md +1044 -0
- package/dist/docs/tools/memory.md +54 -0
- package/dist/docs/tools/shell.md +260 -0
- package/dist/docs/tools/todos.md +57 -0
- package/dist/docs/tools/web-fetch.md +59 -0
- package/dist/docs/tools/web-search.md +42 -0
- package/dist/docs/tos-privacy.md +96 -0
- package/dist/docs/troubleshooting.md +158 -0
- package/dist/google-gemini-cli-core-0.21.0-nightly.20251219.70696e364.tgz +0 -0
- package/dist/src/agents/delegate-to-agent-tool.test.js +1 -0
- package/dist/src/agents/delegate-to-agent-tool.test.js.map +1 -1
- package/dist/src/agents/introspection-agent.d.ts +23 -0
- package/dist/src/agents/introspection-agent.js +72 -0
- package/dist/src/agents/introspection-agent.js.map +1 -0
- package/dist/src/agents/introspection-agent.test.d.ts +6 -0
- package/dist/src/agents/introspection-agent.test.js +47 -0
- package/dist/src/agents/introspection-agent.test.js.map +1 -0
- package/dist/src/agents/local-executor.js +14 -12
- package/dist/src/agents/local-executor.js.map +1 -1
- package/dist/src/agents/local-executor.test.js +3 -0
- package/dist/src/agents/local-executor.test.js.map +1 -1
- package/dist/src/agents/registry.js +6 -0
- package/dist/src/agents/registry.js.map +1 -1
- package/dist/src/agents/registry.test.js +16 -0
- package/dist/src/agents/registry.test.js.map +1 -1
- package/dist/src/config/config.d.ts +6 -0
- package/dist/src/config/config.js +22 -0
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +59 -1
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/core/client.js +8 -4
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +20 -0
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/clientHookTriggers.js +2 -2
- package/dist/src/core/clientHookTriggers.js.map +1 -1
- package/dist/src/core/coreToolHookTriggers.js +3 -3
- package/dist/src/core/coreToolHookTriggers.js.map +1 -1
- package/dist/src/core/geminiChatHookTriggers.js +3 -3
- package/dist/src/core/geminiChatHookTriggers.js.map +1 -1
- package/dist/src/core/sessionHookTriggers.js +3 -3
- package/dist/src/core/sessionHookTriggers.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/hooks/hookEventHandler.js +10 -4
- package/dist/src/hooks/hookEventHandler.js.map +1 -1
- package/dist/src/hooks/hookEventHandler.test.js +40 -0
- package/dist/src/hooks/hookEventHandler.test.js.map +1 -1
- package/dist/src/hooks/hookRunner.js +12 -8
- package/dist/src/hooks/hookRunner.js.map +1 -1
- package/dist/src/hooks/hookRunner.test.js +58 -33
- package/dist/src/hooks/hookRunner.test.js.map +1 -1
- package/dist/src/mcp/oauth-provider.js +6 -2
- package/dist/src/mcp/oauth-provider.js.map +1 -1
- package/dist/src/mcp/oauth-provider.test.js +4 -1
- package/dist/src/mcp/oauth-provider.test.js.map +1 -1
- package/dist/src/mcp/oauth-utils.d.ts +8 -1
- package/dist/src/mcp/oauth-utils.js +30 -1
- package/dist/src/mcp/oauth-utils.js.map +1 -1
- package/dist/src/mcp/oauth-utils.test.js +42 -0
- package/dist/src/mcp/oauth-utils.test.js.map +1 -1
- package/dist/src/services/contextManager.d.ts +5 -11
- package/dist/src/services/contextManager.js +20 -17
- package/dist/src/services/contextManager.js.map +1 -1
- package/dist/src/services/contextManager.test.js +40 -41
- package/dist/src/services/contextManager.test.js.map +1 -1
- package/dist/src/tools/get-internal-docs.d.ts +27 -0
- package/dist/src/tools/get-internal-docs.js +129 -0
- package/dist/src/tools/get-internal-docs.js.map +1 -0
- package/dist/src/tools/get-internal-docs.test.d.ts +6 -0
- package/dist/src/tools/get-internal-docs.test.js +56 -0
- package/dist/src/tools/get-internal-docs.test.js.map +1 -0
- package/dist/src/tools/tool-names.d.ts +1 -0
- package/dist/src/tools/tool-names.js +1 -0
- package/dist/src/tools/tool-names.js.map +1 -1
- package/dist/src/utils/environmentContext.js +3 -0
- package/dist/src/utils/environmentContext.js.map +1 -1
- package/dist/src/utils/environmentContext.test.js +2 -0
- package/dist/src/utils/environmentContext.test.js.map +1 -1
- package/dist/src/utils/events.d.ts +3 -2
- package/dist/src/utils/events.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.js +1 -1
- package/dist/src/utils/memoryDiscovery.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.test.js +3 -1
- package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
- package/dist/src/utils/shell-utils.js +25 -4
- package/dist/src/utils/shell-utils.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- 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
|