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.
- monoco/core/automation/__init__.py +0 -11
- monoco/core/automation/handlers.py +108 -26
- monoco/core/config.py +28 -10
- monoco/core/daemon/__init__.py +5 -0
- monoco/core/daemon/pid.py +290 -0
- monoco/core/injection.py +86 -8
- monoco/core/integrations.py +0 -24
- monoco/core/router/__init__.py +1 -39
- monoco/core/router/action.py +3 -142
- monoco/core/scheduler/events.py +28 -2
- monoco/core/setup.py +9 -0
- monoco/core/sync.py +199 -4
- monoco/core/watcher/__init__.py +6 -0
- monoco/core/watcher/base.py +18 -1
- monoco/core/watcher/im.py +460 -0
- monoco/core/watcher/memo.py +40 -48
- monoco/daemon/app.py +3 -60
- monoco/daemon/commands.py +459 -25
- monoco/daemon/scheduler.py +1 -16
- monoco/daemon/services.py +15 -0
- monoco/features/agent/resources/en/AGENTS.md +14 -14
- monoco/features/agent/resources/en/skills/monoco_role_engineer/SKILL.md +101 -0
- monoco/features/agent/resources/en/skills/monoco_role_manager/SKILL.md +95 -0
- monoco/features/agent/resources/en/skills/monoco_role_planner/SKILL.md +177 -0
- monoco/features/agent/resources/en/skills/monoco_role_reviewer/SKILL.md +139 -0
- monoco/features/agent/resources/zh/skills/monoco_role_engineer/SKILL.md +101 -0
- monoco/features/agent/resources/zh/skills/monoco_role_manager/SKILL.md +95 -0
- monoco/features/agent/resources/zh/skills/monoco_role_planner/SKILL.md +177 -0
- monoco/features/agent/resources/zh/skills/monoco_role_reviewer/SKILL.md +139 -0
- monoco/features/hooks/__init__.py +61 -6
- monoco/features/hooks/commands.py +281 -271
- monoco/features/hooks/dispatchers/__init__.py +23 -0
- monoco/features/hooks/dispatchers/agent_dispatcher.py +486 -0
- monoco/features/hooks/dispatchers/git_dispatcher.py +478 -0
- monoco/features/hooks/manager.py +357 -0
- monoco/features/hooks/models.py +262 -0
- monoco/features/hooks/parser.py +322 -0
- monoco/features/hooks/universal_interceptor.py +503 -0
- monoco/features/im/__init__.py +67 -0
- monoco/features/im/core.py +782 -0
- monoco/features/im/models.py +311 -0
- monoco/features/issue/commands.py +65 -50
- monoco/features/issue/core.py +199 -99
- monoco/features/issue/domain_commands.py +0 -19
- monoco/features/issue/resources/en/AGENTS.md +17 -122
- monoco/features/issue/resources/hooks/agent/before-tool.sh +102 -0
- monoco/features/issue/resources/hooks/agent/session-start.sh +88 -0
- monoco/features/issue/resources/hooks/{post-checkout.sh → git/git-post-checkout.sh} +10 -9
- monoco/features/issue/resources/hooks/git/git-pre-commit.sh +31 -0
- monoco/features/issue/resources/hooks/{pre-push.sh → git/git-pre-push.sh} +7 -13
- monoco/features/issue/resources/zh/AGENTS.md +18 -123
- monoco/features/memo/cli.py +15 -64
- monoco/features/memo/core.py +6 -34
- monoco/features/memo/models.py +24 -15
- monoco/features/memo/resources/en/AGENTS.md +31 -0
- monoco/features/memo/resources/zh/AGENTS.md +28 -5
- monoco/main.py +5 -3
- {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/METADATA +1 -1
- monoco_toolkit-0.4.0.dist-info/RECORD +170 -0
- monoco/core/automation/config.py +0 -338
- monoco/core/execution.py +0 -67
- monoco/core/executor/__init__.py +0 -38
- monoco/core/executor/agent_action.py +0 -254
- monoco/core/executor/git_action.py +0 -303
- monoco/core/executor/im_action.py +0 -309
- monoco/core/executor/pytest_action.py +0 -218
- monoco/core/router/router.py +0 -392
- monoco/features/agent/resources/atoms/atom-code-dev.yaml +0 -61
- monoco/features/agent/resources/atoms/atom-issue-lifecycle.yaml +0 -73
- monoco/features/agent/resources/atoms/atom-knowledge.yaml +0 -55
- monoco/features/agent/resources/atoms/atom-review.yaml +0 -60
- monoco/features/agent/resources/en/skills/monoco_atom_core/SKILL.md +0 -99
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_manager/SKILL.md +0 -93
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_planner/SKILL.md +0 -85
- monoco/features/agent/resources/en/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -114
- monoco/features/agent/resources/workflows/workflow-dev.yaml +0 -83
- monoco/features/agent/resources/workflows/workflow-issue-create.yaml +0 -72
- monoco/features/agent/resources/workflows/workflow-review.yaml +0 -94
- monoco/features/agent/resources/zh/roles/monoco_role_engineer.yaml +0 -49
- monoco/features/agent/resources/zh/roles/monoco_role_manager.yaml +0 -46
- monoco/features/agent/resources/zh/roles/monoco_role_planner.yaml +0 -46
- monoco/features/agent/resources/zh/roles/monoco_role_reviewer.yaml +0 -47
- monoco/features/agent/resources/zh/skills/monoco_atom_core/SKILL.md +0 -99
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_engineer/SKILL.md +0 -94
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_manager/SKILL.md +0 -88
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_planner/SKILL.md +0 -259
- monoco/features/agent/resources/zh/skills/monoco_workflow_agent_reviewer/SKILL.md +0 -137
- monoco/features/artifact/resources/zh/skills/monoco_atom_artifact/SKILL.md +0 -278
- monoco/features/glossary/resources/en/skills/monoco_atom_glossary/SKILL.md +0 -35
- monoco/features/glossary/resources/zh/skills/monoco_atom_glossary/SKILL.md +0 -35
- monoco/features/hooks/adapter.py +0 -67
- monoco/features/hooks/core.py +0 -441
- monoco/features/i18n/resources/en/skills/monoco_atom_i18n/SKILL.md +0 -96
- monoco/features/i18n/resources/en/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
- monoco/features/i18n/resources/zh/skills/monoco_atom_i18n/SKILL.md +0 -96
- monoco/features/i18n/resources/zh/skills/monoco_workflow_i18n_scan/SKILL.md +0 -105
- monoco/features/issue/resources/en/skills/monoco_atom_issue/SKILL.md +0 -165
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_development/SKILL.md +0 -224
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_management/SKILL.md +0 -159
- monoco/features/issue/resources/en/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
- monoco/features/issue/resources/hooks/pre-commit.sh +0 -41
- monoco/features/issue/resources/zh/skills/monoco_atom_issue_lifecycle/SKILL.md +0 -190
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_creation/SKILL.md +0 -167
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_development/SKILL.md +0 -224
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_management/SKILL.md +0 -159
- monoco/features/issue/resources/zh/skills/monoco_workflow_issue_refinement/SKILL.md +0 -203
- monoco/features/memo/resources/en/skills/monoco_atom_memo/SKILL.md +0 -77
- monoco/features/memo/resources/en/skills/monoco_workflow_note_processing/SKILL.md +0 -140
- monoco/features/memo/resources/zh/skills/monoco_atom_memo/SKILL.md +0 -77
- monoco/features/memo/resources/zh/skills/monoco_workflow_note_processing/SKILL.md +0 -140
- monoco/features/spike/resources/en/skills/monoco_atom_spike/SKILL.md +0 -76
- monoco/features/spike/resources/en/skills/monoco_workflow_research/SKILL.md +0 -121
- monoco/features/spike/resources/zh/skills/monoco_atom_spike/SKILL.md +0 -76
- monoco/features/spike/resources/zh/skills/monoco_workflow_research/SKILL.md +0 -121
- monoco_toolkit-0.3.12.dist-info/RECORD +0 -202
- {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.3.12.dist-info → monoco_toolkit-0.4.0.dist-info}/entry_points.txt +0 -0
- {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
|