newcode 0.1.1__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 (289) 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 +147 -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 +630 -0
  9. code_puppy/agents/agent_golang_reviewer.py +151 -0
  10. code_puppy/agents/agent_helios.py +122 -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 +380 -0
  14. code_puppy/agents/agent_planning.py +165 -0
  15. code_puppy/agents/agent_python_programmer.py +167 -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 +2145 -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 +296 -0
  28. code_puppy/agents/pack/husky.py +307 -0
  29. code_puppy/agents/pack/retriever.py +380 -0
  30. code_puppy/agents/pack/shepherd.py +327 -0
  31. code_puppy/agents/pack/terrier.py +281 -0
  32. code_puppy/agents/pack/watchdog.py +357 -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 +674 -0
  47. code_puppy/chatgpt_codex_client.py +338 -0
  48. code_puppy/claude_cache_client.py +664 -0
  49. code_puppy/cli_runner.py +1038 -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 +526 -0
  57. code_puppy/command_line/command_handler.py +283 -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 +853 -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 +91 -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/skills_completion.py +160 -0
  97. code_puppy/command_line/uc_menu.py +893 -0
  98. code_puppy/command_line/utils.py +93 -0
  99. code_puppy/command_line/wiggum_state.py +78 -0
  100. code_puppy/config.py +1787 -0
  101. code_puppy/error_logging.py +133 -0
  102. code_puppy/gemini_code_assist.py +385 -0
  103. code_puppy/gemini_model.py +754 -0
  104. code_puppy/hook_engine/README.md +105 -0
  105. code_puppy/hook_engine/__init__.py +15 -0
  106. code_puppy/hook_engine/aliases.py +155 -0
  107. code_puppy/hook_engine/engine.py +195 -0
  108. code_puppy/hook_engine/executor.py +293 -0
  109. code_puppy/hook_engine/matcher.py +145 -0
  110. code_puppy/hook_engine/models.py +222 -0
  111. code_puppy/hook_engine/registry.py +106 -0
  112. code_puppy/hook_engine/validator.py +141 -0
  113. code_puppy/http_utils.py +361 -0
  114. code_puppy/keymap.py +128 -0
  115. code_puppy/main.py +10 -0
  116. code_puppy/mcp_/__init__.py +66 -0
  117. code_puppy/mcp_/async_lifecycle.py +286 -0
  118. code_puppy/mcp_/blocking_startup.py +469 -0
  119. code_puppy/mcp_/captured_stdio_server.py +275 -0
  120. code_puppy/mcp_/circuit_breaker.py +290 -0
  121. code_puppy/mcp_/config_wizard.py +507 -0
  122. code_puppy/mcp_/dashboard.py +308 -0
  123. code_puppy/mcp_/error_isolation.py +407 -0
  124. code_puppy/mcp_/examples/retry_example.py +226 -0
  125. code_puppy/mcp_/health_monitor.py +589 -0
  126. code_puppy/mcp_/managed_server.py +428 -0
  127. code_puppy/mcp_/manager.py +807 -0
  128. code_puppy/mcp_/mcp_logs.py +224 -0
  129. code_puppy/mcp_/registry.py +451 -0
  130. code_puppy/mcp_/retry_manager.py +337 -0
  131. code_puppy/mcp_/server_registry_catalog.py +1126 -0
  132. code_puppy/mcp_/status_tracker.py +355 -0
  133. code_puppy/mcp_/system_tools.py +209 -0
  134. code_puppy/mcp_prompts/__init__.py +1 -0
  135. code_puppy/mcp_prompts/hook_creator.py +103 -0
  136. code_puppy/messaging/__init__.py +255 -0
  137. code_puppy/messaging/bus.py +613 -0
  138. code_puppy/messaging/commands.py +167 -0
  139. code_puppy/messaging/markdown_patches.py +57 -0
  140. code_puppy/messaging/message_queue.py +361 -0
  141. code_puppy/messaging/messages.py +569 -0
  142. code_puppy/messaging/queue_console.py +271 -0
  143. code_puppy/messaging/renderers.py +311 -0
  144. code_puppy/messaging/rich_renderer.py +1153 -0
  145. code_puppy/messaging/spinner/__init__.py +83 -0
  146. code_puppy/messaging/spinner/console_spinner.py +240 -0
  147. code_puppy/messaging/spinner/spinner_base.py +96 -0
  148. code_puppy/messaging/subagent_console.py +460 -0
  149. code_puppy/model_factory.py +848 -0
  150. code_puppy/model_switching.py +63 -0
  151. code_puppy/model_utils.py +168 -0
  152. code_puppy/models.json +130 -0
  153. code_puppy/models_dev_api.json +1 -0
  154. code_puppy/models_dev_parser.py +592 -0
  155. code_puppy/plugins/__init__.py +186 -0
  156. code_puppy/plugins/agent_skills/__init__.py +22 -0
  157. code_puppy/plugins/agent_skills/config.py +175 -0
  158. code_puppy/plugins/agent_skills/discovery.py +136 -0
  159. code_puppy/plugins/agent_skills/downloader.py +392 -0
  160. code_puppy/plugins/agent_skills/installer.py +22 -0
  161. code_puppy/plugins/agent_skills/metadata.py +219 -0
  162. code_puppy/plugins/agent_skills/prompt_builder.py +100 -0
  163. code_puppy/plugins/agent_skills/register_callbacks.py +241 -0
  164. code_puppy/plugins/agent_skills/remote_catalog.py +322 -0
  165. code_puppy/plugins/agent_skills/skill_catalog.py +257 -0
  166. code_puppy/plugins/agent_skills/skills_install_menu.py +664 -0
  167. code_puppy/plugins/agent_skills/skills_menu.py +781 -0
  168. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  169. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  170. code_puppy/plugins/antigravity_oauth/antigravity_model.py +706 -0
  171. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  172. code_puppy/plugins/antigravity_oauth/constants.py +133 -0
  173. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  174. code_puppy/plugins/antigravity_oauth/register_callbacks.py +518 -0
  175. code_puppy/plugins/antigravity_oauth/storage.py +288 -0
  176. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  177. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  178. code_puppy/plugins/antigravity_oauth/transport.py +863 -0
  179. code_puppy/plugins/antigravity_oauth/utils.py +168 -0
  180. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  181. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  182. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
  183. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +176 -0
  184. code_puppy/plugins/chatgpt_oauth/test_plugin.py +295 -0
  185. code_puppy/plugins/chatgpt_oauth/utils.py +499 -0
  186. code_puppy/plugins/claude_code_hooks/__init__.py +1 -0
  187. code_puppy/plugins/claude_code_hooks/config.py +131 -0
  188. code_puppy/plugins/claude_code_hooks/register_callbacks.py +163 -0
  189. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  190. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  191. code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
  192. code_puppy/plugins/claude_code_oauth/config.py +52 -0
  193. code_puppy/plugins/claude_code_oauth/register_callbacks.py +453 -0
  194. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  195. code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +241 -0
  196. code_puppy/plugins/claude_code_oauth/utils.py +601 -0
  197. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  198. code_puppy/plugins/customizable_commands/register_callbacks.py +152 -0
  199. code_puppy/plugins/example_custom_command/README.md +280 -0
  200. code_puppy/plugins/example_custom_command/register_callbacks.py +48 -0
  201. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  202. code_puppy/plugins/file_permission_handler/register_callbacks.py +528 -0
  203. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  204. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  205. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  206. code_puppy/plugins/hook_creator/__init__.py +1 -0
  207. code_puppy/plugins/hook_creator/register_callbacks.py +33 -0
  208. code_puppy/plugins/hook_manager/__init__.py +1 -0
  209. code_puppy/plugins/hook_manager/config.py +277 -0
  210. code_puppy/plugins/hook_manager/hooks_menu.py +551 -0
  211. code_puppy/plugins/hook_manager/register_callbacks.py +205 -0
  212. code_puppy/plugins/oauth_puppy_html.py +224 -0
  213. code_puppy/plugins/scheduler/__init__.py +1 -0
  214. code_puppy/plugins/scheduler/register_callbacks.py +88 -0
  215. code_puppy/plugins/scheduler/scheduler_menu.py +522 -0
  216. code_puppy/plugins/scheduler/scheduler_wizard.py +341 -0
  217. code_puppy/plugins/shell_safety/__init__.py +6 -0
  218. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  219. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  220. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  221. code_puppy/plugins/synthetic_status/__init__.py +1 -0
  222. code_puppy/plugins/synthetic_status/register_callbacks.py +132 -0
  223. code_puppy/plugins/synthetic_status/status_api.py +147 -0
  224. code_puppy/plugins/universal_constructor/__init__.py +13 -0
  225. code_puppy/plugins/universal_constructor/models.py +138 -0
  226. code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
  227. code_puppy/plugins/universal_constructor/registry.py +302 -0
  228. code_puppy/plugins/universal_constructor/sandbox.py +584 -0
  229. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  230. code_puppy/pydantic_patches.py +317 -0
  231. code_puppy/reopenable_async_client.py +232 -0
  232. code_puppy/round_robin_model.py +150 -0
  233. code_puppy/scheduler/__init__.py +41 -0
  234. code_puppy/scheduler/__main__.py +9 -0
  235. code_puppy/scheduler/cli.py +118 -0
  236. code_puppy/scheduler/config.py +126 -0
  237. code_puppy/scheduler/daemon.py +280 -0
  238. code_puppy/scheduler/executor.py +155 -0
  239. code_puppy/scheduler/platform.py +19 -0
  240. code_puppy/scheduler/platform_unix.py +22 -0
  241. code_puppy/scheduler/platform_win.py +32 -0
  242. code_puppy/session_storage.py +338 -0
  243. code_puppy/status_display.py +257 -0
  244. code_puppy/summarization_agent.py +176 -0
  245. code_puppy/terminal_utils.py +418 -0
  246. code_puppy/tools/__init__.py +470 -0
  247. code_puppy/tools/agent_tools.py +616 -0
  248. code_puppy/tools/ask_user_question/__init__.py +26 -0
  249. code_puppy/tools/ask_user_question/constants.py +73 -0
  250. code_puppy/tools/ask_user_question/demo_tui.py +55 -0
  251. code_puppy/tools/ask_user_question/handler.py +232 -0
  252. code_puppy/tools/ask_user_question/models.py +304 -0
  253. code_puppy/tools/ask_user_question/registration.py +36 -0
  254. code_puppy/tools/ask_user_question/renderers.py +309 -0
  255. code_puppy/tools/ask_user_question/terminal_ui.py +329 -0
  256. code_puppy/tools/ask_user_question/theme.py +155 -0
  257. code_puppy/tools/ask_user_question/tui_loop.py +423 -0
  258. code_puppy/tools/browser/__init__.py +37 -0
  259. code_puppy/tools/browser/browser_control.py +289 -0
  260. code_puppy/tools/browser/browser_interactions.py +545 -0
  261. code_puppy/tools/browser/browser_locators.py +640 -0
  262. code_puppy/tools/browser/browser_manager.py +378 -0
  263. code_puppy/tools/browser/browser_navigation.py +251 -0
  264. code_puppy/tools/browser/browser_screenshot.py +179 -0
  265. code_puppy/tools/browser/browser_scripts.py +462 -0
  266. code_puppy/tools/browser/browser_workflows.py +221 -0
  267. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  268. code_puppy/tools/browser/terminal_command_tools.py +534 -0
  269. code_puppy/tools/browser/terminal_screenshot_tools.py +552 -0
  270. code_puppy/tools/browser/terminal_tools.py +525 -0
  271. code_puppy/tools/command_runner.py +1346 -0
  272. code_puppy/tools/common.py +1409 -0
  273. code_puppy/tools/display.py +84 -0
  274. code_puppy/tools/file_modifications.py +739 -0
  275. code_puppy/tools/file_operations.py +802 -0
  276. code_puppy/tools/scheduler_tools.py +412 -0
  277. code_puppy/tools/skills_tools.py +251 -0
  278. code_puppy/tools/subagent_context.py +158 -0
  279. code_puppy/tools/tools_content.py +51 -0
  280. code_puppy/tools/universal_constructor.py +889 -0
  281. code_puppy/uvx_detection.py +242 -0
  282. code_puppy/version_checker.py +82 -0
  283. newcode-0.1.1.data/data/code_puppy/models.json +130 -0
  284. newcode-0.1.1.data/data/code_puppy/models_dev_api.json +1 -0
  285. newcode-0.1.1.dist-info/METADATA +154 -0
  286. newcode-0.1.1.dist-info/RECORD +289 -0
  287. newcode-0.1.1.dist-info/WHEEL +4 -0
  288. newcode-0.1.1.dist-info/entry_points.txt +3 -0
  289. newcode-0.1.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,522 @@
1
+ """Interactive TUI for managing scheduled tasks.
2
+
3
+ Launch with /scheduler to browse, create, edit, and manage scheduled prompts.
4
+ Built with prompt_toolkit for proper interactive split-panel interface.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import time
10
+ from typing import List, Optional
11
+
12
+ from prompt_toolkit.application import Application
13
+ from prompt_toolkit.key_binding import KeyBindings
14
+ from prompt_toolkit.layout import Dimension, Layout, VSplit, Window
15
+ from prompt_toolkit.layout.controls import FormattedTextControl
16
+ from prompt_toolkit.widgets import Frame
17
+
18
+ from code_puppy.messaging import emit_error, emit_success, emit_warning
19
+ from code_puppy.scheduler.config import (
20
+ ScheduledTask,
21
+ add_task,
22
+ delete_task,
23
+ load_tasks,
24
+ toggle_task,
25
+ )
26
+ from code_puppy.scheduler.daemon import (
27
+ get_daemon_pid,
28
+ start_daemon_background,
29
+ stop_daemon,
30
+ )
31
+ from code_puppy.scheduler.executor import run_task_by_id
32
+ from code_puppy.tools.command_runner import set_awaiting_user_input
33
+
34
+ PAGE_SIZE = 12
35
+
36
+
37
+ class SchedulerMenu:
38
+ """Interactive TUI for managing scheduled tasks."""
39
+
40
+ def __init__(self):
41
+ """Initialize the scheduler menu."""
42
+ self.tasks: List[ScheduledTask] = []
43
+ self.selected_idx = 0
44
+ self.current_page = 0
45
+ self.result = None
46
+ self.menu_control: Optional[FormattedTextControl] = None
47
+ self.preview_control: Optional[FormattedTextControl] = None
48
+ self._refresh_data()
49
+
50
+ def _refresh_data(self) -> None:
51
+ """Refresh tasks from disk."""
52
+ try:
53
+ self.tasks = load_tasks()
54
+ except Exception as e:
55
+ emit_error(f"Failed to load tasks: {e}")
56
+ self.tasks = []
57
+
58
+ def _get_current_task(self) -> Optional[ScheduledTask]:
59
+ """Get the currently selected task."""
60
+ if 0 <= self.selected_idx < len(self.tasks):
61
+ return self.tasks[self.selected_idx]
62
+ return None
63
+
64
+ def _get_status_icon(self, task: ScheduledTask) -> tuple:
65
+ """Get status icon and color for a task."""
66
+ if not task.enabled:
67
+ return ("⏸", "fg:ansiyellow")
68
+ if task.last_status == "running":
69
+ return ("⏳", "fg:ansicyan")
70
+ if task.last_status == "success":
71
+ return ("✓", "fg:ansigreen")
72
+ if task.last_status == "failed":
73
+ return ("✗", "fg:ansired")
74
+ return ("○", "fg:ansibrightblack")
75
+
76
+ def _render_task_list(self) -> List:
77
+ """Render the task list panel."""
78
+ lines = []
79
+
80
+ # Header with daemon status
81
+ daemon_pid = get_daemon_pid()
82
+ if daemon_pid:
83
+ lines.append(
84
+ ("fg:ansigreen bold", f" Daemon: RUNNING (PID {daemon_pid})")
85
+ )
86
+ else:
87
+ lines.append(("fg:ansired bold", " Daemon: STOPPED"))
88
+ lines.append(("", "\n\n"))
89
+
90
+ if not self.tasks:
91
+ lines.append(("fg:ansiyellow", " No scheduled tasks.\n"))
92
+ lines.append(("fg:ansibrightblack", " Press 'n' to create one.\n"))
93
+ self._render_navigation_hints(lines)
94
+ return lines
95
+
96
+ # Pagination
97
+ total_pages = (len(self.tasks) + PAGE_SIZE - 1) // PAGE_SIZE
98
+ start_idx = self.current_page * PAGE_SIZE
99
+ end_idx = min(start_idx + PAGE_SIZE, len(self.tasks))
100
+
101
+ for i in range(start_idx, end_idx):
102
+ task = self.tasks[i]
103
+ is_selected = i == self.selected_idx
104
+ icon, icon_color = self._get_status_icon(task)
105
+
106
+ prefix = " > " if is_selected else " "
107
+ style = "bold" if is_selected else ""
108
+
109
+ lines.append((style, prefix))
110
+ lines.append((icon_color, icon))
111
+ lines.append((style, f" {task.name[:25]}"))
112
+ lines.append(("", "\n"))
113
+
114
+ lines.append(("", "\n"))
115
+ lines.append(
116
+ ("fg:ansibrightblack", f" Page {self.current_page + 1}/{total_pages}\n")
117
+ )
118
+ self._render_navigation_hints(lines)
119
+ return lines
120
+
121
+ def _render_navigation_hints(self, lines: List) -> None:
122
+ """Render navigation hints."""
123
+ lines.append(("", "\n"))
124
+ lines.append(("fg:ansibrightblack", " ↑/↓ j/k "))
125
+ lines.append(("", "Navigate "))
126
+ lines.append(("fg:ansibrightblack", "←/→ "))
127
+ lines.append(("", "Page\n"))
128
+ lines.append(("fg:ansigreen", " Space "))
129
+ lines.append(("", "Toggle "))
130
+ lines.append(("fg:ansicyan", " n "))
131
+ lines.append(("", "New Task\n"))
132
+ lines.append(("fg:ansiyellow", " r "))
133
+ lines.append(("", "Run Now "))
134
+ lines.append(("fg:ansimagenta", " t "))
135
+ lines.append(("", "Tail Log\n"))
136
+ lines.append(("fg:ansired", " d "))
137
+ lines.append(("", "Delete "))
138
+ lines.append(("fg:ansibrightblack", " s "))
139
+ lines.append(("", "Start/Stop Daemon\n"))
140
+ lines.append(("fg:ansired", " q "))
141
+ lines.append(("", "Exit"))
142
+
143
+ def _render_task_details(self) -> List:
144
+ """Render the task details panel."""
145
+ lines = []
146
+ lines.append(("dim cyan", " TASK DETAILS\n\n"))
147
+
148
+ task = self._get_current_task()
149
+ if not task:
150
+ lines.append(("fg:ansiyellow", " No task selected.\n\n"))
151
+ lines.append(("fg:ansibrightblack", " Select a task or press 'n'\n"))
152
+ lines.append(("fg:ansibrightblack", " to create a new one."))
153
+ return lines
154
+
155
+ # Status
156
+ icon, color = self._get_status_icon(task)
157
+ status_text = "Enabled" if task.enabled else "Disabled"
158
+ lines.append(("bold", " Status: "))
159
+ lines.append((color, f"{icon} {status_text}\n\n"))
160
+
161
+ # Name
162
+ lines.append(("bold", f" {task.name}\n\n"))
163
+
164
+ # Schedule
165
+ lines.append(("bold", " Schedule: "))
166
+ lines.append(("", f"{task.schedule_type} ({task.schedule_value})\n\n"))
167
+
168
+ # Agent & Model
169
+ lines.append(("bold", " Agent: "))
170
+ lines.append(("fg:ansicyan", f"{task.agent}\n"))
171
+ if task.model:
172
+ lines.append(("bold", " Model: "))
173
+ lines.append(("fg:ansicyan", f"{task.model}\n"))
174
+ lines.append(("", "\n"))
175
+
176
+ # Prompt (truncated)
177
+ lines.append(("bold", " Prompt:\n"))
178
+ prompt_preview = (
179
+ task.prompt[:150] + "..." if len(task.prompt) > 150 else task.prompt
180
+ )
181
+ for line in prompt_preview.split("\n")[:4]:
182
+ lines.append(("fg:ansibrightblack", f" {line}\n"))
183
+ lines.append(("", "\n"))
184
+
185
+ # Last run
186
+ if task.last_run:
187
+ lines.append(("bold", " Last Run: "))
188
+ lines.append(("fg:ansibrightblack", f"{task.last_run[:19]}\n"))
189
+ lines.append(("bold", " Exit Code: "))
190
+ code_color = "fg:ansigreen" if task.last_exit_code == 0 else "fg:ansired"
191
+ lines.append((code_color, f"{task.last_exit_code}\n"))
192
+
193
+ # Log file
194
+ lines.append(("", "\n"))
195
+ lines.append(("bold", " Log: "))
196
+ log_short = task.log_file[-40:] if len(task.log_file) > 40 else task.log_file
197
+ lines.append(("fg:ansibrightblack", f"...{log_short}"))
198
+
199
+ return lines
200
+
201
+ def update_display(self) -> None:
202
+ """Update the display."""
203
+ if self.menu_control:
204
+ self.menu_control.text = self._render_task_list()
205
+ if self.preview_control:
206
+ self.preview_control.text = self._render_task_details()
207
+
208
+ def run(self) -> Optional[str]:
209
+ """Run the interactive menu."""
210
+ self.result = None
211
+ self.menu_control = FormattedTextControl(text="")
212
+ self.preview_control = FormattedTextControl(text="")
213
+
214
+ menu_window = Window(
215
+ content=self.menu_control, wrap_lines=True, width=Dimension(weight=40)
216
+ )
217
+ preview_window = Window(
218
+ content=self.preview_control, wrap_lines=True, width=Dimension(weight=60)
219
+ )
220
+ menu_frame = Frame(menu_window, title="📅 Scheduled Tasks")
221
+ preview_frame = Frame(preview_window, title="Details")
222
+ root_container = VSplit([menu_frame, preview_frame])
223
+
224
+ kb = KeyBindings()
225
+
226
+ @kb.add("up")
227
+ @kb.add("k")
228
+ def _(event):
229
+ if self.selected_idx > 0:
230
+ self.selected_idx -= 1
231
+ self.current_page = self.selected_idx // PAGE_SIZE
232
+ self.update_display()
233
+
234
+ @kb.add("down")
235
+ @kb.add("j")
236
+ def _(event):
237
+ if self.selected_idx < len(self.tasks) - 1:
238
+ self.selected_idx += 1
239
+ self.current_page = self.selected_idx // PAGE_SIZE
240
+ self.update_display()
241
+
242
+ @kb.add("left")
243
+ def _(event):
244
+ if self.current_page > 0:
245
+ self.current_page -= 1
246
+ self.selected_idx = self.current_page * PAGE_SIZE
247
+ self.update_display()
248
+
249
+ @kb.add("right")
250
+ def _(event):
251
+ total_pages = (len(self.tasks) + PAGE_SIZE - 1) // PAGE_SIZE
252
+ if self.current_page < total_pages - 1:
253
+ self.current_page += 1
254
+ self.selected_idx = self.current_page * PAGE_SIZE
255
+ self.update_display()
256
+
257
+ @kb.add("space")
258
+ def _(event):
259
+ task = self._get_current_task()
260
+ if task:
261
+ toggle_task(task.id)
262
+ self._refresh_data()
263
+ self.result = "changed"
264
+ self.update_display()
265
+
266
+ @kb.add("n")
267
+ def _(event):
268
+ self.result = "new_task"
269
+ event.app.exit()
270
+
271
+ @kb.add("r")
272
+ def _(event):
273
+ self.result = "run_task"
274
+ event.app.exit()
275
+
276
+ @kb.add("t")
277
+ def _(event):
278
+ self.result = "tail_log"
279
+ event.app.exit()
280
+
281
+ @kb.add("d")
282
+ def _(event):
283
+ self.result = "delete_task"
284
+ event.app.exit()
285
+
286
+ @kb.add("s")
287
+ def _(event):
288
+ self.result = "toggle_daemon"
289
+ event.app.exit()
290
+
291
+ @kb.add("q")
292
+ @kb.add("escape")
293
+ def _(event):
294
+ self.result = "quit"
295
+ event.app.exit()
296
+
297
+ @kb.add("c-c")
298
+ def _(event):
299
+ self.result = "quit"
300
+ event.app.exit()
301
+
302
+ layout = Layout(root_container)
303
+ app = Application(
304
+ layout=layout, key_bindings=kb, full_screen=False, mouse_support=False
305
+ )
306
+
307
+ set_awaiting_user_input(True)
308
+ sys.stdout.write("\033[?1049h") # Enter alternate buffer
309
+ sys.stdout.write("\033[2J\033[H")
310
+ sys.stdout.flush()
311
+ time.sleep(0.05)
312
+
313
+ try:
314
+ self.update_display()
315
+ sys.stdout.write("\033[2J\033[H")
316
+ sys.stdout.flush()
317
+ app.run(in_thread=True)
318
+ finally:
319
+ sys.stdout.write("\033[?1049l") # Exit alternate buffer
320
+ sys.stdout.flush()
321
+ try:
322
+ import termios
323
+
324
+ termios.tcflush(sys.stdin.fileno(), termios.TCIFLUSH)
325
+ except (ImportError, Exception):
326
+ pass
327
+ time.sleep(0.1)
328
+ set_awaiting_user_input(False)
329
+
330
+ return self.result
331
+
332
+
333
+ def _create_new_task() -> Optional[ScheduledTask]:
334
+ """Interactive TUI wizard to create a new task."""
335
+ from code_puppy.plugins.scheduler.scheduler_wizard import create_task_wizard
336
+
337
+ result = create_task_wizard()
338
+ if not result:
339
+ return None
340
+
341
+ return ScheduledTask(
342
+ name=result["name"],
343
+ prompt=result["prompt"],
344
+ agent=result["agent"],
345
+ model=result["model"],
346
+ schedule_type=result["schedule_type"],
347
+ schedule_value=result["schedule_value"],
348
+ working_directory=result["working_directory"],
349
+ )
350
+
351
+
352
+ def _tail_log_file(log_file: str) -> None:
353
+ """Interactive log file viewer with proper keybindings."""
354
+ import threading
355
+
356
+ from prompt_toolkit.application import Application
357
+ from prompt_toolkit.key_binding import KeyBindings
358
+ from prompt_toolkit.layout import Layout, Window
359
+ from prompt_toolkit.layout.controls import FormattedTextControl
360
+
361
+ from code_puppy.command_line.utils import safe_input
362
+ from code_puppy.tools.command_runner import set_awaiting_user_input
363
+
364
+ if not os.path.exists(log_file):
365
+ print(f"\n⚠️ Log file not found: {log_file}")
366
+ safe_input("\nPress Enter to continue...")
367
+ return
368
+
369
+ # Read initial content
370
+ try:
371
+ with open(log_file, "r") as f:
372
+ content = f.read()
373
+ # Keep last 200 lines
374
+ lines = content.split("\n")
375
+ if len(lines) > 200:
376
+ lines = lines[-200:]
377
+ content = "\n".join(lines)
378
+ except Exception as e:
379
+ print(f"\n⚠️ Error reading log: {e}")
380
+ safe_input("\nPress Enter to continue...")
381
+ return
382
+
383
+ # State
384
+ log_content = [content]
385
+ stop_tailing = [False]
386
+
387
+ def render_log():
388
+ lines = []
389
+ lines.append(("bold fg:ansicyan", f"📄 Log: {log_file}\n"))
390
+ lines.append(("fg:ansiyellow", "Press q, Esc, or d to close\n"))
391
+ lines.append(("fg:ansibrightblack", "-" * 60 + "\n\n"))
392
+ lines.append(("", log_content[0]))
393
+ return lines
394
+
395
+ control = FormattedTextControl(text=render_log)
396
+ window = Window(content=control, wrap_lines=True)
397
+
398
+ kb = KeyBindings()
399
+
400
+ @kb.add("q")
401
+ @kb.add("d")
402
+ @kb.add("escape")
403
+ @kb.add("c-c")
404
+ def _(event):
405
+ stop_tailing[0] = True
406
+ event.app.exit()
407
+
408
+ layout = Layout(window)
409
+ app = Application(
410
+ layout=layout, key_bindings=kb, full_screen=False, mouse_support=False
411
+ )
412
+
413
+ # Background thread to tail the file
414
+ def tail_thread():
415
+ try:
416
+ with open(log_file, "r") as f:
417
+ f.seek(0, 2) # Go to end
418
+ while not stop_tailing[0]:
419
+ line = f.readline()
420
+ if line:
421
+ log_content[0] += line
422
+ # Keep only last 200 lines
423
+ lines = log_content[0].split("\n")
424
+ if len(lines) > 200:
425
+ log_content[0] = "\n".join(lines[-200:])
426
+ try:
427
+ app.invalidate()
428
+ except Exception:
429
+ pass
430
+ else:
431
+ time.sleep(0.3)
432
+ except Exception:
433
+ pass
434
+
435
+ # Start tail thread
436
+ tailer = threading.Thread(target=tail_thread, daemon=True)
437
+ tailer.start()
438
+
439
+ set_awaiting_user_input(True)
440
+ try:
441
+ app.run(in_thread=True)
442
+ finally:
443
+ stop_tailing[0] = True
444
+ set_awaiting_user_input(False)
445
+
446
+
447
+ def show_scheduler_menu() -> bool:
448
+ """Launch the interactive scheduler TUI menu."""
449
+ changes_made = False
450
+
451
+ while True:
452
+ menu = SchedulerMenu()
453
+ result = menu.run()
454
+ task = menu._get_current_task()
455
+
456
+ if result == "new_task":
457
+ new_task = _create_new_task()
458
+ if new_task:
459
+ add_task(new_task)
460
+ emit_success(f"Created task: {new_task.name}")
461
+ changes_made = True
462
+ continue
463
+
464
+ elif result == "run_task":
465
+ if task:
466
+ print(f"\n⏳ Running task: {task.name}...")
467
+ success, msg = run_task_by_id(task.id)
468
+ if success:
469
+ emit_success(msg)
470
+ else:
471
+ emit_error(msg)
472
+ from code_puppy.command_line.utils import safe_input
473
+
474
+ safe_input("\nPress Enter to continue...")
475
+ changes_made = True
476
+ continue
477
+
478
+ elif result == "tail_log":
479
+ if task and task.log_file:
480
+ _tail_log_file(task.log_file)
481
+ continue
482
+
483
+ elif result == "delete_task":
484
+ if task:
485
+ from code_puppy.command_line.utils import safe_input
486
+
487
+ confirm = safe_input(f"\nDelete '{task.name}'? (y/N): ").strip().lower()
488
+ if confirm in ("y", "yes"):
489
+ delete_task(task.id)
490
+ emit_warning(f"Deleted task: {task.name}")
491
+ changes_made = True
492
+ continue
493
+
494
+ elif result == "toggle_daemon":
495
+ pid = get_daemon_pid()
496
+ if pid:
497
+ print("\n⏳ Stopping daemon...")
498
+ if stop_daemon():
499
+ emit_success("Daemon stopped")
500
+ else:
501
+ emit_error("Failed to stop daemon")
502
+ else:
503
+ print("\n⏳ Starting daemon in background...")
504
+ if start_daemon_background():
505
+ emit_success("Daemon started")
506
+ else:
507
+ emit_error("Failed to start daemon")
508
+ from code_puppy.command_line.utils import safe_input
509
+
510
+ safe_input("\nPress Enter to continue...")
511
+ continue
512
+
513
+ elif result == "changed":
514
+ changes_made = True
515
+ continue
516
+
517
+ elif result == "quit":
518
+ break
519
+ else:
520
+ break
521
+
522
+ return changes_made