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.
- code_puppy/__init__.py +10 -0
- code_puppy/__main__.py +10 -0
- code_puppy/agents/__init__.py +31 -0
- code_puppy/agents/agent_c_reviewer.py +155 -0
- code_puppy/agents/agent_code_puppy.py +147 -0
- code_puppy/agents/agent_code_reviewer.py +90 -0
- code_puppy/agents/agent_cpp_reviewer.py +132 -0
- code_puppy/agents/agent_creator_agent.py +630 -0
- code_puppy/agents/agent_golang_reviewer.py +151 -0
- code_puppy/agents/agent_helios.py +122 -0
- code_puppy/agents/agent_javascript_reviewer.py +160 -0
- code_puppy/agents/agent_manager.py +742 -0
- code_puppy/agents/agent_pack_leader.py +380 -0
- code_puppy/agents/agent_planning.py +165 -0
- code_puppy/agents/agent_python_programmer.py +167 -0
- code_puppy/agents/agent_python_reviewer.py +90 -0
- code_puppy/agents/agent_qa_expert.py +163 -0
- code_puppy/agents/agent_qa_kitten.py +208 -0
- code_puppy/agents/agent_scheduler.py +121 -0
- code_puppy/agents/agent_security_auditor.py +181 -0
- code_puppy/agents/agent_terminal_qa.py +323 -0
- code_puppy/agents/agent_typescript_reviewer.py +166 -0
- code_puppy/agents/base_agent.py +2145 -0
- code_puppy/agents/event_stream_handler.py +348 -0
- code_puppy/agents/json_agent.py +202 -0
- code_puppy/agents/pack/__init__.py +34 -0
- code_puppy/agents/pack/bloodhound.py +296 -0
- code_puppy/agents/pack/husky.py +307 -0
- code_puppy/agents/pack/retriever.py +380 -0
- code_puppy/agents/pack/shepherd.py +327 -0
- code_puppy/agents/pack/terrier.py +281 -0
- code_puppy/agents/pack/watchdog.py +357 -0
- code_puppy/agents/prompt_reviewer.py +145 -0
- code_puppy/agents/subagent_stream_handler.py +276 -0
- code_puppy/api/__init__.py +13 -0
- code_puppy/api/app.py +169 -0
- code_puppy/api/main.py +21 -0
- code_puppy/api/pty_manager.py +453 -0
- code_puppy/api/routers/__init__.py +12 -0
- code_puppy/api/routers/agents.py +36 -0
- code_puppy/api/routers/commands.py +217 -0
- code_puppy/api/routers/config.py +75 -0
- code_puppy/api/routers/sessions.py +234 -0
- code_puppy/api/templates/terminal.html +361 -0
- code_puppy/api/websocket.py +154 -0
- code_puppy/callbacks.py +674 -0
- code_puppy/chatgpt_codex_client.py +338 -0
- code_puppy/claude_cache_client.py +664 -0
- code_puppy/cli_runner.py +1038 -0
- code_puppy/command_line/__init__.py +1 -0
- code_puppy/command_line/add_model_menu.py +1092 -0
- code_puppy/command_line/agent_menu.py +662 -0
- code_puppy/command_line/attachments.py +395 -0
- code_puppy/command_line/autosave_menu.py +704 -0
- code_puppy/command_line/clipboard.py +527 -0
- code_puppy/command_line/colors_menu.py +526 -0
- code_puppy/command_line/command_handler.py +283 -0
- code_puppy/command_line/command_registry.py +150 -0
- code_puppy/command_line/config_commands.py +719 -0
- code_puppy/command_line/core_commands.py +853 -0
- code_puppy/command_line/diff_menu.py +865 -0
- code_puppy/command_line/file_path_completion.py +73 -0
- code_puppy/command_line/load_context_completion.py +52 -0
- code_puppy/command_line/mcp/__init__.py +10 -0
- code_puppy/command_line/mcp/base.py +32 -0
- code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
- code_puppy/command_line/mcp/custom_server_form.py +688 -0
- code_puppy/command_line/mcp/custom_server_installer.py +195 -0
- code_puppy/command_line/mcp/edit_command.py +148 -0
- code_puppy/command_line/mcp/handler.py +138 -0
- code_puppy/command_line/mcp/help_command.py +147 -0
- code_puppy/command_line/mcp/install_command.py +214 -0
- code_puppy/command_line/mcp/install_menu.py +705 -0
- code_puppy/command_line/mcp/list_command.py +94 -0
- code_puppy/command_line/mcp/logs_command.py +235 -0
- code_puppy/command_line/mcp/remove_command.py +82 -0
- code_puppy/command_line/mcp/restart_command.py +100 -0
- code_puppy/command_line/mcp/search_command.py +123 -0
- code_puppy/command_line/mcp/start_all_command.py +135 -0
- code_puppy/command_line/mcp/start_command.py +117 -0
- code_puppy/command_line/mcp/status_command.py +184 -0
- code_puppy/command_line/mcp/stop_all_command.py +112 -0
- code_puppy/command_line/mcp/stop_command.py +80 -0
- code_puppy/command_line/mcp/test_command.py +107 -0
- code_puppy/command_line/mcp/utils.py +129 -0
- code_puppy/command_line/mcp/wizard_utils.py +334 -0
- code_puppy/command_line/mcp_completion.py +174 -0
- code_puppy/command_line/model_picker_completion.py +197 -0
- code_puppy/command_line/model_settings_menu.py +932 -0
- code_puppy/command_line/motd.py +91 -0
- code_puppy/command_line/onboarding_slides.py +179 -0
- code_puppy/command_line/onboarding_wizard.py +342 -0
- code_puppy/command_line/pin_command_completion.py +329 -0
- code_puppy/command_line/prompt_toolkit_completion.py +846 -0
- code_puppy/command_line/session_commands.py +302 -0
- code_puppy/command_line/skills_completion.py +160 -0
- code_puppy/command_line/uc_menu.py +893 -0
- code_puppy/command_line/utils.py +93 -0
- code_puppy/command_line/wiggum_state.py +78 -0
- code_puppy/config.py +1787 -0
- code_puppy/error_logging.py +133 -0
- code_puppy/gemini_code_assist.py +385 -0
- code_puppy/gemini_model.py +754 -0
- code_puppy/hook_engine/README.md +105 -0
- code_puppy/hook_engine/__init__.py +15 -0
- code_puppy/hook_engine/aliases.py +155 -0
- code_puppy/hook_engine/engine.py +195 -0
- code_puppy/hook_engine/executor.py +293 -0
- code_puppy/hook_engine/matcher.py +145 -0
- code_puppy/hook_engine/models.py +222 -0
- code_puppy/hook_engine/registry.py +106 -0
- code_puppy/hook_engine/validator.py +141 -0
- code_puppy/http_utils.py +361 -0
- code_puppy/keymap.py +128 -0
- code_puppy/main.py +10 -0
- code_puppy/mcp_/__init__.py +66 -0
- code_puppy/mcp_/async_lifecycle.py +286 -0
- code_puppy/mcp_/blocking_startup.py +469 -0
- code_puppy/mcp_/captured_stdio_server.py +275 -0
- code_puppy/mcp_/circuit_breaker.py +290 -0
- code_puppy/mcp_/config_wizard.py +507 -0
- code_puppy/mcp_/dashboard.py +308 -0
- code_puppy/mcp_/error_isolation.py +407 -0
- code_puppy/mcp_/examples/retry_example.py +226 -0
- code_puppy/mcp_/health_monitor.py +589 -0
- code_puppy/mcp_/managed_server.py +428 -0
- code_puppy/mcp_/manager.py +807 -0
- code_puppy/mcp_/mcp_logs.py +224 -0
- code_puppy/mcp_/registry.py +451 -0
- code_puppy/mcp_/retry_manager.py +337 -0
- code_puppy/mcp_/server_registry_catalog.py +1126 -0
- code_puppy/mcp_/status_tracker.py +355 -0
- code_puppy/mcp_/system_tools.py +209 -0
- code_puppy/mcp_prompts/__init__.py +1 -0
- code_puppy/mcp_prompts/hook_creator.py +103 -0
- code_puppy/messaging/__init__.py +255 -0
- code_puppy/messaging/bus.py +613 -0
- code_puppy/messaging/commands.py +167 -0
- code_puppy/messaging/markdown_patches.py +57 -0
- code_puppy/messaging/message_queue.py +361 -0
- code_puppy/messaging/messages.py +569 -0
- code_puppy/messaging/queue_console.py +271 -0
- code_puppy/messaging/renderers.py +311 -0
- code_puppy/messaging/rich_renderer.py +1153 -0
- code_puppy/messaging/spinner/__init__.py +83 -0
- code_puppy/messaging/spinner/console_spinner.py +240 -0
- code_puppy/messaging/spinner/spinner_base.py +96 -0
- code_puppy/messaging/subagent_console.py +460 -0
- code_puppy/model_factory.py +848 -0
- code_puppy/model_switching.py +63 -0
- code_puppy/model_utils.py +168 -0
- code_puppy/models.json +130 -0
- code_puppy/models_dev_api.json +1 -0
- code_puppy/models_dev_parser.py +592 -0
- code_puppy/plugins/__init__.py +186 -0
- code_puppy/plugins/agent_skills/__init__.py +22 -0
- code_puppy/plugins/agent_skills/config.py +175 -0
- code_puppy/plugins/agent_skills/discovery.py +136 -0
- code_puppy/plugins/agent_skills/downloader.py +392 -0
- code_puppy/plugins/agent_skills/installer.py +22 -0
- code_puppy/plugins/agent_skills/metadata.py +219 -0
- code_puppy/plugins/agent_skills/prompt_builder.py +100 -0
- code_puppy/plugins/agent_skills/register_callbacks.py +241 -0
- code_puppy/plugins/agent_skills/remote_catalog.py +322 -0
- code_puppy/plugins/agent_skills/skill_catalog.py +257 -0
- code_puppy/plugins/agent_skills/skills_install_menu.py +664 -0
- code_puppy/plugins/agent_skills/skills_menu.py +781 -0
- code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
- code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
- code_puppy/plugins/antigravity_oauth/antigravity_model.py +706 -0
- code_puppy/plugins/antigravity_oauth/config.py +42 -0
- code_puppy/plugins/antigravity_oauth/constants.py +133 -0
- code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
- code_puppy/plugins/antigravity_oauth/register_callbacks.py +518 -0
- code_puppy/plugins/antigravity_oauth/storage.py +288 -0
- code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
- code_puppy/plugins/antigravity_oauth/token.py +167 -0
- code_puppy/plugins/antigravity_oauth/transport.py +863 -0
- code_puppy/plugins/antigravity_oauth/utils.py +168 -0
- code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
- code_puppy/plugins/chatgpt_oauth/config.py +52 -0
- code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +176 -0
- code_puppy/plugins/chatgpt_oauth/test_plugin.py +295 -0
- code_puppy/plugins/chatgpt_oauth/utils.py +499 -0
- code_puppy/plugins/claude_code_hooks/__init__.py +1 -0
- code_puppy/plugins/claude_code_hooks/config.py +131 -0
- code_puppy/plugins/claude_code_hooks/register_callbacks.py +163 -0
- code_puppy/plugins/claude_code_oauth/README.md +167 -0
- code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
- code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
- code_puppy/plugins/claude_code_oauth/config.py +52 -0
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +453 -0
- code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
- code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +241 -0
- code_puppy/plugins/claude_code_oauth/utils.py +601 -0
- code_puppy/plugins/customizable_commands/__init__.py +0 -0
- code_puppy/plugins/customizable_commands/register_callbacks.py +152 -0
- code_puppy/plugins/example_custom_command/README.md +280 -0
- code_puppy/plugins/example_custom_command/register_callbacks.py +48 -0
- code_puppy/plugins/file_permission_handler/__init__.py +4 -0
- code_puppy/plugins/file_permission_handler/register_callbacks.py +528 -0
- code_puppy/plugins/frontend_emitter/__init__.py +25 -0
- code_puppy/plugins/frontend_emitter/emitter.py +121 -0
- code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
- code_puppy/plugins/hook_creator/__init__.py +1 -0
- code_puppy/plugins/hook_creator/register_callbacks.py +33 -0
- code_puppy/plugins/hook_manager/__init__.py +1 -0
- code_puppy/plugins/hook_manager/config.py +277 -0
- code_puppy/plugins/hook_manager/hooks_menu.py +551 -0
- code_puppy/plugins/hook_manager/register_callbacks.py +205 -0
- code_puppy/plugins/oauth_puppy_html.py +224 -0
- code_puppy/plugins/scheduler/__init__.py +1 -0
- code_puppy/plugins/scheduler/register_callbacks.py +88 -0
- code_puppy/plugins/scheduler/scheduler_menu.py +522 -0
- code_puppy/plugins/scheduler/scheduler_wizard.py +341 -0
- code_puppy/plugins/shell_safety/__init__.py +6 -0
- code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
- code_puppy/plugins/shell_safety/command_cache.py +156 -0
- code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
- code_puppy/plugins/synthetic_status/__init__.py +1 -0
- code_puppy/plugins/synthetic_status/register_callbacks.py +132 -0
- code_puppy/plugins/synthetic_status/status_api.py +147 -0
- code_puppy/plugins/universal_constructor/__init__.py +13 -0
- code_puppy/plugins/universal_constructor/models.py +138 -0
- code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
- code_puppy/plugins/universal_constructor/registry.py +302 -0
- code_puppy/plugins/universal_constructor/sandbox.py +584 -0
- code_puppy/prompts/antigravity_system_prompt.md +1 -0
- code_puppy/pydantic_patches.py +317 -0
- code_puppy/reopenable_async_client.py +232 -0
- code_puppy/round_robin_model.py +150 -0
- code_puppy/scheduler/__init__.py +41 -0
- code_puppy/scheduler/__main__.py +9 -0
- code_puppy/scheduler/cli.py +118 -0
- code_puppy/scheduler/config.py +126 -0
- code_puppy/scheduler/daemon.py +280 -0
- code_puppy/scheduler/executor.py +155 -0
- code_puppy/scheduler/platform.py +19 -0
- code_puppy/scheduler/platform_unix.py +22 -0
- code_puppy/scheduler/platform_win.py +32 -0
- code_puppy/session_storage.py +338 -0
- code_puppy/status_display.py +257 -0
- code_puppy/summarization_agent.py +176 -0
- code_puppy/terminal_utils.py +418 -0
- code_puppy/tools/__init__.py +470 -0
- code_puppy/tools/agent_tools.py +616 -0
- code_puppy/tools/ask_user_question/__init__.py +26 -0
- code_puppy/tools/ask_user_question/constants.py +73 -0
- code_puppy/tools/ask_user_question/demo_tui.py +55 -0
- code_puppy/tools/ask_user_question/handler.py +232 -0
- code_puppy/tools/ask_user_question/models.py +304 -0
- code_puppy/tools/ask_user_question/registration.py +36 -0
- code_puppy/tools/ask_user_question/renderers.py +309 -0
- code_puppy/tools/ask_user_question/terminal_ui.py +329 -0
- code_puppy/tools/ask_user_question/theme.py +155 -0
- code_puppy/tools/ask_user_question/tui_loop.py +423 -0
- code_puppy/tools/browser/__init__.py +37 -0
- code_puppy/tools/browser/browser_control.py +289 -0
- code_puppy/tools/browser/browser_interactions.py +545 -0
- code_puppy/tools/browser/browser_locators.py +640 -0
- code_puppy/tools/browser/browser_manager.py +378 -0
- code_puppy/tools/browser/browser_navigation.py +251 -0
- code_puppy/tools/browser/browser_screenshot.py +179 -0
- code_puppy/tools/browser/browser_scripts.py +462 -0
- code_puppy/tools/browser/browser_workflows.py +221 -0
- code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
- code_puppy/tools/browser/terminal_command_tools.py +534 -0
- code_puppy/tools/browser/terminal_screenshot_tools.py +552 -0
- code_puppy/tools/browser/terminal_tools.py +525 -0
- code_puppy/tools/command_runner.py +1346 -0
- code_puppy/tools/common.py +1409 -0
- code_puppy/tools/display.py +84 -0
- code_puppy/tools/file_modifications.py +739 -0
- code_puppy/tools/file_operations.py +802 -0
- code_puppy/tools/scheduler_tools.py +412 -0
- code_puppy/tools/skills_tools.py +251 -0
- code_puppy/tools/subagent_context.py +158 -0
- code_puppy/tools/tools_content.py +51 -0
- code_puppy/tools/universal_constructor.py +889 -0
- code_puppy/uvx_detection.py +242 -0
- code_puppy/version_checker.py +82 -0
- newcode-0.1.1.data/data/code_puppy/models.json +130 -0
- newcode-0.1.1.data/data/code_puppy/models_dev_api.json +1 -0
- newcode-0.1.1.dist-info/METADATA +154 -0
- newcode-0.1.1.dist-info/RECORD +289 -0
- newcode-0.1.1.dist-info/WHEEL +4 -0
- newcode-0.1.1.dist-info/entry_points.txt +3 -0
- 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
|