monoco-toolkit 0.3.11__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. monoco/core/automation/__init__.py +40 -0
  2. monoco/core/automation/field_watcher.py +296 -0
  3. monoco/core/automation/handlers.py +805 -0
  4. monoco/core/config.py +29 -11
  5. monoco/core/daemon/__init__.py +5 -0
  6. monoco/core/daemon/pid.py +290 -0
  7. monoco/core/git.py +15 -0
  8. monoco/core/hooks/context.py +74 -13
  9. monoco/core/injection.py +86 -8
  10. monoco/core/integrations.py +0 -24
  11. monoco/core/router/__init__.py +17 -0
  12. monoco/core/router/action.py +202 -0
  13. monoco/core/scheduler/__init__.py +63 -0
  14. monoco/core/scheduler/base.py +152 -0
  15. monoco/core/scheduler/engines.py +175 -0
  16. monoco/core/scheduler/events.py +197 -0
  17. monoco/core/scheduler/local.py +377 -0
  18. monoco/core/setup.py +9 -0
  19. monoco/core/sync.py +199 -4
  20. monoco/core/watcher/__init__.py +63 -0
  21. monoco/core/watcher/base.py +382 -0
  22. monoco/core/watcher/dropzone.py +152 -0
  23. monoco/core/watcher/im.py +460 -0
  24. monoco/core/watcher/issue.py +303 -0
  25. monoco/core/watcher/memo.py +192 -0
  26. monoco/core/watcher/task.py +238 -0
  27. monoco/daemon/app.py +3 -60
  28. monoco/daemon/commands.py +459 -25
  29. monoco/daemon/events.py +34 -0
  30. monoco/daemon/scheduler.py +157 -201
  31. monoco/daemon/services.py +42 -243
  32. monoco/features/agent/__init__.py +25 -7
  33. monoco/features/agent/cli.py +91 -57
  34. monoco/features/agent/engines.py +31 -170
  35. monoco/features/agent/resources/en/AGENTS.md +14 -14
  36. monoco/features/agent/resources/en/skills/monoco_role_engineer/SKILL.md +101 -0
  37. monoco/features/agent/resources/en/skills/monoco_role_manager/SKILL.md +95 -0
  38. monoco/features/agent/resources/en/skills/monoco_role_planner/SKILL.md +177 -0
  39. monoco/features/agent/resources/en/skills/monoco_role_reviewer/SKILL.md +139 -0
  40. monoco/features/agent/resources/zh/skills/monoco_role_engineer/SKILL.md +101 -0
  41. monoco/features/agent/resources/zh/skills/monoco_role_manager/SKILL.md +95 -0
  42. monoco/features/agent/resources/zh/skills/monoco_role_planner/SKILL.md +177 -0
  43. monoco/features/agent/resources/zh/skills/monoco_role_reviewer/SKILL.md +139 -0
  44. monoco/features/agent/worker.py +1 -1
  45. monoco/features/hooks/__init__.py +61 -6
  46. monoco/features/hooks/commands.py +281 -271
  47. monoco/features/hooks/dispatchers/__init__.py +23 -0
  48. monoco/features/hooks/dispatchers/agent_dispatcher.py +486 -0
  49. monoco/features/hooks/dispatchers/git_dispatcher.py +478 -0
  50. monoco/features/hooks/manager.py +357 -0
  51. monoco/features/hooks/models.py +262 -0
  52. monoco/features/hooks/parser.py +322 -0
  53. monoco/features/hooks/universal_interceptor.py +503 -0
  54. monoco/features/im/__init__.py +67 -0
  55. monoco/features/im/core.py +782 -0
  56. monoco/features/im/models.py +311 -0
  57. monoco/features/issue/commands.py +133 -60
  58. monoco/features/issue/core.py +385 -40
  59. monoco/features/issue/domain_commands.py +0 -19
  60. monoco/features/issue/resources/en/AGENTS.md +17 -122
  61. monoco/features/issue/resources/hooks/agent/before-tool.sh +102 -0
  62. monoco/features/issue/resources/hooks/agent/session-start.sh +88 -0
  63. monoco/features/issue/resources/hooks/{post-checkout.sh → git/git-post-checkout.sh} +10 -9
  64. monoco/features/issue/resources/hooks/git/git-pre-commit.sh +31 -0
  65. monoco/features/issue/resources/hooks/{pre-push.sh → git/git-pre-push.sh} +7 -13
  66. monoco/features/issue/resources/zh/AGENTS.md +18 -123
  67. monoco/features/memo/cli.py +15 -64
  68. monoco/features/memo/core.py +6 -34
  69. monoco/features/memo/models.py +24 -15
  70. monoco/features/memo/resources/en/AGENTS.md +31 -0
  71. monoco/features/memo/resources/zh/AGENTS.md +28 -5
  72. monoco/features/spike/commands.py +5 -3
  73. monoco/main.py +5 -3
  74. {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/METADATA +1 -1
  75. monoco_toolkit-0.4.0.dist-info/RECORD +170 -0
  76. monoco/core/execution.py +0 -67
  77. monoco/features/agent/apoptosis.py +0 -44
  78. monoco/features/agent/manager.py +0 -127
  79. monoco/features/agent/resources/atoms/atom-code-dev.yaml +0 -61
  80. monoco/features/agent/resources/atoms/atom-issue-lifecycle.yaml +0 -73
  81. monoco/features/agent/resources/atoms/atom-knowledge.yaml +0 -55
  82. monoco/features/agent/resources/atoms/atom-review.yaml +0 -60
  83. monoco/features/agent/resources/en/skills/monoco_atom_core/SKILL.md +0 -99
  84. monoco/features/agent/resources/en/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
  85. monoco/features/agent/resources/en/skills/monoco_workflow_agent_manager/SKILL.md +0 -93
  86. monoco/features/agent/resources/en/skills/monoco_workflow_agent_planner/SKILL.md +0 -85
  87. monoco/features/agent/resources/en/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -114
  88. monoco/features/agent/resources/workflows/workflow-dev.yaml +0 -83
  89. monoco/features/agent/resources/workflows/workflow-issue-create.yaml +0 -72
  90. monoco/features/agent/resources/workflows/workflow-review.yaml +0 -94
  91. monoco/features/agent/resources/zh/roles/monoco_role_engineer.yaml +0 -49
  92. monoco/features/agent/resources/zh/roles/monoco_role_manager.yaml +0 -46
  93. monoco/features/agent/resources/zh/roles/monoco_role_planner.yaml +0 -46
  94. monoco/features/agent/resources/zh/roles/monoco_role_reviewer.yaml +0 -47
  95. monoco/features/agent/resources/zh/skills/monoco_atom_core/SKILL.md +0 -99
  96. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
  97. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_manager/SKILL.md +0 -88
  98. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_planner/SKILL.md +0 -259
  99. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -137
  100. monoco/features/agent/session.py +0 -169
  101. monoco/features/artifact/resources/zh/skills/monoco_atom_artifact/SKILL.md +0 -278
  102. monoco/features/glossary/resources/en/skills/monoco_atom_glossary/SKILL.md +0 -35
  103. monoco/features/glossary/resources/zh/skills/monoco_atom_glossary/SKILL.md +0 -35
  104. monoco/features/hooks/adapter.py +0 -67
  105. monoco/features/hooks/core.py +0 -441
  106. monoco/features/i18n/resources/en/skills/monoco_atom_i18n/SKILL.md +0 -96
  107. monoco/features/i18n/resources/en/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
  108. monoco/features/i18n/resources/zh/skills/monoco_atom_i18n/SKILL.md +0 -96
  109. monoco/features/i18n/resources/zh/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
  110. monoco/features/issue/resources/en/skills/monoco_atom_issue/SKILL.md +0 -165
  111. monoco/features/issue/resources/en/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
  112. monoco/features/issue/resources/en/skills/monoco_workflow_issue_development/SKILL.md +0 -224
  113. monoco/features/issue/resources/en/skills/monoco_workflow_issue_management/SKILL.md +0 -159
  114. monoco/features/issue/resources/en/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
  115. monoco/features/issue/resources/hooks/pre-commit.sh +0 -41
  116. monoco/features/issue/resources/zh/skills/monoco_atom_issue_lifecycle/SKILL.md +0 -190
  117. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
  118. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_development/SKILL.md +0 -224
  119. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_management/SKILL.md +0 -159
  120. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
  121. monoco/features/memo/resources/en/skills/monoco_atom_memo/SKILL.md +0 -77
  122. monoco/features/memo/resources/en/skills/monoco_workflow_note_processing/SKILL.md +0 -140
  123. monoco/features/memo/resources/zh/skills/monoco_atom_memo/SKILL.md +0 -77
  124. monoco/features/memo/resources/zh/skills/monoco_workflow_note_processing/SKILL.md +0 -140
  125. monoco/features/spike/resources/en/skills/monoco_atom_spike/SKILL.md +0 -76
  126. monoco/features/spike/resources/en/skills/monoco_workflow_research/SKILL.md +0 -121
  127. monoco/features/spike/resources/zh/skills/monoco_atom_spike/SKILL.md +0 -76
  128. monoco/features/spike/resources/zh/skills/monoco_workflow_research/SKILL.md +0 -121
  129. monoco_toolkit-0.3.11.dist-info/RECORD +0 -181
  130. {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/WHEEL +0 -0
  131. {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/entry_points.txt +0 -0
  132. {monoco_toolkit-0.3.11.dist-info → monoco_toolkit-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,23 @@
1
+ """
2
+ Hook Dispatchers for Universal Hooks system.
3
+
4
+ Provides type-specific dispatchers for Git, IDE, and Agent hooks.
5
+ """
6
+
7
+ from .git_dispatcher import GitHookDispatcher
8
+ from .agent_dispatcher import (
9
+ AgentHookDispatcher,
10
+ ClaudeCodeDispatcher,
11
+ GeminiDispatcher,
12
+ create_agent_dispatchers,
13
+ get_dispatcher_for_provider,
14
+ )
15
+
16
+ __all__ = [
17
+ "GitHookDispatcher",
18
+ "AgentHookDispatcher",
19
+ "ClaudeCodeDispatcher",
20
+ "GeminiDispatcher",
21
+ "create_agent_dispatchers",
22
+ "get_dispatcher_for_provider",
23
+ ]
@@ -0,0 +1,486 @@
1
+ """
2
+ Agent Hooks Dispatcher for Universal Hooks system.
3
+
4
+ Manages distribution and execution of Agent-specific hooks
5
+ (e.g., for Claude Code, Gemini CLI) using the ACL (Anti-Corruption Layer) pattern.
6
+
7
+ Instead of directly copying hook scripts, this dispatcher injects configurations
8
+ into agent-specific settings files (e.g., .claude/settings.json) that invoke
9
+ the Universal Interceptor for protocol translation.
10
+ """
11
+
12
+ import json
13
+ import os
14
+ from abc import abstractmethod
15
+ from pathlib import Path
16
+ from typing import Any, Optional
17
+
18
+ from ..manager import HookDispatcher
19
+ from ..models import AgentEvent, HookType, ParsedHook
20
+
21
+
22
+ class AgentHookDispatcher(HookDispatcher):
23
+ """
24
+ Base dispatcher for Agent lifecycle hooks with ACL support.
25
+
26
+ Responsible for:
27
+ - Injecting hook configurations into agent-specific settings files
28
+ - Managing provider-specific hook conventions and protocol translation
29
+ - Auto-detecting agent environments via environment variables
30
+
31
+ The ACL pattern ensures that hooks written for the Monoco unified protocol
32
+ can work across different agent platforms without modification.
33
+ """
34
+
35
+ # Environment variable names for auto-detection
36
+ ENV_CLAUDE_CODE_REMOTE = "CLAUDE_CODE_REMOTE"
37
+ ENV_GEMINI_ENV_FILE = "GEMINI_ENV_FILE"
38
+
39
+ # Settings file paths (relative to project root)
40
+ CLAUDE_SETTINGS_PATH = ".claude/settings.json"
41
+ GEMINI_SETTINGS_PATH = ".gemini/settings.json"
42
+
43
+ def __init__(self, provider: str):
44
+ """
45
+ Initialize the Agent hook dispatcher.
46
+
47
+ Args:
48
+ provider: The agent provider (e.g., 'claude-code', 'gemini-cli')
49
+ """
50
+ super().__init__(HookType.AGENT, provider=provider)
51
+
52
+ @abstractmethod
53
+ def get_settings_path(self, project_root: Path) -> Optional[Path]:
54
+ """Get the path to the agent's settings file."""
55
+ pass
56
+
57
+ @abstractmethod
58
+ def translate_event(self, monoco_event: str) -> str:
59
+ """
60
+ Translate Monoco event name to agent-specific event name.
61
+
62
+ Args:
63
+ monoco_event: Event name in Monoco unified protocol
64
+
65
+ Returns:
66
+ Agent-specific event name
67
+ """
68
+ pass
69
+
70
+ @abstractmethod
71
+ def generate_hook_config(self, hook: ParsedHook) -> dict[str, Any]:
72
+ """
73
+ Generate agent-specific hook configuration.
74
+
75
+ Args:
76
+ hook: The parsed hook to generate config for
77
+
78
+ Returns:
79
+ Agent-specific hook configuration dictionary
80
+ """
81
+ pass
82
+
83
+ def can_execute(self, hook: ParsedHook) -> bool:
84
+ """Check if this dispatcher can execute the given hook."""
85
+ return (
86
+ hook.metadata.type == HookType.AGENT
87
+ and hook.metadata.provider == self.provider
88
+ )
89
+
90
+ def execute(self, hook: ParsedHook, context: Optional[dict] = None) -> bool:
91
+ """
92
+ Execute an agent hook directly.
93
+
94
+ Most agent hooks are executed by the agent framework itself
95
+ after being configured. Direct execution is used for testing.
96
+ """
97
+ # TODO: Implement direct execution via Universal Interceptor if needed
98
+ return True
99
+
100
+ def is_available(self, project_root: Path) -> bool:
101
+ """
102
+ Check if this agent environment is available.
103
+
104
+ Returns True if either:
105
+ 1. The agent's settings directory exists
106
+ 2. The corresponding environment variable is set
107
+
108
+ Args:
109
+ project_root: The project root directory
110
+
111
+ Returns:
112
+ True if this agent is available
113
+ """
114
+ settings_path = self.get_settings_path(project_root)
115
+ if settings_path and settings_path.exists():
116
+ return True
117
+
118
+ # Check environment variables
119
+ if self.provider == "claude-code":
120
+ return os.environ.get(self.ENV_CLAUDE_CODE_REMOTE) is not None
121
+ elif self.provider == "gemini-cli":
122
+ return os.environ.get(self.ENV_GEMINI_ENV_FILE) is not None
123
+
124
+ return False
125
+
126
+ def install(self, hook: ParsedHook, project_root: Path) -> bool:
127
+ """
128
+ Install a hook by injecting configuration into agent settings.
129
+
130
+ Args:
131
+ hook: The parsed hook to install
132
+ project_root: The project root directory
133
+
134
+ Returns:
135
+ True if installation succeeded
136
+ """
137
+ settings_path = self.get_settings_path(project_root)
138
+ if not settings_path:
139
+ return False
140
+
141
+ try:
142
+ # Load existing settings or create new
143
+ settings = self._load_settings(settings_path)
144
+
145
+ # Generate hook configuration
146
+ hook_config = self.generate_hook_config(hook)
147
+
148
+ # Inject into settings
149
+ self._inject_hook_config(settings, hook_config, hook)
150
+
151
+ # Save settings
152
+ self._save_settings(settings_path, settings)
153
+
154
+ return True
155
+ except Exception:
156
+ return False
157
+
158
+ def uninstall(self, hook_name: str, project_root: Path) -> bool:
159
+ """
160
+ Remove a hook configuration from agent settings.
161
+
162
+ Args:
163
+ hook_name: The name/identifier of the hook to remove
164
+ project_root: The project root directory
165
+
166
+ Returns:
167
+ True if uninstallation succeeded
168
+ """
169
+ settings_path = self.get_settings_path(project_root)
170
+ if not settings_path or not settings_path.exists():
171
+ return True # Already uninstalled
172
+
173
+ try:
174
+ settings = self._load_settings(settings_path)
175
+
176
+ # Remove hook configurations that match the name
177
+ self._remove_hook_config(settings, hook_name)
178
+
179
+ # Save settings
180
+ self._save_settings(settings_path, settings)
181
+
182
+ return True
183
+ except Exception:
184
+ return False
185
+
186
+ def sync(
187
+ self,
188
+ hooks: list[ParsedHook],
189
+ project_root: Path,
190
+ ) -> dict[str, bool]:
191
+ """
192
+ Synchronize all agent hooks with the settings file.
193
+
194
+ Args:
195
+ hooks: List of parsed hooks to sync
196
+ project_root: The project root directory
197
+
198
+ Returns:
199
+ Dictionary mapping hook names to success status
200
+ """
201
+ results = {}
202
+
203
+ settings_path = self.get_settings_path(project_root)
204
+ if not settings_path:
205
+ return results
206
+
207
+ try:
208
+ # Load or create settings
209
+ settings = self._load_settings(settings_path)
210
+
211
+ # Clear existing Monoco-managed hooks
212
+ self._clear_monoco_hooks(settings)
213
+
214
+ # Install current hooks
215
+ for hook in hooks:
216
+ if not self.can_execute(hook):
217
+ continue
218
+
219
+ hook_config = self.generate_hook_config(hook)
220
+ self._inject_hook_config(settings, hook_config, hook)
221
+ results[hook.script_path.name] = True
222
+
223
+ # Save settings
224
+ self._save_settings(settings_path, settings)
225
+
226
+ except Exception as e:
227
+ for hook in hooks:
228
+ if self.can_execute(hook):
229
+ results[hook.script_path.name] = False
230
+
231
+ return results
232
+
233
+ def _load_settings(self, settings_path: Path) -> dict[str, Any]:
234
+ """Load settings from file or return empty dict."""
235
+ if settings_path.exists():
236
+ try:
237
+ with open(settings_path, "r", encoding="utf-8") as f:
238
+ return json.load(f)
239
+ except (json.JSONDecodeError, IOError):
240
+ return {}
241
+ return {}
242
+
243
+ def _save_settings(self, settings_path: Path, settings: dict[str, Any]) -> None:
244
+ """Save settings to file."""
245
+ settings_path.parent.mkdir(parents=True, exist_ok=True)
246
+ with open(settings_path, "w", encoding="utf-8") as f:
247
+ json.dump(settings, f, indent=2, ensure_ascii=False)
248
+
249
+ def _inject_hook_config(
250
+ self,
251
+ settings: dict[str, Any],
252
+ hook_config: dict[str, Any],
253
+ hook: ParsedHook,
254
+ ) -> None:
255
+ """
256
+ Inject hook configuration into settings.
257
+
258
+ Args:
259
+ settings: The settings dictionary to modify
260
+ hook_config: The hook configuration to inject
261
+ hook: The parsed hook for metadata
262
+ """
263
+ if "hooks" not in settings:
264
+ settings["hooks"] = {}
265
+
266
+ agent_event = self.translate_event(hook.metadata.event)
267
+
268
+ if agent_event not in settings["hooks"]:
269
+ settings["hooks"][agent_event] = []
270
+
271
+ # Add marker to identify as Monoco-managed
272
+ hook_config["_monoco_managed"] = True
273
+ hook_config["_monoco_hook_id"] = hook.script_path.stem
274
+
275
+ settings["hooks"][agent_event].append(hook_config)
276
+
277
+ def _remove_hook_config(self, settings: dict[str, Any], hook_name: str) -> None:
278
+ """
279
+ Remove hook configuration from settings.
280
+
281
+ Args:
282
+ settings: The settings dictionary to modify
283
+ hook_name: The name/identifier of the hook to remove
284
+ """
285
+ if "hooks" not in settings:
286
+ return
287
+
288
+ for event, configs in list(settings["hooks"].items()):
289
+ if isinstance(configs, list):
290
+ settings["hooks"][event] = [
291
+ c for c in configs
292
+ if not (c.get("_monoco_managed") and c.get("_monoco_hook_id") == hook_name)
293
+ ]
294
+
295
+ def _clear_monoco_hooks(self, settings: dict[str, Any]) -> None:
296
+ """Remove all Monoco-managed hooks from settings."""
297
+ if "hooks" not in settings:
298
+ return
299
+
300
+ for event, configs in list(settings["hooks"].items()):
301
+ if isinstance(configs, list):
302
+ settings["hooks"][event] = [
303
+ c for c in configs if not c.get("_monoco_managed")
304
+ ]
305
+
306
+
307
+ class ClaudeCodeDispatcher(AgentHookDispatcher):
308
+ """
309
+ Dispatcher for Claude Code agent hooks.
310
+
311
+ Injects hook configurations into `.claude/settings.json`.
312
+
313
+ Event Mapping (Monoco -> Claude):
314
+ - session-start -> SessionStart
315
+ - before-tool -> PreToolUse
316
+ - after-tool -> PostToolUse
317
+ - before-agent -> UserPromptSubmit
318
+ - after-agent -> Stop
319
+ - session-end -> SessionEnd
320
+ """
321
+
322
+ # Event mapping from Monoco unified protocol to Claude Code
323
+ EVENT_MAP = {
324
+ "session-start": "SessionStart",
325
+ "before-tool": "PreToolUse",
326
+ "after-tool": "PostToolUse",
327
+ "before-agent": "UserPromptSubmit",
328
+ "after-agent": "Stop",
329
+ "session-end": "SessionEnd",
330
+ }
331
+
332
+ def __init__(self):
333
+ """Initialize the Claude Code dispatcher."""
334
+ super().__init__(provider="claude-code")
335
+
336
+ def get_settings_path(self, project_root: Path) -> Optional[Path]:
337
+ """Get the path to Claude Code's settings file."""
338
+ return project_root / self.CLAUDE_SETTINGS_PATH
339
+
340
+ def translate_event(self, monoco_event: str) -> str:
341
+ """Translate Monoco event to Claude Code event name."""
342
+ return self.EVENT_MAP.get(monoco_event, monoco_event)
343
+
344
+ def generate_hook_config(self, hook: ParsedHook) -> dict[str, Any]:
345
+ """
346
+ Generate Claude Code hook configuration.
347
+
348
+ Claude Code uses a matcher-based configuration:
349
+ {
350
+ "matcher": "Bash",
351
+ "hooks": [
352
+ {
353
+ "type": "command",
354
+ "command": "monoco hook run agent before-tool"
355
+ }
356
+ ]
357
+ }
358
+ """
359
+ agent_event = self.translate_event(hook.metadata.event)
360
+
361
+ # Build the command that invokes the Universal Interceptor
362
+ # The interceptor will handle protocol translation
363
+ command = f"monoco hook run agent {hook.metadata.event}"
364
+
365
+ config: dict[str, Any] = {
366
+ "hooks": [
367
+ {
368
+ "type": "command",
369
+ "command": command,
370
+ }
371
+ ]
372
+ }
373
+
374
+ # Add matcher if specified in metadata
375
+ if hook.metadata.matcher:
376
+ # For Claude Code, matcher can be a tool name or pattern
377
+ config["matcher"] = hook.metadata.matcher[0] if hook.metadata.matcher else "*"
378
+
379
+ return config
380
+
381
+
382
+ class GeminiDispatcher(AgentHookDispatcher):
383
+ """
384
+ Dispatcher for Gemini CLI agent hooks.
385
+
386
+ Injects hook configurations into `.gemini/settings.json`.
387
+
388
+ Event Mapping (Monoco -> Gemini):
389
+ - session-start -> SessionStart
390
+ - before-tool -> BeforeTool
391
+ - after-tool -> AfterTool
392
+ - before-agent -> BeforeAgent
393
+ - after-agent -> AfterAgent
394
+ - session-end -> SessionEnd
395
+ """
396
+
397
+ # Event mapping from Monoco unified protocol to Gemini CLI
398
+ EVENT_MAP = {
399
+ "session-start": "SessionStart",
400
+ "before-tool": "BeforeTool",
401
+ "after-tool": "AfterTool",
402
+ "before-agent": "BeforeAgent",
403
+ "after-agent": "AfterAgent",
404
+ "session-end": "SessionEnd",
405
+ }
406
+
407
+ def __init__(self):
408
+ """Initialize the Gemini CLI dispatcher."""
409
+ super().__init__(provider="gemini-cli")
410
+
411
+ def get_settings_path(self, project_root: Path) -> Optional[Path]:
412
+ """Get the path to Gemini CLI's settings file."""
413
+ return project_root / self.GEMINI_SETTINGS_PATH
414
+
415
+ def translate_event(self, monoco_event: str) -> str:
416
+ """Translate Monoco event to Gemini CLI event name."""
417
+ return self.EVENT_MAP.get(monoco_event, monoco_event)
418
+
419
+ def generate_hook_config(self, hook: ParsedHook) -> dict[str, Any]:
420
+ """
421
+ Generate Gemini CLI hook configuration.
422
+
423
+ Gemini uses a similar matcher-based configuration:
424
+ {
425
+ "matcher": "Bash",
426
+ "hooks": [
427
+ {
428
+ "type": "command",
429
+ "command": "monoco hook run agent before-tool"
430
+ }
431
+ ]
432
+ }
433
+ """
434
+ agent_event = self.translate_event(hook.metadata.event)
435
+
436
+ # Build the command that invokes the Universal Interceptor
437
+ command = f"monoco hook run agent {hook.metadata.event}"
438
+
439
+ config: dict[str, Any] = {
440
+ "hooks": [
441
+ {
442
+ "type": "command",
443
+ "command": command,
444
+ }
445
+ ]
446
+ }
447
+
448
+ # Add matcher if specified in metadata
449
+ if hook.metadata.matcher:
450
+ config["matcher"] = hook.metadata.matcher[0] if hook.metadata.matcher else "*"
451
+
452
+ return config
453
+
454
+
455
+ def create_agent_dispatchers() -> list[AgentHookDispatcher]:
456
+ """
457
+ Factory function to create all available agent dispatchers.
458
+
459
+ Returns:
460
+ List of configured agent dispatchers
461
+ """
462
+ return [
463
+ ClaudeCodeDispatcher(),
464
+ GeminiDispatcher(),
465
+ ]
466
+
467
+
468
+ def get_dispatcher_for_provider(provider: str) -> Optional[AgentHookDispatcher]:
469
+ """
470
+ Get the appropriate dispatcher for a provider.
471
+
472
+ Args:
473
+ provider: The provider name (e.g., 'claude-code', 'gemini-cli')
474
+
475
+ Returns:
476
+ The dispatcher instance or None if not found
477
+ """
478
+ dispatchers = {
479
+ "claude-code": ClaudeCodeDispatcher,
480
+ "gemini-cli": GeminiDispatcher,
481
+ }
482
+
483
+ dispatcher_class = dispatchers.get(provider)
484
+ if dispatcher_class:
485
+ return dispatcher_class()
486
+ return None