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