@google/gemini-cli-core 0.21.0-nightly.20251218.739c02bd6 → 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.d.ts +0 -6
- package/dist/src/agents/local-executor.js +73 -47
- package/dist/src/agents/local-executor.js.map +1 -1
- package/dist/src/agents/local-executor.test.d.ts +1 -7
- package/dist/src/agents/local-executor.test.js +27 -9
- package/dist/src/agents/local-executor.test.js.map +1 -1
- package/dist/src/agents/registry.d.ts +1 -0
- package/dist/src/agents/registry.js +51 -7
- package/dist/src/agents/registry.js.map +1 -1
- package/dist/src/agents/registry.test.js +112 -1
- package/dist/src/agents/registry.test.js.map +1 -1
- package/dist/src/agents/toml-loader.d.ts +65 -0
- package/dist/src/agents/toml-loader.js +176 -0
- package/dist/src/agents/toml-loader.js.map +1 -0
- package/dist/src/agents/toml-loader.test.d.ts +6 -0
- package/dist/src/agents/toml-loader.test.js +190 -0
- package/dist/src/agents/toml-loader.test.js.map +1 -0
- package/dist/src/availability/modelAvailabilityService.d.ts +2 -1
- package/dist/src/config/config.d.ts +9 -0
- package/dist/src/config/config.js +29 -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/config/storage.d.ts +2 -0
- package/dist/src/config/storage.js +6 -0
- package/dist/src/config/storage.js.map +1 -1
- package/dist/src/config/storage.test.js +8 -0
- package/dist/src/config/storage.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/hookPlanner.js +3 -1
- package/dist/src/hooks/hookPlanner.js.map +1 -1
- package/dist/src/hooks/hookPlanner.test.js +61 -0
- package/dist/src/hooks/hookPlanner.test.js.map +1 -1
- package/dist/src/hooks/hookRegistry.d.ts +1 -1
- package/dist/src/hooks/hookRegistry.js +2 -2
- package/dist/src/hooks/hookRegistry.js.map +1 -1
- package/dist/src/hooks/hookRegistry.test.js +73 -0
- package/dist/src/hooks/hookRegistry.test.js.map +1 -1
- package/dist/src/hooks/hookRunner.js +14 -10
- package/dist/src/hooks/hookRunner.js.map +1 -1
- package/dist/src/hooks/hookRunner.test.js +81 -33
- package/dist/src/hooks/hookRunner.test.js.map +1 -1
- package/dist/src/hooks/types.d.ts +2 -0
- package/dist/src/hooks/types.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/services/shellExecutionService.js +18 -2
- package/dist/src/services/shellExecutionService.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 +14 -0
- package/dist/src/tools/tool-names.js +55 -0
- package/dist/src/tools/tool-names.js.map +1 -1
- package/dist/src/tools/tool-names.test.d.ts +6 -0
- package/dist/src/tools/tool-names.test.js +43 -0
- package/dist/src/tools/tool-names.test.js.map +1 -0
- package/dist/src/tools/tool-registry.d.ts +0 -1
- package/dist/src/tools/tool-registry.js +1 -1
- package/dist/src/tools/tool-registry.js.map +1 -1
- package/dist/src/tools/tool-registry.test.js +2 -1
- package/dist/src/tools/tool-registry.test.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/googleQuotaErrors.js +20 -0
- package/dist/src/utils/googleQuotaErrors.js.map +1 -1
- package/dist/src/utils/googleQuotaErrors.test.js +53 -2
- package/dist/src/utils/googleQuotaErrors.test.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 -6
- package/dist/google-gemini-cli-core-0.21.0-nightly.20251216.bb0c0d8ee.tgz +0 -0
|
@@ -0,0 +1,806 @@
|
|
|
1
|
+
# Hooks on Gemini CLI: Best practices
|
|
2
|
+
|
|
3
|
+
This guide covers security considerations, performance optimization, debugging
|
|
4
|
+
techniques, and privacy considerations for developing and deploying hooks in
|
|
5
|
+
Gemini CLI.
|
|
6
|
+
|
|
7
|
+
## Security considerations
|
|
8
|
+
|
|
9
|
+
### Validate all inputs
|
|
10
|
+
|
|
11
|
+
Never trust data from hooks without validation. Hook inputs may contain
|
|
12
|
+
user-provided data that could be malicious:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
#!/usr/bin/env bash
|
|
16
|
+
input=$(cat)
|
|
17
|
+
|
|
18
|
+
# Validate JSON structure
|
|
19
|
+
if ! echo "$input" | jq empty 2>/dev/null; then
|
|
20
|
+
echo "Invalid JSON input" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Validate required fields
|
|
25
|
+
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
|
|
26
|
+
if [ -z "$tool_name" ]; then
|
|
27
|
+
echo "Missing tool_name field" >&2
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Use timeouts
|
|
33
|
+
|
|
34
|
+
Set reasonable timeouts to prevent hooks from hanging indefinitely:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"hooks": {
|
|
39
|
+
"BeforeTool": [
|
|
40
|
+
{
|
|
41
|
+
"matcher": "*",
|
|
42
|
+
"hooks": [
|
|
43
|
+
{
|
|
44
|
+
"name": "slow-validator",
|
|
45
|
+
"type": "command",
|
|
46
|
+
"command": "./hooks/validate.sh",
|
|
47
|
+
"timeout": 5000
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Recommended timeouts:**
|
|
57
|
+
|
|
58
|
+
- Fast validation: 1000-5000ms
|
|
59
|
+
- Network requests: 10000-30000ms
|
|
60
|
+
- Heavy computation: 30000-60000ms
|
|
61
|
+
|
|
62
|
+
### Limit permissions
|
|
63
|
+
|
|
64
|
+
Run hooks with minimal required permissions:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
#!/usr/bin/env bash
|
|
68
|
+
# Don't run as root
|
|
69
|
+
if [ "$EUID" -eq 0 ]; then
|
|
70
|
+
echo "Hook should not run as root" >&2
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# Check file permissions before writing
|
|
75
|
+
if [ -w "$file_path" ]; then
|
|
76
|
+
# Safe to write
|
|
77
|
+
else
|
|
78
|
+
echo "Insufficient permissions" >&2
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Scan for secrets
|
|
84
|
+
|
|
85
|
+
Use `BeforeTool` hooks to prevent committing sensitive data:
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
const SECRET_PATTERNS = [
|
|
89
|
+
/api[_-]?key\s*[:=]\s*['"]?[a-zA-Z0-9_-]{20,}['"]?/i,
|
|
90
|
+
/password\s*[:=]\s*['"]?[^\s'"]{8,}['"]?/i,
|
|
91
|
+
/secret\s*[:=]\s*['"]?[a-zA-Z0-9_-]{20,}['"]?/i,
|
|
92
|
+
/AKIA[0-9A-Z]{16}/, // AWS access key
|
|
93
|
+
/ghp_[a-zA-Z0-9]{36}/, // GitHub personal access token
|
|
94
|
+
/sk-[a-zA-Z0-9]{48}/, // OpenAI API key
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
function containsSecret(content) {
|
|
98
|
+
return SECRET_PATTERNS.some((pattern) => pattern.test(content));
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Review external scripts
|
|
103
|
+
|
|
104
|
+
Always review hook scripts from untrusted sources before enabling them:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Review before installing
|
|
108
|
+
cat third-party-hook.sh | less
|
|
109
|
+
|
|
110
|
+
# Check for suspicious patterns
|
|
111
|
+
grep -E 'curl|wget|ssh|eval' third-party-hook.sh
|
|
112
|
+
|
|
113
|
+
# Verify hook source
|
|
114
|
+
ls -la third-party-hook.sh
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Sandbox untrusted hooks
|
|
118
|
+
|
|
119
|
+
For maximum security, consider running untrusted hooks in isolated environments:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Run hook in Docker container
|
|
123
|
+
docker run --rm \
|
|
124
|
+
-v "$GEMINI_PROJECT_DIR:/workspace:ro" \
|
|
125
|
+
-i untrusted-hook-image \
|
|
126
|
+
/hook-script.sh < input.json
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Performance
|
|
130
|
+
|
|
131
|
+
### Keep hooks fast
|
|
132
|
+
|
|
133
|
+
Hooks run synchronously—slow hooks delay the agent loop. Optimize for speed by
|
|
134
|
+
using parallel operations:
|
|
135
|
+
|
|
136
|
+
```javascript
|
|
137
|
+
// Sequential operations are slower
|
|
138
|
+
const data1 = await fetch(url1).then((r) => r.json());
|
|
139
|
+
const data2 = await fetch(url2).then((r) => r.json());
|
|
140
|
+
const data3 = await fetch(url3).then((r) => r.json());
|
|
141
|
+
|
|
142
|
+
// Prefer parallel operations for better performance
|
|
143
|
+
const [data1, data2, data3] = await Promise.all([
|
|
144
|
+
fetch(url1).then((r) => r.json()),
|
|
145
|
+
fetch(url2).then((r) => r.json()),
|
|
146
|
+
fetch(url3).then((r) => r.json()),
|
|
147
|
+
]);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Cache expensive operations
|
|
151
|
+
|
|
152
|
+
Store results between invocations to avoid repeated computation:
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
const fs = require('fs');
|
|
156
|
+
const path = require('path');
|
|
157
|
+
|
|
158
|
+
const CACHE_FILE = '.gemini/hook-cache.json';
|
|
159
|
+
|
|
160
|
+
function readCache() {
|
|
161
|
+
try {
|
|
162
|
+
return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
|
|
163
|
+
} catch {
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function writeCache(data) {
|
|
169
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify(data, null, 2));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function main() {
|
|
173
|
+
const cache = readCache();
|
|
174
|
+
const cacheKey = `tool-list-${(Date.now() / 3600000) | 0}`; // Hourly cache
|
|
175
|
+
|
|
176
|
+
if (cache[cacheKey]) {
|
|
177
|
+
console.log(JSON.stringify(cache[cacheKey]));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Expensive operation
|
|
182
|
+
const result = await computeExpensiveResult();
|
|
183
|
+
cache[cacheKey] = result;
|
|
184
|
+
writeCache(cache);
|
|
185
|
+
|
|
186
|
+
console.log(JSON.stringify(result));
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Use appropriate events
|
|
191
|
+
|
|
192
|
+
Choose hook events that match your use case to avoid unnecessary execution.
|
|
193
|
+
`AfterAgent` fires once per agent loop completion, while `AfterModel` fires
|
|
194
|
+
after every LLM call (potentially multiple times per loop):
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
// If checking final completion, use AfterAgent instead of AfterModel
|
|
198
|
+
{
|
|
199
|
+
"hooks": {
|
|
200
|
+
"AfterAgent": [
|
|
201
|
+
{
|
|
202
|
+
"matcher": "*",
|
|
203
|
+
"hooks": [
|
|
204
|
+
{
|
|
205
|
+
"name": "final-checker",
|
|
206
|
+
"command": "./check-completion.sh"
|
|
207
|
+
}
|
|
208
|
+
]
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Filter with matchers
|
|
216
|
+
|
|
217
|
+
Use specific matchers to avoid unnecessary hook execution. Instead of matching
|
|
218
|
+
all tools with `*`, specify only the tools you need:
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{
|
|
222
|
+
"matcher": "write_file|replace",
|
|
223
|
+
"hooks": [
|
|
224
|
+
{
|
|
225
|
+
"name": "validate-writes",
|
|
226
|
+
"command": "./validate.sh"
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Optimize JSON parsing
|
|
233
|
+
|
|
234
|
+
For large inputs, use streaming JSON parsers to avoid loading everything into
|
|
235
|
+
memory:
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
// Standard approach: parse entire input
|
|
239
|
+
const input = JSON.parse(await readStdin());
|
|
240
|
+
const content = input.tool_input.content;
|
|
241
|
+
|
|
242
|
+
// For very large inputs: stream and extract only needed fields
|
|
243
|
+
const { createReadStream } = require('fs');
|
|
244
|
+
const JSONStream = require('JSONStream');
|
|
245
|
+
|
|
246
|
+
const stream = createReadStream(0).pipe(JSONStream.parse('tool_input.content'));
|
|
247
|
+
let content = '';
|
|
248
|
+
stream.on('data', (chunk) => {
|
|
249
|
+
content += chunk;
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Debugging
|
|
254
|
+
|
|
255
|
+
### Log to files
|
|
256
|
+
|
|
257
|
+
Write debug information to dedicated log files:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
#!/usr/bin/env bash
|
|
261
|
+
LOG_FILE=".gemini/hooks/debug.log"
|
|
262
|
+
|
|
263
|
+
# Log with timestamp
|
|
264
|
+
log() {
|
|
265
|
+
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
input=$(cat)
|
|
269
|
+
log "Received input: ${input:0:100}..."
|
|
270
|
+
|
|
271
|
+
# Hook logic here
|
|
272
|
+
|
|
273
|
+
log "Hook completed successfully"
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Use stderr for errors
|
|
277
|
+
|
|
278
|
+
Error messages on stderr are surfaced appropriately based on exit codes:
|
|
279
|
+
|
|
280
|
+
```javascript
|
|
281
|
+
try {
|
|
282
|
+
const result = dangerousOperation();
|
|
283
|
+
console.log(JSON.stringify({ result }));
|
|
284
|
+
} catch (error) {
|
|
285
|
+
console.error(`Hook error: ${error.message}`);
|
|
286
|
+
process.exit(2); // Blocking error
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Test hooks independently
|
|
291
|
+
|
|
292
|
+
Run hook scripts manually with sample JSON input:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
# Create test input
|
|
296
|
+
cat > test-input.json << 'EOF'
|
|
297
|
+
{
|
|
298
|
+
"session_id": "test-123",
|
|
299
|
+
"cwd": "/tmp/test",
|
|
300
|
+
"hook_event_name": "BeforeTool",
|
|
301
|
+
"tool_name": "write_file",
|
|
302
|
+
"tool_input": {
|
|
303
|
+
"file_path": "test.txt",
|
|
304
|
+
"content": "Test content"
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
EOF
|
|
308
|
+
|
|
309
|
+
# Test the hook
|
|
310
|
+
cat test-input.json | .gemini/hooks/my-hook.sh
|
|
311
|
+
|
|
312
|
+
# Check exit code
|
|
313
|
+
echo "Exit code: $?"
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Check exit codes
|
|
317
|
+
|
|
318
|
+
Ensure your script returns the correct exit code:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
#!/usr/bin/env bash
|
|
322
|
+
set -e # Exit on error
|
|
323
|
+
|
|
324
|
+
# Hook logic
|
|
325
|
+
process_input() {
|
|
326
|
+
# ...
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if process_input; then
|
|
330
|
+
echo "Success message"
|
|
331
|
+
exit 0
|
|
332
|
+
else
|
|
333
|
+
echo "Error message" >&2
|
|
334
|
+
exit 2
|
|
335
|
+
fi
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Enable telemetry
|
|
339
|
+
|
|
340
|
+
Hook execution is logged when `telemetry.logPrompts` is enabled:
|
|
341
|
+
|
|
342
|
+
```json
|
|
343
|
+
{
|
|
344
|
+
"telemetry": {
|
|
345
|
+
"logPrompts": true
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
View hook telemetry in logs to debug execution issues.
|
|
351
|
+
|
|
352
|
+
### Use hook panel
|
|
353
|
+
|
|
354
|
+
The `/hooks panel` command shows execution status and recent output:
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
/hooks panel
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Check for:
|
|
361
|
+
|
|
362
|
+
- Hook execution counts
|
|
363
|
+
- Recent successes/failures
|
|
364
|
+
- Error messages
|
|
365
|
+
- Execution timing
|
|
366
|
+
|
|
367
|
+
## Development
|
|
368
|
+
|
|
369
|
+
### Start simple
|
|
370
|
+
|
|
371
|
+
Begin with basic logging hooks before implementing complex logic:
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
#!/usr/bin/env bash
|
|
375
|
+
# Simple logging hook to understand input structure
|
|
376
|
+
input=$(cat)
|
|
377
|
+
echo "$input" >> .gemini/hook-inputs.log
|
|
378
|
+
echo "Logged input"
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Use JSON libraries
|
|
382
|
+
|
|
383
|
+
Parse JSON with proper libraries instead of text processing:
|
|
384
|
+
|
|
385
|
+
**Bad:**
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
# Fragile text parsing
|
|
389
|
+
tool_name=$(echo "$input" | grep -oP '"tool_name":\s*"\K[^"]+')
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**Good:**
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
# Robust JSON parsing
|
|
396
|
+
tool_name=$(echo "$input" | jq -r '.tool_name')
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Make scripts executable
|
|
400
|
+
|
|
401
|
+
Always make hook scripts executable:
|
|
402
|
+
|
|
403
|
+
```bash
|
|
404
|
+
chmod +x .gemini/hooks/*.sh
|
|
405
|
+
chmod +x .gemini/hooks/*.js
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Version control
|
|
409
|
+
|
|
410
|
+
Commit hooks to share with your team:
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
git add .gemini/hooks/
|
|
414
|
+
git add .gemini/settings.json
|
|
415
|
+
git commit -m "Add project hooks for security and testing"
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
**`.gitignore` considerations:**
|
|
419
|
+
|
|
420
|
+
```gitignore
|
|
421
|
+
# Ignore hook cache and logs
|
|
422
|
+
.gemini/hook-cache.json
|
|
423
|
+
.gemini/hook-debug.log
|
|
424
|
+
.gemini/memory/session-*.jsonl
|
|
425
|
+
|
|
426
|
+
# Keep hook scripts
|
|
427
|
+
!.gemini/hooks/*.sh
|
|
428
|
+
!.gemini/hooks/*.js
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Document behavior
|
|
432
|
+
|
|
433
|
+
Add descriptions to help others understand your hooks:
|
|
434
|
+
|
|
435
|
+
```json
|
|
436
|
+
{
|
|
437
|
+
"hooks": {
|
|
438
|
+
"BeforeTool": [
|
|
439
|
+
{
|
|
440
|
+
"matcher": "write_file|replace",
|
|
441
|
+
"hooks": [
|
|
442
|
+
{
|
|
443
|
+
"name": "secret-scanner",
|
|
444
|
+
"type": "command",
|
|
445
|
+
"command": "$GEMINI_PROJECT_DIR/.gemini/hooks/block-secrets.sh",
|
|
446
|
+
"description": "Scans code changes for API keys, passwords, and other secrets before writing"
|
|
447
|
+
}
|
|
448
|
+
]
|
|
449
|
+
}
|
|
450
|
+
]
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Add comments in hook scripts:
|
|
456
|
+
|
|
457
|
+
```javascript
|
|
458
|
+
#!/usr/bin/env node
|
|
459
|
+
/**
|
|
460
|
+
* RAG Tool Filter Hook
|
|
461
|
+
*
|
|
462
|
+
* This hook reduces the tool space from 100+ tools to ~15 relevant ones
|
|
463
|
+
* by extracting keywords from the user's request and filtering tools
|
|
464
|
+
* based on semantic similarity.
|
|
465
|
+
*
|
|
466
|
+
* Performance: ~500ms average, cached tool embeddings
|
|
467
|
+
* Dependencies: @google/generative-ai
|
|
468
|
+
*/
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
## Troubleshooting
|
|
472
|
+
|
|
473
|
+
### Hook not executing
|
|
474
|
+
|
|
475
|
+
**Check hook name in `/hooks panel`:**
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
/hooks panel
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Verify the hook appears in the list and is enabled.
|
|
482
|
+
|
|
483
|
+
**Verify matcher pattern:**
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
# Test regex pattern
|
|
487
|
+
echo "write_file|replace" | grep -E "write_.*|replace"
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
**Check disabled list:**
|
|
491
|
+
|
|
492
|
+
```json
|
|
493
|
+
{
|
|
494
|
+
"hooks": {
|
|
495
|
+
"disabled": ["my-hook-name"]
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
**Ensure script is executable:**
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
ls -la .gemini/hooks/my-hook.sh
|
|
504
|
+
chmod +x .gemini/hooks/my-hook.sh
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
**Verify script path:**
|
|
508
|
+
|
|
509
|
+
```bash
|
|
510
|
+
# Check path expansion
|
|
511
|
+
echo "$GEMINI_PROJECT_DIR/.gemini/hooks/my-hook.sh"
|
|
512
|
+
|
|
513
|
+
# Verify file exists
|
|
514
|
+
test -f "$GEMINI_PROJECT_DIR/.gemini/hooks/my-hook.sh" && echo "File exists"
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### Hook timing out
|
|
518
|
+
|
|
519
|
+
**Check configured timeout:**
|
|
520
|
+
|
|
521
|
+
```json
|
|
522
|
+
{
|
|
523
|
+
"name": "slow-hook",
|
|
524
|
+
"timeout": 60000
|
|
525
|
+
}
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
**Optimize slow operations:**
|
|
529
|
+
|
|
530
|
+
```javascript
|
|
531
|
+
// Before: Sequential operations (slow)
|
|
532
|
+
for (const item of items) {
|
|
533
|
+
await processItem(item);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// After: Parallel operations (fast)
|
|
537
|
+
await Promise.all(items.map((item) => processItem(item)));
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
**Use caching:**
|
|
541
|
+
|
|
542
|
+
```javascript
|
|
543
|
+
const cache = new Map();
|
|
544
|
+
|
|
545
|
+
async function getCachedData(key) {
|
|
546
|
+
if (cache.has(key)) {
|
|
547
|
+
return cache.get(key);
|
|
548
|
+
}
|
|
549
|
+
const data = await fetchData(key);
|
|
550
|
+
cache.set(key, data);
|
|
551
|
+
return data;
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
**Consider splitting into multiple faster hooks:**
|
|
556
|
+
|
|
557
|
+
```json
|
|
558
|
+
{
|
|
559
|
+
"hooks": {
|
|
560
|
+
"BeforeTool": [
|
|
561
|
+
{
|
|
562
|
+
"matcher": "write_file",
|
|
563
|
+
"hooks": [
|
|
564
|
+
{
|
|
565
|
+
"name": "quick-check",
|
|
566
|
+
"command": "./quick-validation.sh",
|
|
567
|
+
"timeout": 1000
|
|
568
|
+
}
|
|
569
|
+
]
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
"matcher": "write_file",
|
|
573
|
+
"hooks": [
|
|
574
|
+
{
|
|
575
|
+
"name": "deep-check",
|
|
576
|
+
"command": "./deep-analysis.sh",
|
|
577
|
+
"timeout": 30000
|
|
578
|
+
}
|
|
579
|
+
]
|
|
580
|
+
}
|
|
581
|
+
]
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### Invalid JSON output
|
|
587
|
+
|
|
588
|
+
**Validate JSON before outputting:**
|
|
589
|
+
|
|
590
|
+
```bash
|
|
591
|
+
#!/usr/bin/env bash
|
|
592
|
+
output='{"decision": "allow"}'
|
|
593
|
+
|
|
594
|
+
# Validate JSON
|
|
595
|
+
if echo "$output" | jq empty 2>/dev/null; then
|
|
596
|
+
echo "$output"
|
|
597
|
+
else
|
|
598
|
+
echo "Invalid JSON generated" >&2
|
|
599
|
+
exit 1
|
|
600
|
+
fi
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
**Ensure proper quoting and escaping:**
|
|
604
|
+
|
|
605
|
+
```javascript
|
|
606
|
+
// Bad: Unescaped string interpolation
|
|
607
|
+
const message = `User said: ${userInput}`;
|
|
608
|
+
console.log(JSON.stringify({ message }));
|
|
609
|
+
|
|
610
|
+
// Good: Automatic escaping
|
|
611
|
+
console.log(JSON.stringify({ message: `User said: ${userInput}` }));
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**Check for binary data or control characters:**
|
|
615
|
+
|
|
616
|
+
```javascript
|
|
617
|
+
function sanitizeForJSON(str) {
|
|
618
|
+
return str.replace(/[\x00-\x1F\x7F-\x9F]/g, ''); // Remove control chars
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const cleanContent = sanitizeForJSON(content);
|
|
622
|
+
console.log(JSON.stringify({ content: cleanContent }));
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
### Exit code issues
|
|
626
|
+
|
|
627
|
+
**Verify script returns correct codes:**
|
|
628
|
+
|
|
629
|
+
```bash
|
|
630
|
+
#!/usr/bin/env bash
|
|
631
|
+
set -e # Exit on error
|
|
632
|
+
|
|
633
|
+
# Processing logic
|
|
634
|
+
if validate_input; then
|
|
635
|
+
echo "Success"
|
|
636
|
+
exit 0
|
|
637
|
+
else
|
|
638
|
+
echo "Validation failed" >&2
|
|
639
|
+
exit 2
|
|
640
|
+
fi
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
**Check for unintended errors:**
|
|
644
|
+
|
|
645
|
+
```bash
|
|
646
|
+
#!/usr/bin/env bash
|
|
647
|
+
# Don't use 'set -e' if you want to handle errors explicitly
|
|
648
|
+
# set -e
|
|
649
|
+
|
|
650
|
+
if ! command_that_might_fail; then
|
|
651
|
+
# Handle error
|
|
652
|
+
echo "Command failed but continuing" >&2
|
|
653
|
+
fi
|
|
654
|
+
|
|
655
|
+
# Always exit explicitly
|
|
656
|
+
exit 0
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
**Use trap for cleanup:**
|
|
660
|
+
|
|
661
|
+
```bash
|
|
662
|
+
#!/usr/bin/env bash
|
|
663
|
+
|
|
664
|
+
cleanup() {
|
|
665
|
+
# Cleanup logic
|
|
666
|
+
rm -f /tmp/hook-temp-*
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
trap cleanup EXIT
|
|
670
|
+
|
|
671
|
+
# Hook logic here
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
### Environment variables not available
|
|
675
|
+
|
|
676
|
+
**Check if variable is set:**
|
|
677
|
+
|
|
678
|
+
```bash
|
|
679
|
+
#!/usr/bin/env bash
|
|
680
|
+
|
|
681
|
+
if [ -z "$GEMINI_PROJECT_DIR" ]; then
|
|
682
|
+
echo "GEMINI_PROJECT_DIR not set" >&2
|
|
683
|
+
exit 1
|
|
684
|
+
fi
|
|
685
|
+
|
|
686
|
+
if [ -z "$CUSTOM_VAR" ]; then
|
|
687
|
+
echo "Warning: CUSTOM_VAR not set, using default" >&2
|
|
688
|
+
CUSTOM_VAR="default-value"
|
|
689
|
+
fi
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
**Debug available variables:**
|
|
693
|
+
|
|
694
|
+
```bash
|
|
695
|
+
#!/usr/bin/env bash
|
|
696
|
+
|
|
697
|
+
# List all environment variables
|
|
698
|
+
env > .gemini/hook-env.log
|
|
699
|
+
|
|
700
|
+
# Check specific variables
|
|
701
|
+
echo "GEMINI_PROJECT_DIR: $GEMINI_PROJECT_DIR" >> .gemini/hook-env.log
|
|
702
|
+
echo "GEMINI_SESSION_ID: $GEMINI_SESSION_ID" >> .gemini/hook-env.log
|
|
703
|
+
echo "GEMINI_API_KEY: ${GEMINI_API_KEY:+<set>}" >> .gemini/hook-env.log
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
**Use .env files:**
|
|
707
|
+
|
|
708
|
+
```bash
|
|
709
|
+
#!/usr/bin/env bash
|
|
710
|
+
|
|
711
|
+
# Load .env file if it exists
|
|
712
|
+
if [ -f "$GEMINI_PROJECT_DIR/.env" ]; then
|
|
713
|
+
source "$GEMINI_PROJECT_DIR/.env"
|
|
714
|
+
fi
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
## Privacy considerations
|
|
718
|
+
|
|
719
|
+
Hook inputs and outputs may contain sensitive information. Gemini CLI respects
|
|
720
|
+
the `telemetry.logPrompts` setting for hook data logging.
|
|
721
|
+
|
|
722
|
+
### What data is collected
|
|
723
|
+
|
|
724
|
+
Hook telemetry may include:
|
|
725
|
+
|
|
726
|
+
- **Hook inputs:** User prompts, tool arguments, file contents
|
|
727
|
+
- **Hook outputs:** Hook responses, decision reasons, added context
|
|
728
|
+
- **Standard streams:** stdout and stderr from hook processes
|
|
729
|
+
- **Execution metadata:** Hook name, event type, duration, success/failure
|
|
730
|
+
|
|
731
|
+
### Privacy settings
|
|
732
|
+
|
|
733
|
+
**Enabled (default):**
|
|
734
|
+
|
|
735
|
+
Full hook I/O is logged to telemetry. Use this when:
|
|
736
|
+
|
|
737
|
+
- Developing and debugging hooks
|
|
738
|
+
- Telemetry is redirected to a trusted enterprise system
|
|
739
|
+
- You understand and accept the privacy implications
|
|
740
|
+
|
|
741
|
+
**Disabled:**
|
|
742
|
+
|
|
743
|
+
Only metadata is logged (event name, duration, success/failure). Hook inputs and
|
|
744
|
+
outputs are excluded. Use this when:
|
|
745
|
+
|
|
746
|
+
- Sending telemetry to third-party systems
|
|
747
|
+
- Working with sensitive data
|
|
748
|
+
- Privacy regulations require minimizing data collection
|
|
749
|
+
|
|
750
|
+
### Configuration
|
|
751
|
+
|
|
752
|
+
**Disable PII logging in settings:**
|
|
753
|
+
|
|
754
|
+
```json
|
|
755
|
+
{
|
|
756
|
+
"telemetry": {
|
|
757
|
+
"logPrompts": false
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
**Disable via environment variable:**
|
|
763
|
+
|
|
764
|
+
```bash
|
|
765
|
+
export GEMINI_TELEMETRY_LOG_PROMPTS=false
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
### Sensitive data in hooks
|
|
769
|
+
|
|
770
|
+
If your hooks process sensitive data:
|
|
771
|
+
|
|
772
|
+
1. **Minimize logging:** Don't write sensitive data to log files
|
|
773
|
+
2. **Sanitize outputs:** Remove sensitive data before outputting
|
|
774
|
+
3. **Use secure storage:** Encrypt sensitive data at rest
|
|
775
|
+
4. **Limit access:** Restrict hook script permissions
|
|
776
|
+
|
|
777
|
+
**Example sanitization:**
|
|
778
|
+
|
|
779
|
+
```javascript
|
|
780
|
+
function sanitizeOutput(data) {
|
|
781
|
+
const sanitized = { ...data };
|
|
782
|
+
|
|
783
|
+
// Remove sensitive fields
|
|
784
|
+
delete sanitized.apiKey;
|
|
785
|
+
delete sanitized.password;
|
|
786
|
+
|
|
787
|
+
// Redact sensitive strings
|
|
788
|
+
if (sanitized.content) {
|
|
789
|
+
sanitized.content = sanitized.content.replace(
|
|
790
|
+
/api[_-]?key\s*[:=]\s*['"]?[a-zA-Z0-9_-]{20,}['"]?/gi,
|
|
791
|
+
'[REDACTED]',
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
return sanitized;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
console.log(JSON.stringify(sanitizeOutput(hookOutput)));
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
## Learn more
|
|
802
|
+
|
|
803
|
+
- [Hooks Reference](index.md) - Complete API reference
|
|
804
|
+
- [Writing Hooks](writing-hooks.md) - Tutorial and examples
|
|
805
|
+
- [Configuration](../cli/configuration.md) - Gemini CLI settings
|
|
806
|
+
- [Hooks Design Document](../hooks-design.md) - Technical architecture
|