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,412 @@
1
+ """Scheduler tools for the Scheduler Agent.
2
+
3
+ These tools allow the scheduler agent to manage scheduled tasks,
4
+ control the daemon, and view logs.
5
+ """
6
+
7
+ import os
8
+
9
+ from pydantic import Field
10
+
11
+ from code_puppy.messaging import emit_info, emit_success, emit_warning
12
+ from code_puppy.tools.common import generate_group_id
13
+
14
+
15
+ def register_scheduler_list_tasks(agent):
16
+ """Register the scheduler_list_tasks tool."""
17
+
18
+ def scheduler_list_tasks() -> str:
19
+ """List all scheduled tasks with their status and daemon info.
20
+
21
+ Returns a formatted overview of:
22
+ - Daemon status (running/stopped)
23
+ - All configured tasks with their schedules
24
+ - Last run status for each task
25
+ """
26
+ from code_puppy.scheduler import load_tasks
27
+ from code_puppy.scheduler.daemon import get_daemon_pid
28
+
29
+ group_id = generate_group_id("scheduler_list_tasks")
30
+ emit_info("📅 SCHEDULER LIST TASKS", message_group=group_id)
31
+
32
+ tasks = load_tasks()
33
+ pid = get_daemon_pid()
34
+
35
+ lines = []
36
+ lines.append("## Scheduler Status")
37
+ lines.append(
38
+ f"**Daemon:** {'🟢 Running (PID ' + str(pid) + ')' if pid else '🔴 Stopped'}"
39
+ )
40
+ lines.append(f"**Total Tasks:** {len(tasks)}")
41
+ lines.append("")
42
+
43
+ if not tasks:
44
+ lines.append("No scheduled tasks configured yet.")
45
+ lines.append("")
46
+ lines.append("Use `scheduler_create_task` to create one!")
47
+ return "\n".join(lines)
48
+
49
+ lines.append("## Tasks")
50
+ lines.append("")
51
+
52
+ for task in tasks:
53
+ status_icon = "🟢" if task.enabled else "🔴"
54
+ run_status = ""
55
+ if task.last_status == "success":
56
+ run_status = " ✅"
57
+ elif task.last_status == "failed":
58
+ run_status = " ❌"
59
+ elif task.last_status == "running":
60
+ run_status = " ⏳"
61
+
62
+ lines.append(f"### {status_icon} {task.name} (`{task.id}`){run_status}")
63
+ lines.append(
64
+ f"- **Schedule:** {task.schedule_type} ({task.schedule_value})"
65
+ )
66
+ lines.append(f"- **Agent:** {task.agent}")
67
+ lines.append(f"- **Model:** {task.model or '(default)'}")
68
+ lines.append(
69
+ f"- **Prompt:** {task.prompt[:100]}{'...' if len(task.prompt) > 100 else ''}"
70
+ )
71
+ lines.append(f"- **Directory:** {task.working_directory}")
72
+ if task.last_run:
73
+ lines.append(
74
+ f"- **Last Run:** {task.last_run[:19]} (exit code: {task.last_exit_code})"
75
+ )
76
+ lines.append("")
77
+
78
+ return "\n".join(lines)
79
+
80
+ agent.tool_plain(scheduler_list_tasks)
81
+
82
+
83
+ def register_scheduler_create_task(agent):
84
+ """Register the scheduler_create_task tool."""
85
+
86
+ def scheduler_create_task(
87
+ name: str = Field(description="Human-readable name for the task"),
88
+ prompt: str = Field(description="The prompt to execute"),
89
+ agent: str = Field(
90
+ default="code-puppy",
91
+ description="Agent to use (e.g., code-puppy, code-reviewer, security-auditor)",
92
+ ),
93
+ model: str = Field(
94
+ default="", description="Model to use (empty string for default)"
95
+ ),
96
+ schedule_type: str = Field(
97
+ default="interval",
98
+ description="Schedule type: 'interval', 'hourly', or 'daily'",
99
+ ),
100
+ schedule_value: str = Field(
101
+ default="1h",
102
+ description="Schedule value (e.g., '30m', '2h', '1d' for intervals)",
103
+ ),
104
+ working_directory: str = Field(
105
+ default=".", description="Working directory for the task"
106
+ ),
107
+ ) -> str:
108
+ """Create a new scheduled task.
109
+
110
+ Creates a task that will run automatically according to the specified schedule.
111
+ The daemon must be running for tasks to execute.
112
+ """
113
+ from code_puppy.scheduler import ScheduledTask, add_task
114
+ from code_puppy.scheduler.daemon import get_daemon_pid
115
+
116
+ group_id = generate_group_id("scheduler_create_task", name)
117
+ emit_info(f"📅 SCHEDULER CREATE TASK → {name}", message_group=group_id)
118
+
119
+ task = ScheduledTask(
120
+ name=name,
121
+ prompt=prompt,
122
+ agent=agent,
123
+ model=model,
124
+ schedule_type=schedule_type,
125
+ schedule_value=schedule_value,
126
+ working_directory=working_directory,
127
+ )
128
+
129
+ add_task(task)
130
+ emit_success(f"Created task: {name} ({task.id})", message_group=group_id)
131
+
132
+ result = f"""✅ **Task Created Successfully!**
133
+
134
+ **ID:** `{task.id}`
135
+ **Name:** {task.name}
136
+ **Schedule:** {task.schedule_type} ({task.schedule_value})
137
+ **Agent:** {task.agent}
138
+ **Model:** {task.model or "(default)"}
139
+ **Directory:** {task.working_directory}
140
+ **Log File:** `{task.log_file}`
141
+
142
+ **Prompt:**
143
+ ```
144
+ {task.prompt}
145
+ ```
146
+ """
147
+
148
+ pid = get_daemon_pid()
149
+ if not pid:
150
+ result += "\n⚠️ **Note:** The scheduler daemon is not running. Use `scheduler_start_daemon` to start it!"
151
+ else:
152
+ result += f"\n🟢 Daemon is running (PID {pid}). Task will execute according to schedule."
153
+
154
+ return result
155
+
156
+ agent.tool_plain(scheduler_create_task)
157
+
158
+
159
+ def register_scheduler_delete_task(agent):
160
+ """Register the scheduler_delete_task tool."""
161
+
162
+ def scheduler_delete_task(
163
+ task_id: str = Field(description="The ID of the task to delete"),
164
+ ) -> str:
165
+ """Delete a scheduled task by its ID.
166
+
167
+ Permanently removes the task from the schedule.
168
+ The task's log file is NOT deleted.
169
+ """
170
+ from code_puppy.scheduler import delete_task, get_task
171
+
172
+ group_id = generate_group_id("scheduler_delete_task", task_id)
173
+ emit_info(f"📅 SCHEDULER DELETE TASK → {task_id}", message_group=group_id)
174
+
175
+ task = get_task(task_id)
176
+ if not task:
177
+ emit_warning(f"Task not found: {task_id}", message_group=group_id)
178
+ return f"❌ Task not found: `{task_id}`"
179
+
180
+ task_name = task.name
181
+ if delete_task(task_id):
182
+ emit_success(f"Deleted task: {task_name}", message_group=group_id)
183
+ return f"✅ Deleted task: **{task_name}** (`{task_id}`)"
184
+ else:
185
+ return f"❌ Failed to delete task: `{task_id}`"
186
+
187
+ agent.tool_plain(scheduler_delete_task)
188
+
189
+
190
+ def register_scheduler_toggle_task(agent):
191
+ """Register the scheduler_toggle_task tool."""
192
+
193
+ def scheduler_toggle_task(
194
+ task_id: str = Field(description="The ID of the task to enable/disable"),
195
+ ) -> str:
196
+ """Toggle a task's enabled/disabled state.
197
+
198
+ Disabled tasks remain in the schedule but won't run until re-enabled.
199
+ """
200
+ from code_puppy.scheduler import get_task, toggle_task
201
+
202
+ group_id = generate_group_id("scheduler_toggle_task", task_id)
203
+ emit_info(f"📅 SCHEDULER TOGGLE TASK → {task_id}", message_group=group_id)
204
+
205
+ task = get_task(task_id)
206
+ if not task:
207
+ emit_warning(f"Task not found: {task_id}", message_group=group_id)
208
+ return f"❌ Task not found: `{task_id}`"
209
+
210
+ task_name = task.name
211
+ new_state = toggle_task(task_id)
212
+
213
+ if new_state is None:
214
+ return f"❌ Failed to toggle task: `{task_id}`"
215
+
216
+ status = "🟢 **Enabled**" if new_state else "🔴 **Disabled**"
217
+ status_msg = "enabled" if new_state else "disabled"
218
+ emit_success(f"Task {task_name} {status_msg}", message_group=group_id)
219
+ return f"Task **{task_name}** (`{task_id}`) is now {status}"
220
+
221
+ agent.tool_plain(scheduler_toggle_task)
222
+
223
+
224
+ def register_scheduler_daemon_status(agent):
225
+ """Register the scheduler_daemon_status tool."""
226
+
227
+ def scheduler_daemon_status() -> str:
228
+ """Check if the scheduler daemon is running.
229
+
230
+ Returns detailed status including PID and task counts.
231
+ """
232
+ from code_puppy.scheduler import load_tasks
233
+ from code_puppy.scheduler.daemon import get_daemon_pid
234
+
235
+ group_id = generate_group_id("scheduler_daemon_status")
236
+ emit_info("📅 SCHEDULER DAEMON STATUS", message_group=group_id)
237
+
238
+ pid = get_daemon_pid()
239
+ tasks = load_tasks()
240
+ enabled_count = sum(1 for t in tasks if t.enabled)
241
+
242
+ if pid:
243
+ return f"""🟢 **Daemon is RUNNING**
244
+
245
+ **PID:** {pid}
246
+ **Total Tasks:** {len(tasks)}
247
+ **Enabled Tasks:** {enabled_count}
248
+
249
+ The scheduler is actively monitoring and running tasks according to their schedules."""
250
+ else:
251
+ return f"""🔴 **Daemon is STOPPED**
252
+
253
+ **Total Tasks:** {len(tasks)}
254
+ **Enabled Tasks:** {enabled_count}
255
+
256
+ The scheduler daemon is not running. Scheduled tasks will NOT execute until you start it.
257
+
258
+ Use `scheduler_start_daemon` to start the daemon."""
259
+
260
+ agent.tool_plain(scheduler_daemon_status)
261
+
262
+
263
+ def register_scheduler_start_daemon(agent):
264
+ """Register the scheduler_start_daemon tool."""
265
+
266
+ def scheduler_start_daemon() -> str:
267
+ """Start the scheduler daemon in the background.
268
+
269
+ The daemon runs independently and will continue even after
270
+ Code Puppy exits. It checks for and runs scheduled tasks.
271
+ """
272
+ from code_puppy.scheduler.daemon import get_daemon_pid, start_daemon_background
273
+
274
+ group_id = generate_group_id("scheduler_start_daemon")
275
+ emit_info("📅 SCHEDULER START DAEMON", message_group=group_id)
276
+
277
+ pid = get_daemon_pid()
278
+ if pid:
279
+ emit_warning(f"Daemon already running (PID {pid})", message_group=group_id)
280
+ return f"ℹ️ Daemon is already running (PID {pid})"
281
+
282
+ if start_daemon_background():
283
+ new_pid = get_daemon_pid()
284
+ emit_success(f"Daemon started (PID {new_pid})", message_group=group_id)
285
+ return f"✅ **Daemon started successfully!** (PID {new_pid})\n\nScheduled tasks will now run automatically."
286
+ else:
287
+ emit_warning("Failed to start daemon", message_group=group_id)
288
+ return "❌ **Failed to start daemon.** Check the logs for errors."
289
+
290
+ agent.tool_plain(scheduler_start_daemon)
291
+
292
+
293
+ def register_scheduler_stop_daemon(agent):
294
+ """Register the scheduler_stop_daemon tool."""
295
+
296
+ def scheduler_stop_daemon() -> str:
297
+ """Stop the scheduler daemon.
298
+
299
+ Stops the background process. No scheduled tasks will run
300
+ until the daemon is started again.
301
+ """
302
+ from code_puppy.scheduler.daemon import get_daemon_pid, stop_daemon
303
+
304
+ group_id = generate_group_id("scheduler_stop_daemon")
305
+ emit_info("📅 SCHEDULER STOP DAEMON", message_group=group_id)
306
+
307
+ pid = get_daemon_pid()
308
+ if not pid:
309
+ emit_warning("Daemon is not running", message_group=group_id)
310
+ return "ℹ️ Daemon is not running."
311
+
312
+ if stop_daemon():
313
+ emit_success(f"Daemon stopped (was PID {pid})", message_group=group_id)
314
+ return f"✅ **Daemon stopped.** (was PID {pid})\n\nScheduled tasks will NOT run until you start the daemon again."
315
+ else:
316
+ emit_warning(f"Failed to stop daemon (PID {pid})", message_group=group_id)
317
+ return f"❌ **Failed to stop daemon** (PID {pid}). You may need to kill it manually."
318
+
319
+ agent.tool_plain(scheduler_stop_daemon)
320
+
321
+
322
+ def register_scheduler_run_task(agent):
323
+ """Register the scheduler_run_task tool."""
324
+
325
+ def scheduler_run_task(
326
+ task_id: str = Field(description="The ID of the task to run immediately"),
327
+ ) -> str:
328
+ """Run a scheduled task immediately.
329
+
330
+ Executes the task right now, regardless of its schedule.
331
+ Useful for testing or one-off runs.
332
+ """
333
+ from code_puppy.scheduler import get_task
334
+ from code_puppy.scheduler.executor import run_task_by_id
335
+
336
+ group_id = generate_group_id("scheduler_run_task", task_id)
337
+ emit_info(f"📅 SCHEDULER RUN TASK → {task_id}", message_group=group_id)
338
+
339
+ task = get_task(task_id)
340
+ if not task:
341
+ emit_warning(f"Task not found: {task_id}", message_group=group_id)
342
+ return f"❌ Task not found: `{task_id}`"
343
+
344
+ emit_info(f"Running: {task.name}...", message_group=group_id)
345
+ result = f"⏳ Running task **{task.name}** (`{task_id}`)...\n\n"
346
+
347
+ success, message = run_task_by_id(task_id)
348
+
349
+ if success:
350
+ emit_success(f"Task completed: {task.name}", message_group=group_id)
351
+ result += f"✅ **Task completed successfully!**\n\n{message}\n\nView the log with `scheduler_view_log`."
352
+ else:
353
+ emit_warning(f"Task failed: {task.name}", message_group=group_id)
354
+ result += f"❌ **Task failed.**\n\n{message}\n\nCheck the log with `scheduler_view_log` for details."
355
+
356
+ return result
357
+
358
+ agent.tool_plain(scheduler_run_task)
359
+
360
+
361
+ def register_scheduler_view_log(agent):
362
+ """Register the scheduler_view_log tool."""
363
+
364
+ def scheduler_view_log(
365
+ task_id: str = Field(description="The ID of the task whose log to view"),
366
+ lines: int = Field(
367
+ default=50, description="Number of lines to show from the end of the log"
368
+ ),
369
+ ) -> str:
370
+ """View the log file for a scheduled task.
371
+
372
+ Shows the most recent output from task executions.
373
+ Each run appends to the log file.
374
+ """
375
+ from code_puppy.scheduler import get_task
376
+
377
+ group_id = generate_group_id("scheduler_view_log", task_id)
378
+ emit_info(
379
+ f"📅 SCHEDULER VIEW LOG → {task_id} (last {lines} lines)",
380
+ message_group=group_id,
381
+ )
382
+
383
+ task = get_task(task_id)
384
+ if not task:
385
+ emit_warning(f"Task not found: {task_id}", message_group=group_id)
386
+ return f"❌ Task not found: `{task_id}`"
387
+
388
+ log_file = task.log_file
389
+ if not os.path.exists(log_file):
390
+ return f"📄 **No log file yet for task:** {task.name} (`{task_id}`)\n\nThe log will be created when the task runs for the first time."
391
+
392
+ try:
393
+ with open(log_file, "r") as f:
394
+ content = f.read()
395
+
396
+ log_lines = content.split("\n")
397
+ if len(log_lines) > lines:
398
+ log_lines = log_lines[-lines:]
399
+
400
+ truncated_content = "\n".join(log_lines)
401
+
402
+ return f"""📄 **Log for task:** {task.name} (`{task_id}`)
403
+ **File:** `{log_file}`
404
+ **Showing:** last {lines} lines
405
+
406
+ ```
407
+ {truncated_content}
408
+ ```"""
409
+ except Exception as e:
410
+ return f"❌ Error reading log file: {e}"
411
+
412
+ agent.tool_plain(scheduler_view_log)
@@ -0,0 +1,244 @@
1
+ """Skills tools - dedicated tools for Agent Skills integration."""
2
+
3
+ import logging
4
+ from typing import List, Optional
5
+
6
+ from pydantic import BaseModel
7
+ from pydantic_ai import RunContext
8
+
9
+ from code_puppy.messaging import (
10
+ SkillActivateMessage,
11
+ SkillEntry,
12
+ SkillListMessage,
13
+ get_message_bus,
14
+ )
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ # Output models
20
+ class SkillListOutput(BaseModel):
21
+ """Output for list_or_search_skills tool."""
22
+
23
+ skills: List[dict] # Each has: name, description, path, tags
24
+ total_count: int
25
+ query: Optional[str] = None # The search query if provided
26
+ error: Optional[str] = None
27
+
28
+
29
+ class SkillActivateOutput(BaseModel):
30
+ """Output for activate_skill tool."""
31
+
32
+ skill_name: str
33
+ content: str # Full SKILL.md content
34
+ resources: List[str] # Available resource files
35
+ error: Optional[str] = None
36
+
37
+
38
+ def register_activate_skill(agent):
39
+ """Register the activate_skill tool."""
40
+
41
+ @agent.tool
42
+ async def activate_skill(
43
+ context: RunContext, skill_name: str = ""
44
+ ) -> SkillActivateOutput:
45
+ """Activate a skill by loading its full SKILL.md instructions."""
46
+ # Import from plugin
47
+ from pathlib import Path
48
+
49
+ from code_puppy.plugins.agent_skills.config import (
50
+ get_skill_directories,
51
+ get_skills_enabled,
52
+ )
53
+ from code_puppy.plugins.agent_skills.discovery import discover_skills
54
+ from code_puppy.plugins.agent_skills.metadata import (
55
+ get_skill_resources,
56
+ load_full_skill_content,
57
+ )
58
+
59
+ # Check if skills enabled
60
+ if not get_skills_enabled():
61
+ return SkillActivateOutput(
62
+ skill_name=skill_name,
63
+ content="",
64
+ resources=[],
65
+ error="Skills integration is disabled. Enable it with /set skills_enabled=true",
66
+ )
67
+
68
+ # Discover skills
69
+ try:
70
+ skill_dirs = [Path(d) for d in get_skill_directories()]
71
+ discovered = discover_skills(skill_dirs)
72
+ except Exception as e:
73
+ logger.error(f"Failed to discover skills: {e}")
74
+ return SkillActivateOutput(
75
+ skill_name=skill_name,
76
+ content="",
77
+ resources=[],
78
+ error=f"Failed to discover skills: {e}",
79
+ )
80
+
81
+ # Find skill by name
82
+ skill_path = None
83
+ for skill_info in discovered:
84
+ if skill_info.name == skill_name and skill_info.has_skill_md:
85
+ skill_path = skill_info.path
86
+ break
87
+
88
+ if not skill_path:
89
+ return SkillActivateOutput(
90
+ skill_name=skill_name,
91
+ content="",
92
+ resources=[],
93
+ error=f"Skill '{skill_name}' not found. Use list_or_search_skills to see available skills.",
94
+ )
95
+
96
+ # Load full content
97
+ content = load_full_skill_content(skill_path)
98
+ if content is None:
99
+ return SkillActivateOutput(
100
+ skill_name=skill_name,
101
+ content="",
102
+ resources=[],
103
+ error=f"Failed to load content for skill '{skill_name}'",
104
+ )
105
+
106
+ # Get resource list
107
+ resource_paths = get_skill_resources(skill_path)
108
+ resources = [str(p) for p in resource_paths]
109
+
110
+ # Emit message for UI
111
+ content_preview = content[:200] if content else ""
112
+ skill_msg = SkillActivateMessage(
113
+ skill_name=skill_name,
114
+ skill_path=str(skill_path),
115
+ content_preview=content_preview,
116
+ resource_count=len(resources),
117
+ success=True,
118
+ )
119
+ get_message_bus().emit(skill_msg)
120
+
121
+ return SkillActivateOutput(
122
+ skill_name=skill_name, content=content, resources=resources, error=None
123
+ )
124
+
125
+ return activate_skill
126
+
127
+
128
+ def register_list_or_search_skills(agent):
129
+ """Register the list_or_search_skills tool."""
130
+
131
+ @agent.tool
132
+ async def list_or_search_skills(
133
+ context: RunContext, query: Optional[str] = None
134
+ ) -> SkillListOutput:
135
+ """List available skills, optionally filtered by search query.
136
+
137
+ Args:
138
+ query: Optional search term to filter skills by name/description/tags.
139
+ If None, returns all available skills.
140
+ """
141
+ # Import from plugin
142
+ from pathlib import Path
143
+
144
+ from code_puppy.plugins.agent_skills.config import (
145
+ get_disabled_skills,
146
+ get_skill_directories,
147
+ get_skills_enabled,
148
+ )
149
+ from code_puppy.plugins.agent_skills.discovery import discover_skills
150
+ from code_puppy.plugins.agent_skills.metadata import parse_skill_metadata
151
+
152
+ # Check if skills enabled
153
+ if not get_skills_enabled():
154
+ return SkillListOutput(
155
+ skills=[],
156
+ total_count=0,
157
+ query=query,
158
+ error="Skills integration is disabled. Enable it with /set skills_enabled=true",
159
+ )
160
+
161
+ # Get disabled skills
162
+ disabled_skills = get_disabled_skills()
163
+
164
+ # Discover all skills
165
+ try:
166
+ skill_dirs = [Path(d) for d in get_skill_directories()]
167
+ discovered = discover_skills(skill_dirs)
168
+ except Exception as e:
169
+ logger.error(f"Failed to discover skills: {e}")
170
+ return SkillListOutput(
171
+ skills=[],
172
+ total_count=0,
173
+ query=query,
174
+ error=f"Failed to discover skills: {e}",
175
+ )
176
+
177
+ # Parse metadata for each skill
178
+ skills_list = []
179
+ for skill_info in discovered:
180
+ # Skip disabled skills
181
+ if skill_info.name in disabled_skills:
182
+ continue
183
+
184
+ # Only include skills with valid SKILL.md
185
+ if not skill_info.has_skill_md:
186
+ continue
187
+
188
+ metadata = parse_skill_metadata(skill_info.path)
189
+ if metadata:
190
+ skill_dict = {
191
+ "name": metadata.name,
192
+ "description": metadata.description,
193
+ "path": str(metadata.path),
194
+ "tags": metadata.tags,
195
+ "version": metadata.version,
196
+ "author": metadata.author,
197
+ }
198
+ skills_list.append(skill_dict)
199
+
200
+ # Filter by query if provided
201
+ if query:
202
+ query_lower = query.lower()
203
+ filtered = []
204
+ for skill in skills_list:
205
+ # Check name (case-insensitive)
206
+ if query_lower in skill["name"].lower():
207
+ filtered.append(skill)
208
+ continue
209
+
210
+ # Check description (case-insensitive)
211
+ if query_lower in skill["description"].lower():
212
+ filtered.append(skill)
213
+ continue
214
+
215
+ # Check tags (case-insensitive)
216
+ for tag in skill["tags"]:
217
+ if query_lower in tag.lower():
218
+ filtered.append(skill)
219
+ break
220
+ skills_list = filtered
221
+
222
+ # Emit message for UI
223
+ skill_entries = [
224
+ SkillEntry(
225
+ name=s["name"],
226
+ description=s["description"],
227
+ path=s["path"],
228
+ tags=s["tags"],
229
+ enabled=s["name"] not in disabled_skills,
230
+ )
231
+ for s in skills_list
232
+ ]
233
+ skill_msg = SkillListMessage(
234
+ skills=skill_entries,
235
+ query=query,
236
+ total_count=len(skills_list),
237
+ )
238
+ get_message_bus().emit(skill_msg)
239
+
240
+ return SkillListOutput(
241
+ skills=skills_list, total_count=len(skills_list), query=query, error=None
242
+ )
243
+
244
+ return list_or_search_skills