monoco-toolkit 0.3.12__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 (120) hide show
  1. monoco/core/automation/__init__.py +0 -11
  2. monoco/core/automation/handlers.py +108 -26
  3. monoco/core/config.py +28 -10
  4. monoco/core/daemon/__init__.py +5 -0
  5. monoco/core/daemon/pid.py +290 -0
  6. monoco/core/injection.py +86 -8
  7. monoco/core/integrations.py +0 -24
  8. monoco/core/router/__init__.py +1 -39
  9. monoco/core/router/action.py +3 -142
  10. monoco/core/scheduler/events.py +28 -2
  11. monoco/core/setup.py +9 -0
  12. monoco/core/sync.py +199 -4
  13. monoco/core/watcher/__init__.py +6 -0
  14. monoco/core/watcher/base.py +18 -1
  15. monoco/core/watcher/im.py +460 -0
  16. monoco/core/watcher/memo.py +40 -48
  17. monoco/daemon/app.py +3 -60
  18. monoco/daemon/commands.py +459 -25
  19. monoco/daemon/scheduler.py +1 -16
  20. monoco/daemon/services.py +15 -0
  21. monoco/features/agent/resources/en/AGENTS.md +14 -14
  22. monoco/features/agent/resources/en/skills/monoco_role_engineer/SKILL.md +101 -0
  23. monoco/features/agent/resources/en/skills/monoco_role_manager/SKILL.md +95 -0
  24. monoco/features/agent/resources/en/skills/monoco_role_planner/SKILL.md +177 -0
  25. monoco/features/agent/resources/en/skills/monoco_role_reviewer/SKILL.md +139 -0
  26. monoco/features/agent/resources/zh/skills/monoco_role_engineer/SKILL.md +101 -0
  27. monoco/features/agent/resources/zh/skills/monoco_role_manager/SKILL.md +95 -0
  28. monoco/features/agent/resources/zh/skills/monoco_role_planner/SKILL.md +177 -0
  29. monoco/features/agent/resources/zh/skills/monoco_role_reviewer/SKILL.md +139 -0
  30. monoco/features/hooks/__init__.py +61 -6
  31. monoco/features/hooks/commands.py +281 -271
  32. monoco/features/hooks/dispatchers/__init__.py +23 -0
  33. monoco/features/hooks/dispatchers/agent_dispatcher.py +486 -0
  34. monoco/features/hooks/dispatchers/git_dispatcher.py +478 -0
  35. monoco/features/hooks/manager.py +357 -0
  36. monoco/features/hooks/models.py +262 -0
  37. monoco/features/hooks/parser.py +322 -0
  38. monoco/features/hooks/universal_interceptor.py +503 -0
  39. monoco/features/im/__init__.py +67 -0
  40. monoco/features/im/core.py +782 -0
  41. monoco/features/im/models.py +311 -0
  42. monoco/features/issue/commands.py +65 -50
  43. monoco/features/issue/core.py +199 -99
  44. monoco/features/issue/domain_commands.py +0 -19
  45. monoco/features/issue/resources/en/AGENTS.md +17 -122
  46. monoco/features/issue/resources/hooks/agent/before-tool.sh +102 -0
  47. monoco/features/issue/resources/hooks/agent/session-start.sh +88 -0
  48. monoco/features/issue/resources/hooks/{post-checkout.sh → git/git-post-checkout.sh} +10 -9
  49. monoco/features/issue/resources/hooks/git/git-pre-commit.sh +31 -0
  50. monoco/features/issue/resources/hooks/{pre-push.sh → git/git-pre-push.sh} +7 -13
  51. monoco/features/issue/resources/zh/AGENTS.md +18 -123
  52. monoco/features/memo/cli.py +15 -64
  53. monoco/features/memo/core.py +6 -34
  54. monoco/features/memo/models.py +24 -15
  55. monoco/features/memo/resources/en/AGENTS.md +31 -0
  56. monoco/features/memo/resources/zh/AGENTS.md +28 -5
  57. monoco/main.py +5 -3
  58. {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/METADATA +1 -1
  59. monoco_toolkit-0.4.0.dist-info/RECORD +170 -0
  60. monoco/core/automation/config.py +0 -338
  61. monoco/core/execution.py +0 -67
  62. monoco/core/executor/__init__.py +0 -38
  63. monoco/core/executor/agent_action.py +0 -254
  64. monoco/core/executor/git_action.py +0 -303
  65. monoco/core/executor/im_action.py +0 -309
  66. monoco/core/executor/pytest_action.py +0 -218
  67. monoco/core/router/router.py +0 -392
  68. monoco/features/agent/resources/atoms/atom-code-dev.yaml +0 -61
  69. monoco/features/agent/resources/atoms/atom-issue-lifecycle.yaml +0 -73
  70. monoco/features/agent/resources/atoms/atom-knowledge.yaml +0 -55
  71. monoco/features/agent/resources/atoms/atom-review.yaml +0 -60
  72. monoco/features/agent/resources/en/skills/monoco_atom_core/SKILL.md +0 -99
  73. monoco/features/agent/resources/en/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
  74. monoco/features/agent/resources/en/skills/monoco_workflow_agent_manager/SKILL.md +0 -93
  75. monoco/features/agent/resources/en/skills/monoco_workflow_agent_planner/SKILL.md +0 -85
  76. monoco/features/agent/resources/en/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -114
  77. monoco/features/agent/resources/workflows/workflow-dev.yaml +0 -83
  78. monoco/features/agent/resources/workflows/workflow-issue-create.yaml +0 -72
  79. monoco/features/agent/resources/workflows/workflow-review.yaml +0 -94
  80. monoco/features/agent/resources/zh/roles/monoco_role_engineer.yaml +0 -49
  81. monoco/features/agent/resources/zh/roles/monoco_role_manager.yaml +0 -46
  82. monoco/features/agent/resources/zh/roles/monoco_role_planner.yaml +0 -46
  83. monoco/features/agent/resources/zh/roles/monoco_role_reviewer.yaml +0 -47
  84. monoco/features/agent/resources/zh/skills/monoco_atom_core/SKILL.md +0 -99
  85. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
  86. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_manager/SKILL.md +0 -88
  87. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_planner/SKILL.md +0 -259
  88. monoco/features/agent/resources/zh/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -137
  89. monoco/features/artifact/resources/zh/skills/monoco_atom_artifact/SKILL.md +0 -278
  90. monoco/features/glossary/resources/en/skills/monoco_atom_glossary/SKILL.md +0 -35
  91. monoco/features/glossary/resources/zh/skills/monoco_atom_glossary/SKILL.md +0 -35
  92. monoco/features/hooks/adapter.py +0 -67
  93. monoco/features/hooks/core.py +0 -441
  94. monoco/features/i18n/resources/en/skills/monoco_atom_i18n/SKILL.md +0 -96
  95. monoco/features/i18n/resources/en/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
  96. monoco/features/i18n/resources/zh/skills/monoco_atom_i18n/SKILL.md +0 -96
  97. monoco/features/i18n/resources/zh/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
  98. monoco/features/issue/resources/en/skills/monoco_atom_issue/SKILL.md +0 -165
  99. monoco/features/issue/resources/en/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
  100. monoco/features/issue/resources/en/skills/monoco_workflow_issue_development/SKILL.md +0 -224
  101. monoco/features/issue/resources/en/skills/monoco_workflow_issue_management/SKILL.md +0 -159
  102. monoco/features/issue/resources/en/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
  103. monoco/features/issue/resources/hooks/pre-commit.sh +0 -41
  104. monoco/features/issue/resources/zh/skills/monoco_atom_issue_lifecycle/SKILL.md +0 -190
  105. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
  106. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_development/SKILL.md +0 -224
  107. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_management/SKILL.md +0 -159
  108. monoco/features/issue/resources/zh/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
  109. monoco/features/memo/resources/en/skills/monoco_atom_memo/SKILL.md +0 -77
  110. monoco/features/memo/resources/en/skills/monoco_workflow_note_processing/SKILL.md +0 -140
  111. monoco/features/memo/resources/zh/skills/monoco_atom_memo/SKILL.md +0 -77
  112. monoco/features/memo/resources/zh/skills/monoco_workflow_note_processing/SKILL.md +0 -140
  113. monoco/features/spike/resources/en/skills/monoco_atom_spike/SKILL.md +0 -76
  114. monoco/features/spike/resources/en/skills/monoco_workflow_research/SKILL.md +0 -121
  115. monoco/features/spike/resources/zh/skills/monoco_atom_spike/SKILL.md +0 -76
  116. monoco/features/spike/resources/zh/skills/monoco_workflow_research/SKILL.md +0 -121
  117. monoco_toolkit-0.3.12.dist-info/RECORD +0 -202
  118. {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/WHEEL +0 -0
  119. {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/entry_points.txt +0 -0
  120. {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -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