codepp 0.0.437__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 (288) hide show
  1. code_puppy/__init__.py +10 -0
  2. code_puppy/__main__.py +10 -0
  3. code_puppy/agents/__init__.py +31 -0
  4. code_puppy/agents/agent_c_reviewer.py +155 -0
  5. code_puppy/agents/agent_code_puppy.py +117 -0
  6. code_puppy/agents/agent_code_reviewer.py +90 -0
  7. code_puppy/agents/agent_cpp_reviewer.py +132 -0
  8. code_puppy/agents/agent_creator_agent.py +638 -0
  9. code_puppy/agents/agent_golang_reviewer.py +151 -0
  10. code_puppy/agents/agent_helios.py +124 -0
  11. code_puppy/agents/agent_javascript_reviewer.py +160 -0
  12. code_puppy/agents/agent_manager.py +742 -0
  13. code_puppy/agents/agent_pack_leader.py +385 -0
  14. code_puppy/agents/agent_planning.py +165 -0
  15. code_puppy/agents/agent_python_programmer.py +169 -0
  16. code_puppy/agents/agent_python_reviewer.py +90 -0
  17. code_puppy/agents/agent_qa_expert.py +163 -0
  18. code_puppy/agents/agent_qa_kitten.py +208 -0
  19. code_puppy/agents/agent_scheduler.py +121 -0
  20. code_puppy/agents/agent_security_auditor.py +181 -0
  21. code_puppy/agents/agent_terminal_qa.py +323 -0
  22. code_puppy/agents/agent_typescript_reviewer.py +166 -0
  23. code_puppy/agents/base_agent.py +2156 -0
  24. code_puppy/agents/event_stream_handler.py +348 -0
  25. code_puppy/agents/json_agent.py +202 -0
  26. code_puppy/agents/pack/__init__.py +34 -0
  27. code_puppy/agents/pack/bloodhound.py +304 -0
  28. code_puppy/agents/pack/husky.py +327 -0
  29. code_puppy/agents/pack/retriever.py +393 -0
  30. code_puppy/agents/pack/shepherd.py +348 -0
  31. code_puppy/agents/pack/terrier.py +287 -0
  32. code_puppy/agents/pack/watchdog.py +367 -0
  33. code_puppy/agents/prompt_reviewer.py +145 -0
  34. code_puppy/agents/subagent_stream_handler.py +276 -0
  35. code_puppy/api/__init__.py +13 -0
  36. code_puppy/api/app.py +169 -0
  37. code_puppy/api/main.py +21 -0
  38. code_puppy/api/pty_manager.py +453 -0
  39. code_puppy/api/routers/__init__.py +12 -0
  40. code_puppy/api/routers/agents.py +36 -0
  41. code_puppy/api/routers/commands.py +217 -0
  42. code_puppy/api/routers/config.py +75 -0
  43. code_puppy/api/routers/sessions.py +234 -0
  44. code_puppy/api/templates/terminal.html +361 -0
  45. code_puppy/api/websocket.py +154 -0
  46. code_puppy/callbacks.py +692 -0
  47. code_puppy/chatgpt_codex_client.py +338 -0
  48. code_puppy/claude_cache_client.py +672 -0
  49. code_puppy/cli_runner.py +1073 -0
  50. code_puppy/command_line/__init__.py +1 -0
  51. code_puppy/command_line/add_model_menu.py +1092 -0
  52. code_puppy/command_line/agent_menu.py +662 -0
  53. code_puppy/command_line/attachments.py +395 -0
  54. code_puppy/command_line/autosave_menu.py +704 -0
  55. code_puppy/command_line/clipboard.py +527 -0
  56. code_puppy/command_line/colors_menu.py +532 -0
  57. code_puppy/command_line/command_handler.py +293 -0
  58. code_puppy/command_line/command_registry.py +150 -0
  59. code_puppy/command_line/config_commands.py +719 -0
  60. code_puppy/command_line/core_commands.py +867 -0
  61. code_puppy/command_line/diff_menu.py +865 -0
  62. code_puppy/command_line/file_path_completion.py +73 -0
  63. code_puppy/command_line/load_context_completion.py +52 -0
  64. code_puppy/command_line/mcp/__init__.py +10 -0
  65. code_puppy/command_line/mcp/base.py +32 -0
  66. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  67. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  68. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  69. code_puppy/command_line/mcp/edit_command.py +148 -0
  70. code_puppy/command_line/mcp/handler.py +138 -0
  71. code_puppy/command_line/mcp/help_command.py +147 -0
  72. code_puppy/command_line/mcp/install_command.py +214 -0
  73. code_puppy/command_line/mcp/install_menu.py +705 -0
  74. code_puppy/command_line/mcp/list_command.py +94 -0
  75. code_puppy/command_line/mcp/logs_command.py +235 -0
  76. code_puppy/command_line/mcp/remove_command.py +82 -0
  77. code_puppy/command_line/mcp/restart_command.py +100 -0
  78. code_puppy/command_line/mcp/search_command.py +123 -0
  79. code_puppy/command_line/mcp/start_all_command.py +135 -0
  80. code_puppy/command_line/mcp/start_command.py +117 -0
  81. code_puppy/command_line/mcp/status_command.py +184 -0
  82. code_puppy/command_line/mcp/stop_all_command.py +112 -0
  83. code_puppy/command_line/mcp/stop_command.py +80 -0
  84. code_puppy/command_line/mcp/test_command.py +107 -0
  85. code_puppy/command_line/mcp/utils.py +129 -0
  86. code_puppy/command_line/mcp/wizard_utils.py +334 -0
  87. code_puppy/command_line/mcp_completion.py +174 -0
  88. code_puppy/command_line/model_picker_completion.py +197 -0
  89. code_puppy/command_line/model_settings_menu.py +932 -0
  90. code_puppy/command_line/motd.py +96 -0
  91. code_puppy/command_line/onboarding_slides.py +179 -0
  92. code_puppy/command_line/onboarding_wizard.py +342 -0
  93. code_puppy/command_line/pin_command_completion.py +329 -0
  94. code_puppy/command_line/prompt_toolkit_completion.py +846 -0
  95. code_puppy/command_line/session_commands.py +302 -0
  96. code_puppy/command_line/shell_passthrough.py +145 -0
  97. code_puppy/command_line/skills_completion.py +160 -0
  98. code_puppy/command_line/uc_menu.py +893 -0
  99. code_puppy/command_line/utils.py +93 -0
  100. code_puppy/command_line/wiggum_state.py +78 -0
  101. code_puppy/config.py +1770 -0
  102. code_puppy/error_logging.py +134 -0
  103. code_puppy/gemini_code_assist.py +385 -0
  104. code_puppy/gemini_model.py +754 -0
  105. code_puppy/hook_engine/README.md +105 -0
  106. code_puppy/hook_engine/__init__.py +21 -0
  107. code_puppy/hook_engine/aliases.py +155 -0
  108. code_puppy/hook_engine/engine.py +221 -0
  109. code_puppy/hook_engine/executor.py +296 -0
  110. code_puppy/hook_engine/matcher.py +156 -0
  111. code_puppy/hook_engine/models.py +240 -0
  112. code_puppy/hook_engine/registry.py +106 -0
  113. code_puppy/hook_engine/validator.py +144 -0
  114. code_puppy/http_utils.py +361 -0
  115. code_puppy/keymap.py +128 -0
  116. code_puppy/main.py +10 -0
  117. code_puppy/mcp_/__init__.py +66 -0
  118. code_puppy/mcp_/async_lifecycle.py +286 -0
  119. code_puppy/mcp_/blocking_startup.py +469 -0
  120. code_puppy/mcp_/captured_stdio_server.py +275 -0
  121. code_puppy/mcp_/circuit_breaker.py +290 -0
  122. code_puppy/mcp_/config_wizard.py +507 -0
  123. code_puppy/mcp_/dashboard.py +308 -0
  124. code_puppy/mcp_/error_isolation.py +407 -0
  125. code_puppy/mcp_/examples/retry_example.py +226 -0
  126. code_puppy/mcp_/health_monitor.py +589 -0
  127. code_puppy/mcp_/managed_server.py +428 -0
  128. code_puppy/mcp_/manager.py +807 -0
  129. code_puppy/mcp_/mcp_logs.py +224 -0
  130. code_puppy/mcp_/registry.py +451 -0
  131. code_puppy/mcp_/retry_manager.py +337 -0
  132. code_puppy/mcp_/server_registry_catalog.py +1126 -0
  133. code_puppy/mcp_/status_tracker.py +355 -0
  134. code_puppy/mcp_/system_tools.py +209 -0
  135. code_puppy/mcp_prompts/__init__.py +1 -0
  136. code_puppy/mcp_prompts/hook_creator.py +103 -0
  137. code_puppy/messaging/__init__.py +255 -0
  138. code_puppy/messaging/bus.py +613 -0
  139. code_puppy/messaging/commands.py +167 -0
  140. code_puppy/messaging/markdown_patches.py +57 -0
  141. code_puppy/messaging/message_queue.py +361 -0
  142. code_puppy/messaging/messages.py +569 -0
  143. code_puppy/messaging/queue_console.py +271 -0
  144. code_puppy/messaging/renderers.py +311 -0
  145. code_puppy/messaging/rich_renderer.py +1158 -0
  146. code_puppy/messaging/spinner/__init__.py +83 -0
  147. code_puppy/messaging/spinner/console_spinner.py +240 -0
  148. code_puppy/messaging/spinner/spinner_base.py +95 -0
  149. code_puppy/messaging/subagent_console.py +460 -0
  150. code_puppy/model_factory.py +848 -0
  151. code_puppy/model_switching.py +63 -0
  152. code_puppy/model_utils.py +168 -0
  153. code_puppy/models.json +174 -0
  154. code_puppy/models_dev_api.json +1 -0
  155. code_puppy/models_dev_parser.py +592 -0
  156. code_puppy/plugins/__init__.py +186 -0
  157. code_puppy/plugins/agent_skills/__init__.py +22 -0
  158. code_puppy/plugins/agent_skills/config.py +175 -0
  159. code_puppy/plugins/agent_skills/discovery.py +136 -0
  160. code_puppy/plugins/agent_skills/downloader.py +392 -0
  161. code_puppy/plugins/agent_skills/installer.py +22 -0
  162. code_puppy/plugins/agent_skills/metadata.py +219 -0
  163. code_puppy/plugins/agent_skills/prompt_builder.py +60 -0
  164. code_puppy/plugins/agent_skills/register_callbacks.py +241 -0
  165. code_puppy/plugins/agent_skills/remote_catalog.py +322 -0
  166. code_puppy/plugins/agent_skills/skill_catalog.py +257 -0
  167. code_puppy/plugins/agent_skills/skills_install_menu.py +664 -0
  168. code_puppy/plugins/agent_skills/skills_menu.py +781 -0
  169. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  170. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  171. code_puppy/plugins/antigravity_oauth/antigravity_model.py +706 -0
  172. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  173. code_puppy/plugins/antigravity_oauth/constants.py +133 -0
  174. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  175. code_puppy/plugins/antigravity_oauth/register_callbacks.py +518 -0
  176. code_puppy/plugins/antigravity_oauth/storage.py +288 -0
  177. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  178. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  179. code_puppy/plugins/antigravity_oauth/transport.py +863 -0
  180. code_puppy/plugins/antigravity_oauth/utils.py +168 -0
  181. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  182. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  183. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +329 -0
  184. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +176 -0
  185. code_puppy/plugins/chatgpt_oauth/test_plugin.py +301 -0
  186. code_puppy/plugins/chatgpt_oauth/utils.py +523 -0
  187. code_puppy/plugins/claude_code_hooks/__init__.py +1 -0
  188. code_puppy/plugins/claude_code_hooks/config.py +137 -0
  189. code_puppy/plugins/claude_code_hooks/register_callbacks.py +175 -0
  190. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  191. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  192. code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
  193. code_puppy/plugins/claude_code_oauth/config.py +52 -0
  194. code_puppy/plugins/claude_code_oauth/register_callbacks.py +453 -0
  195. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  196. code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +241 -0
  197. code_puppy/plugins/claude_code_oauth/utils.py +640 -0
  198. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  199. code_puppy/plugins/customizable_commands/register_callbacks.py +152 -0
  200. code_puppy/plugins/example_custom_command/README.md +280 -0
  201. code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
  202. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  203. code_puppy/plugins/file_permission_handler/register_callbacks.py +470 -0
  204. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  205. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  206. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  207. code_puppy/plugins/hook_creator/__init__.py +1 -0
  208. code_puppy/plugins/hook_creator/register_callbacks.py +33 -0
  209. code_puppy/plugins/hook_manager/__init__.py +1 -0
  210. code_puppy/plugins/hook_manager/config.py +290 -0
  211. code_puppy/plugins/hook_manager/hooks_menu.py +564 -0
  212. code_puppy/plugins/hook_manager/register_callbacks.py +227 -0
  213. code_puppy/plugins/oauth_puppy_html.py +228 -0
  214. code_puppy/plugins/scheduler/__init__.py +1 -0
  215. code_puppy/plugins/scheduler/register_callbacks.py +88 -0
  216. code_puppy/plugins/scheduler/scheduler_menu.py +522 -0
  217. code_puppy/plugins/scheduler/scheduler_wizard.py +341 -0
  218. code_puppy/plugins/shell_safety/__init__.py +6 -0
  219. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  220. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  221. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  222. code_puppy/plugins/synthetic_status/__init__.py +1 -0
  223. code_puppy/plugins/synthetic_status/register_callbacks.py +132 -0
  224. code_puppy/plugins/synthetic_status/status_api.py +147 -0
  225. code_puppy/plugins/universal_constructor/__init__.py +13 -0
  226. code_puppy/plugins/universal_constructor/models.py +138 -0
  227. code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
  228. code_puppy/plugins/universal_constructor/registry.py +302 -0
  229. code_puppy/plugins/universal_constructor/sandbox.py +584 -0
  230. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  231. code_puppy/pydantic_patches.py +356 -0
  232. code_puppy/reopenable_async_client.py +232 -0
  233. code_puppy/round_robin_model.py +150 -0
  234. code_puppy/scheduler/__init__.py +41 -0
  235. code_puppy/scheduler/__main__.py +9 -0
  236. code_puppy/scheduler/cli.py +118 -0
  237. code_puppy/scheduler/config.py +126 -0
  238. code_puppy/scheduler/daemon.py +280 -0
  239. code_puppy/scheduler/executor.py +155 -0
  240. code_puppy/scheduler/platform.py +19 -0
  241. code_puppy/scheduler/platform_unix.py +22 -0
  242. code_puppy/scheduler/platform_win.py +32 -0
  243. code_puppy/session_storage.py +338 -0
  244. code_puppy/status_display.py +257 -0
  245. code_puppy/summarization_agent.py +176 -0
  246. code_puppy/terminal_utils.py +418 -0
  247. code_puppy/tools/__init__.py +501 -0
  248. code_puppy/tools/agent_tools.py +603 -0
  249. code_puppy/tools/ask_user_question/__init__.py +26 -0
  250. code_puppy/tools/ask_user_question/constants.py +73 -0
  251. code_puppy/tools/ask_user_question/demo_tui.py +55 -0
  252. code_puppy/tools/ask_user_question/handler.py +232 -0
  253. code_puppy/tools/ask_user_question/models.py +304 -0
  254. code_puppy/tools/ask_user_question/registration.py +26 -0
  255. code_puppy/tools/ask_user_question/renderers.py +309 -0
  256. code_puppy/tools/ask_user_question/terminal_ui.py +329 -0
  257. code_puppy/tools/ask_user_question/theme.py +155 -0
  258. code_puppy/tools/ask_user_question/tui_loop.py +423 -0
  259. code_puppy/tools/browser/__init__.py +37 -0
  260. code_puppy/tools/browser/browser_control.py +289 -0
  261. code_puppy/tools/browser/browser_interactions.py +545 -0
  262. code_puppy/tools/browser/browser_locators.py +640 -0
  263. code_puppy/tools/browser/browser_manager.py +378 -0
  264. code_puppy/tools/browser/browser_navigation.py +251 -0
  265. code_puppy/tools/browser/browser_screenshot.py +179 -0
  266. code_puppy/tools/browser/browser_scripts.py +462 -0
  267. code_puppy/tools/browser/browser_workflows.py +221 -0
  268. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  269. code_puppy/tools/browser/terminal_command_tools.py +534 -0
  270. code_puppy/tools/browser/terminal_screenshot_tools.py +552 -0
  271. code_puppy/tools/browser/terminal_tools.py +525 -0
  272. code_puppy/tools/command_runner.py +1346 -0
  273. code_puppy/tools/common.py +1409 -0
  274. code_puppy/tools/display.py +84 -0
  275. code_puppy/tools/file_modifications.py +886 -0
  276. code_puppy/tools/file_operations.py +802 -0
  277. code_puppy/tools/scheduler_tools.py +412 -0
  278. code_puppy/tools/skills_tools.py +244 -0
  279. code_puppy/tools/subagent_context.py +158 -0
  280. code_puppy/tools/tools_content.py +51 -0
  281. code_puppy/tools/universal_constructor.py +889 -0
  282. code_puppy/uvx_detection.py +242 -0
  283. code_puppy/version_checker.py +82 -0
  284. codepp-0.0.437.dist-info/METADATA +766 -0
  285. codepp-0.0.437.dist-info/RECORD +288 -0
  286. codepp-0.0.437.dist-info/WHEEL +4 -0
  287. codepp-0.0.437.dist-info/entry_points.txt +3 -0
  288. codepp-0.0.437.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,105 @@
1
+ # Hook Engine
2
+
3
+ A standalone, testable hook execution system for processing events and executing
4
+ configured hooks with pattern matching, timeout handling, and blocking capabilities.
5
+
6
+ ## Overview
7
+
8
+ The Hook Engine provides a complete system for implementing event-driven automation
9
+ through configurable hooks. It is compatible with Anthropic's Claude Code
10
+ `.claude/settings.json` format.
11
+
12
+ Features:
13
+ - **Pattern matching** - Wildcards, file extensions, `&&` / `||` compound logic, regex
14
+ - **Event types** - PreToolUse, PostToolUse, SessionStart, Stop, and more
15
+ - **Async execution** - Non-blocking subprocess execution with per-hook timeouts
16
+ - **Claude Code compatible stdin** - JSON payload on stdin, env vars for compatibility
17
+ - **Blocking capability** - Exit code 1 vetoes the tool call
18
+ - **Once-per-session** - Hooks that only run once per session
19
+ - **Comprehensive validation** - Clear error messages for misconfigured hooks
20
+
21
+ ## Quick Start
22
+
23
+ ```python
24
+ from code_puppy.hook_engine import HookEngine, EventData
25
+
26
+ config = {
27
+ "PreToolUse": [{
28
+ "matcher": "Bash|agent_run_shell_command",
29
+ "hooks": [{
30
+ "type": "command",
31
+ "command": "bash .claude/hooks/my-check.sh",
32
+ "timeout": 5000
33
+ }]
34
+ }]
35
+ }
36
+
37
+ import asyncio
38
+
39
+ engine = HookEngine(config)
40
+ event_data = EventData(
41
+ event_type="PreToolUse",
42
+ tool_name="agent_run_shell_command",
43
+ tool_args={"command": "git status"}
44
+ )
45
+
46
+ async def main():
47
+ result = await engine.process_event("PreToolUse", event_data)
48
+ if result.blocked:
49
+ print(f"Blocked: {result.blocking_reason}")
50
+
51
+ asyncio.run(main())
52
+ ```
53
+
54
+ ## Hook Input Format
55
+
56
+ Scripts receive JSON on stdin (Claude Code compatible):
57
+
58
+ ```json
59
+ {
60
+ "session_id": "codepuppy-session",
61
+ "hook_event_name": "PreToolUse",
62
+ "tool_name": "agent_run_shell_command",
63
+ "tool_input": {"command": "git status"},
64
+ "cwd": "/path/to/project",
65
+ "permission_mode": "default"
66
+ }
67
+ ```
68
+
69
+ Also available as environment variables: `CLAUDE_TOOL_INPUT`, `CLAUDE_TOOL_NAME`,
70
+ `CLAUDE_PROJECT_DIR`, `CLAUDE_HOOK_EVENT`, `CLAUDE_FILE_PATH`.
71
+
72
+ ## Exit Codes
73
+
74
+ - `0` - Allow (stdout shown in transcript)
75
+ - `1` - Block (stderr shown as block reason)
76
+ - `2` - Error feedback to Claude without blocking
77
+
78
+ See `docs/HOOKS.md` for the full user-facing guide.
79
+
80
+ ## Tool Name Compatibility
81
+
82
+ Hooks can be written using **either** the provider's tool name **or** code_puppy's
83
+ internal tool name — the matcher treats them as equivalent.
84
+
85
+ ### Claude Code → code_puppy
86
+
87
+ | Claude Code (`matcher`) | code_puppy internal | Notes |
88
+ |-------------------------|---------------------|-------|
89
+ | `Bash` | `agent_run_shell_command` | Shell execution |
90
+ | `Glob` | `list_files` | File glob / directory listing |
91
+ | `Read` | `read_file` | Read file contents |
92
+ | `Grep` | `grep` | Text search |
93
+ | `Edit` | `replace_in_file` | Patch / partial edit |
94
+ | `Write` | `create_file` | Full-file overwrite |
95
+ | `Delete` | `delete_file` | File deletion |
96
+ | `AskUserQuestion` | `ask_user_question` | Interactive user prompt |
97
+ | `Task` | `invoke_agent` | Sub-agent / task spawn |
98
+ | `Skill` | `activate_skill` | Skill activation |
99
+ | `ToolSearch` | `list_or_search_skills` | Skill/tool discovery |
100
+
101
+ Provider aliases for **Gemini**, **Codex**, and **Swarm** are reserved in
102
+ `aliases.py` and will be populated once their MCP tool vocabularies are verified.
103
+
104
+ Both directions work — `"matcher": "Bash"` and `"matcher": "agent_run_shell_command"`
105
+ are identical at match time.
@@ -0,0 +1,21 @@
1
+ """Hook engine package for Code Puppy."""
2
+
3
+ from . import aliases
4
+ from .engine import HookEngine
5
+ from .models import (
6
+ EventData,
7
+ ExecutionResult,
8
+ HookConfig,
9
+ HookRegistry,
10
+ ProcessEventResult,
11
+ )
12
+
13
+ __all__ = [
14
+ "HookEngine",
15
+ "HookConfig",
16
+ "EventData",
17
+ "ExecutionResult",
18
+ "ProcessEventResult",
19
+ "HookRegistry",
20
+ "aliases",
21
+ ]
@@ -0,0 +1,155 @@
1
+ """
2
+ Tool name alias registry — maps each AI provider's tool names to code_puppy's
3
+ internal tool names, enabling hooks written for any provider to fire correctly.
4
+
5
+ Structure
6
+ ---------
7
+ Each provider block defines a dict[str, str] mapping:
8
+ "<Provider tool name>" -> "<code_puppy internal tool name>"
9
+
10
+ The mapping is bidirectional at lookup time: a hook matcher that names *either*
11
+ the provider tool OR the internal tool will match the same event.
12
+
13
+ Adding a new provider
14
+ ---------------------
15
+ 1. Add a new section following the pattern below.
16
+ 2. Register it in PROVIDER_ALIASES at the bottom of this file.
17
+ 3. That's it — the matcher picks it up automatically.
18
+ """
19
+
20
+ from typing import Dict, FrozenSet, Optional
21
+
22
+ # ---------------------------------------------------------------------------
23
+ # Claude Code (Anthropic)
24
+ # Source: `claude mcp serve` → tools/list (verified against v2.1.52)
25
+ # ---------------------------------------------------------------------------
26
+ CLAUDE_CODE_ALIASES: Dict[str, str] = {
27
+ # Shell execution
28
+ "Bash": "agent_run_shell_command",
29
+ # File system — read
30
+ "Glob": "list_files",
31
+ "Read": "read_file",
32
+ "Grep": "grep",
33
+ # File system — write
34
+ "Edit": "replace_in_file",
35
+ "Write": "create_file", # Write = full overwrite
36
+ # File system — delete
37
+ "Delete": "delete_file",
38
+ # User interaction
39
+ "AskUserQuestion": "ask_user_question",
40
+ # Agent / task orchestration
41
+ "Task": "invoke_agent",
42
+ # Skills
43
+ "Skill": "activate_skill",
44
+ "ToolSearch": "list_or_search_skills",
45
+ # NOTE: the tools below have no direct code_puppy equivalent yet.
46
+ # They are listed here for documentation and future mapping:
47
+ # "TaskOutput" -> (no equivalent)
48
+ # "TaskStop" -> (no equivalent)
49
+ # "WebFetch" -> (no equivalent — see browser_navigate)
50
+ # "WebSearch" -> (no equivalent)
51
+ # "NotebookEdit" -> (no equivalent)
52
+ # "TodoWrite" -> (no equivalent)
53
+ # "EnterPlanMode" -> (no equivalent)
54
+ # "ExitPlanMode" -> (no equivalent)
55
+ # "EnterWorktree" -> (no equivalent)
56
+ }
57
+
58
+
59
+ # ---------------------------------------------------------------------------
60
+ # Gemini (Google)
61
+ # TODO: populate once Gemini MCP tool names are verified.
62
+ # Run `gemini mcp serve` (or equivalent) and inspect the tools/list response,
63
+ # then add entries following the same pattern as CLAUDE_CODE_ALIASES above.
64
+ # ---------------------------------------------------------------------------
65
+ GEMINI_ALIASES: Dict[str, str] = {
66
+ # Add Gemini → code_puppy tool mappings here
67
+ }
68
+
69
+
70
+ # ---------------------------------------------------------------------------
71
+ # Codex (OpenAI)
72
+ # TODO: populate once Codex MCP tool names are verified.
73
+ # Run the Codex MCP server, inspect tools/list, and add entries here.
74
+ # ---------------------------------------------------------------------------
75
+ CODEX_ALIASES: Dict[str, str] = {
76
+ # Add Codex → code_puppy tool mappings here
77
+ }
78
+
79
+
80
+ # ---------------------------------------------------------------------------
81
+ # Swarm (internal / multi-agent)
82
+ # TODO: populate if Swarm exposes its own canonical tool name vocabulary.
83
+ # ---------------------------------------------------------------------------
84
+ SWARM_ALIASES: Dict[str, str] = {
85
+ # Add Swarm → code_puppy tool mappings here
86
+ }
87
+
88
+
89
+ # ---------------------------------------------------------------------------
90
+ # Master registry — all active alias tables, merged at module load time.
91
+ # To disable a provider's aliases, remove its entry from this dict.
92
+ # ---------------------------------------------------------------------------
93
+ PROVIDER_ALIASES: Dict[str, Dict[str, str]] = {
94
+ "claude": CLAUDE_CODE_ALIASES,
95
+ "gemini": GEMINI_ALIASES, # placeholder — empty until populated
96
+ "codex": CODEX_ALIASES, # placeholder — empty until populated
97
+ "swarm": SWARM_ALIASES, # placeholder — empty until populated
98
+ }
99
+
100
+
101
+ # ---------------------------------------------------------------------------
102
+ # Flattened lookup structures — built once at import time for O(1) access.
103
+ # ---------------------------------------------------------------------------
104
+
105
+
106
+ def _build_lookup() -> Dict[str, FrozenSet[str]]:
107
+ """
108
+ Return a dict mapping every known name (provider *and* internal) to the
109
+ full set of equivalent names, including itself.
110
+
111
+ Example result entry:
112
+ "Bash" -> frozenset({"Bash", "agent_run_shell_command"})
113
+ "agent_run_shell_command" -> frozenset({"Bash", "agent_run_shell_command"})
114
+ """
115
+ groups: Dict[str, set] = {}
116
+
117
+ for provider_aliases in PROVIDER_ALIASES.values():
118
+ for provider_name, internal_name in provider_aliases.items():
119
+ # Collect all names that map to the same internal tool
120
+ key = internal_name.lower()
121
+ if key not in groups:
122
+ groups[key] = {internal_name}
123
+ groups[key].add(provider_name)
124
+
125
+ # Build the final lookup: every alias points to the frozen group
126
+ lookup: Dict[str, FrozenSet[str]] = {}
127
+ for group in groups.values():
128
+ frozen = frozenset(group)
129
+ for name in group:
130
+ lookup[name.lower()] = frozen
131
+ return lookup
132
+
133
+
134
+ # Module-level singleton — import this in matcher.py
135
+ ALIAS_LOOKUP: Dict[str, FrozenSet[str]] = _build_lookup()
136
+
137
+
138
+ def get_aliases(tool_name: str) -> FrozenSet[str]:
139
+ """
140
+ Return all known equivalent names for *tool_name* (including itself).
141
+ Returns a frozenset containing only *tool_name* when no aliases exist.
142
+ """
143
+ return ALIAS_LOOKUP.get(tool_name.lower(), frozenset({tool_name}))
144
+
145
+
146
+ def resolve_internal_name(provider_tool_name: str) -> Optional[str]:
147
+ """
148
+ Return the code_puppy internal tool name for a given provider tool name,
149
+ or None if the name is not a known provider alias.
150
+ """
151
+ for provider_aliases in PROVIDER_ALIASES.values():
152
+ for pname, internal in provider_aliases.items():
153
+ if pname.lower() == provider_tool_name.lower():
154
+ return internal
155
+ return None
@@ -0,0 +1,221 @@
1
+ """
2
+ Main HookEngine orchestration class.
3
+ """
4
+
5
+ import logging
6
+ import time
7
+ from typing import Any, Dict, List, Optional
8
+
9
+ from .executor import execute_hooks_sequential, get_blocking_result
10
+ from .matcher import matches
11
+ from .models import (
12
+ EventData,
13
+ HookConfig,
14
+ HookRegistry,
15
+ ProcessEventResult,
16
+ )
17
+ from .registry import build_registry_from_config, get_registry_stats
18
+ from .validator import (
19
+ format_validation_report,
20
+ get_config_suggestions,
21
+ validate_hooks_config,
22
+ )
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class HookEngine:
28
+ """
29
+ Main hook engine for processing events and executing hooks.
30
+
31
+ Coordinates all hook engine components:
32
+ - Loads and validates configuration
33
+ - Matches events against hook patterns
34
+ - Executes hooks with timeout and error handling
35
+ - Aggregates results and determines blocking status
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ config: Optional[Dict[str, Any]] = None,
41
+ strict_validation: bool = True,
42
+ env_vars: Optional[Dict[str, str]] = None,
43
+ ):
44
+ self.env_vars = env_vars or {}
45
+ self.strict_validation = strict_validation
46
+ self._registry: Optional[HookRegistry] = None
47
+
48
+ if config:
49
+ self.load_config(config)
50
+ else:
51
+ self._registry = HookRegistry()
52
+
53
+ def load_config(self, config: Dict[str, Any]) -> None:
54
+ is_valid, errors = validate_hooks_config(config)
55
+
56
+ if not is_valid:
57
+ error_msg = format_validation_report(
58
+ is_valid, errors, get_config_suggestions(config, errors)
59
+ )
60
+ if self.strict_validation:
61
+ raise ValueError(f"Invalid hook configuration:\n{error_msg}")
62
+ else:
63
+ logger.warning(f"Hook configuration has errors:\n{error_msg}")
64
+
65
+ try:
66
+ self._registry = build_registry_from_config(config)
67
+ logger.info(
68
+ f"Loaded hook configuration: {self._registry.count_hooks()} total hooks"
69
+ )
70
+ except Exception as e:
71
+ if self.strict_validation:
72
+ raise ValueError(f"Failed to build hook registry: {e}") from e
73
+ else:
74
+ logger.error(f"Failed to build hook registry: {e}", exc_info=True)
75
+ self._registry = HookRegistry()
76
+
77
+ def reload_config(self, config: Dict[str, Any]) -> None:
78
+ self.load_config(config)
79
+
80
+ async def process_event(
81
+ self,
82
+ event_type: str,
83
+ event_data: EventData,
84
+ sequential: bool = True,
85
+ stop_on_block: bool = True,
86
+ ) -> ProcessEventResult:
87
+ """Process an event through the hook engine."""
88
+ start_time = time.perf_counter()
89
+
90
+ if not self._registry:
91
+ return ProcessEventResult(
92
+ blocked=False, executed_hooks=0, results=[], total_duration_ms=0.0
93
+ )
94
+
95
+ all_hooks = self._registry.get_hooks_for_event(event_type)
96
+
97
+ if not all_hooks:
98
+ duration_ms = (time.perf_counter() - start_time) * 1000
99
+ return ProcessEventResult(
100
+ blocked=False,
101
+ executed_hooks=0,
102
+ results=[],
103
+ total_duration_ms=duration_ms,
104
+ )
105
+
106
+ matching_hooks = self._filter_hooks_by_matcher(
107
+ all_hooks, event_data.tool_name, event_data.tool_args
108
+ )
109
+
110
+ if not matching_hooks:
111
+ duration_ms = (time.perf_counter() - start_time) * 1000
112
+ return ProcessEventResult(
113
+ blocked=False,
114
+ executed_hooks=0,
115
+ results=[],
116
+ total_duration_ms=duration_ms,
117
+ )
118
+
119
+ logger.debug(
120
+ f"Processing {event_type}: {len(matching_hooks)} matching hook(s) for tool '{event_data.tool_name}'"
121
+ )
122
+
123
+ if sequential:
124
+ results = await execute_hooks_sequential(
125
+ matching_hooks, event_data, self.env_vars, stop_on_block=stop_on_block
126
+ )
127
+ else:
128
+ from .executor import execute_hooks_parallel
129
+
130
+ results = await execute_hooks_parallel(
131
+ matching_hooks, event_data, self.env_vars
132
+ )
133
+
134
+ for hook, result in zip(matching_hooks, results):
135
+ if hook.once and result.success:
136
+ self._registry.mark_hook_executed(hook.id)
137
+
138
+ blocking_result = get_blocking_result(results)
139
+ blocked = blocking_result is not None
140
+ blocking_reason = None
141
+
142
+ if blocked:
143
+ blocking_reason = (
144
+ f"Hook '{blocking_result.hook_command}' failed: "
145
+ f"{blocking_result.error or blocking_result.stderr or 'blocked (no details provided)'}"
146
+ )
147
+
148
+ duration_ms = (time.perf_counter() - start_time) * 1000
149
+ return ProcessEventResult(
150
+ blocked=blocked,
151
+ executed_hooks=len(results),
152
+ results=results,
153
+ blocking_reason=blocking_reason,
154
+ total_duration_ms=duration_ms,
155
+ )
156
+
157
+ def _filter_hooks_by_matcher(
158
+ self,
159
+ hooks: List[HookConfig],
160
+ tool_name: str,
161
+ tool_args: Dict[str, Any],
162
+ ) -> List[HookConfig]:
163
+ matching_hooks = []
164
+ for hook in hooks:
165
+ try:
166
+ if matches(hook.matcher, tool_name, tool_args):
167
+ matching_hooks.append(hook)
168
+ except Exception as e:
169
+ logger.error(
170
+ f"Error matching hook '{hook.matcher}': {e}", exc_info=True
171
+ )
172
+ return matching_hooks
173
+
174
+ def get_stats(self) -> Dict[str, Any]:
175
+ if not self._registry:
176
+ return {"total_hooks": 0, "error": "No registry loaded"}
177
+ return get_registry_stats(self._registry)
178
+
179
+ def get_hooks_for_event(self, event_type: str) -> List[HookConfig]:
180
+ if not self._registry:
181
+ return []
182
+ return self._registry.get_hooks_for_event(event_type)
183
+
184
+ def count_hooks(self, event_type: Optional[str] = None) -> int:
185
+ if not self._registry:
186
+ return 0
187
+ return self._registry.count_hooks(event_type)
188
+
189
+ def reset_once_hooks(self) -> None:
190
+ if self._registry:
191
+ self._registry.reset_once_hooks()
192
+
193
+ def add_hook(self, event_type: str, hook: HookConfig) -> None:
194
+ if not self._registry:
195
+ self._registry = HookRegistry()
196
+ self._registry.add_hook(event_type, hook)
197
+
198
+ def remove_hook(self, event_type: str, hook_id: str) -> bool:
199
+ if not self._registry:
200
+ return False
201
+ return self._registry.remove_hook(event_type, hook_id)
202
+
203
+ def set_env_vars(self, env_vars: Dict[str, str]) -> None:
204
+ self.env_vars = env_vars
205
+
206
+ def update_env_vars(self, env_vars: Dict[str, str]) -> None:
207
+ self.env_vars.update(env_vars)
208
+
209
+ @property
210
+ def is_loaded(self) -> bool:
211
+ return self._registry is not None
212
+
213
+ @property
214
+ def registry(self) -> Optional[HookRegistry]:
215
+ return self._registry
216
+
217
+
218
+ def validate_config_file(config: Dict[str, Any]) -> str:
219
+ is_valid, errors = validate_hooks_config(config)
220
+ suggestions = get_config_suggestions(config, errors) if not is_valid else []
221
+ return format_validation_report(is_valid, errors, suggestions)