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,341 @@
1
+ """TUI-based wizard for creating scheduled tasks.
2
+
3
+ Provides interactive menus with arrow-key navigation for selecting
4
+ schedule type, agent, model, and other task parameters.
5
+ """
6
+
7
+ from typing import List, Optional, Tuple
8
+
9
+ from prompt_toolkit.application import Application
10
+ from prompt_toolkit.key_binding import KeyBindings
11
+ from prompt_toolkit.layout import Layout, Window
12
+ from prompt_toolkit.layout.controls import FormattedTextControl
13
+
14
+ from code_puppy.tools.command_runner import set_awaiting_user_input
15
+
16
+
17
+ class SelectionMenu:
18
+ """Simple arrow-key selection menu."""
19
+
20
+ def __init__(
21
+ self, title: str, choices: List[str], descriptions: Optional[List[str]] = None
22
+ ):
23
+ self.title = title
24
+ self.choices = choices
25
+ self.descriptions = descriptions or [""] * len(choices)
26
+ self.selected_idx = 0
27
+ self.result: Optional[str] = None
28
+ self.cancelled = False
29
+
30
+ def _render(self) -> List:
31
+ """Render the menu."""
32
+ lines = []
33
+
34
+ # Title
35
+ lines.append(("bold fg:ansicyan", f"\n {self.title}\n\n"))
36
+
37
+ # Choices
38
+ for i, choice in enumerate(self.choices):
39
+ is_selected = i == self.selected_idx
40
+ prefix = " ❯ " if is_selected else " "
41
+
42
+ if is_selected:
43
+ lines.append(("bold fg:ansigreen", prefix))
44
+ lines.append(("bold fg:ansigreen", f"{choice}"))
45
+ else:
46
+ lines.append(("", prefix))
47
+ lines.append(("fg:ansibrightblack", f"{choice}"))
48
+
49
+ # Show description for selected item
50
+ if is_selected and self.descriptions[i]:
51
+ lines.append(("fg:ansibrightblack", f" - {self.descriptions[i]}"))
52
+
53
+ lines.append(("", "\n"))
54
+
55
+ # Help text
56
+ lines.append(("", "\n"))
57
+ lines.append(("fg:ansibrightblack", " ↑/↓ Navigate "))
58
+ lines.append(("fg:ansigreen", "Enter "))
59
+ lines.append(("fg:ansibrightblack", "Select "))
60
+ lines.append(("fg:ansired", "Ctrl+C "))
61
+ lines.append(("fg:ansibrightblack", "Cancel"))
62
+
63
+ return lines
64
+
65
+ def run(self) -> Optional[str]:
66
+ """Run the selection menu. Returns selected choice or None if cancelled."""
67
+ control = FormattedTextControl(text="")
68
+ window = Window(content=control, wrap_lines=True)
69
+
70
+ kb = KeyBindings()
71
+
72
+ @kb.add("up")
73
+ @kb.add("k")
74
+ def _(event):
75
+ if self.selected_idx > 0:
76
+ self.selected_idx -= 1
77
+ control.text = self._render()
78
+
79
+ @kb.add("down")
80
+ @kb.add("j")
81
+ def _(event):
82
+ if self.selected_idx < len(self.choices) - 1:
83
+ self.selected_idx += 1
84
+ control.text = self._render()
85
+
86
+ @kb.add("enter")
87
+ def _(event):
88
+ self.result = self.choices[self.selected_idx]
89
+ event.app.exit()
90
+
91
+ @kb.add("c-c")
92
+ @kb.add("escape")
93
+ def _(event):
94
+ self.cancelled = True
95
+ event.app.exit()
96
+
97
+ layout = Layout(window)
98
+ app = Application(layout=layout, key_bindings=kb, full_screen=False)
99
+
100
+ set_awaiting_user_input(True)
101
+ try:
102
+ control.text = self._render()
103
+ app.run(in_thread=True)
104
+ finally:
105
+ set_awaiting_user_input(False)
106
+
107
+ if self.cancelled:
108
+ return None
109
+ return self.result
110
+
111
+
112
+ class TextInputMenu:
113
+ """Simple text input with TUI styling."""
114
+
115
+ def __init__(self, title: str, default: str = "", placeholder: str = ""):
116
+ self.title = title
117
+ self.default = default
118
+ self.placeholder = placeholder
119
+
120
+ def run(self) -> Optional[str]:
121
+ """Run text input. Returns entered text or None if cancelled."""
122
+ from code_puppy.command_line.utils import safe_input
123
+
124
+ try:
125
+ prompt = f" {self.title}"
126
+ if self.default:
127
+ prompt += f" [{self.default}]"
128
+ prompt += ": "
129
+
130
+ value = safe_input(prompt).strip()
131
+ if not value and self.default:
132
+ return self.default
133
+ return value if value else None
134
+ except (KeyboardInterrupt, EOFError):
135
+ return None
136
+
137
+
138
+ class MultilineInputMenu:
139
+ """Multi-line text input for prompts."""
140
+
141
+ def __init__(self, title: str):
142
+ self.title = title
143
+
144
+ def run(self) -> Optional[str]:
145
+ """Run multiline input. Returns entered text or None if cancelled."""
146
+ from code_puppy.command_line.utils import safe_input
147
+
148
+ print(f"\n {self.title}")
149
+ print(" (Enter an empty line to finish, Ctrl+C to cancel)\n")
150
+
151
+ lines = []
152
+ try:
153
+ while True:
154
+ line = safe_input(" > ")
155
+ if not line:
156
+ break
157
+ lines.append(line)
158
+ except (KeyboardInterrupt, EOFError):
159
+ print("\n Cancelled.")
160
+ return None
161
+
162
+ return "\n".join(lines) if lines else None
163
+
164
+
165
+ def get_available_agents_list() -> List[Tuple[str, str]]:
166
+ """Get list of available agents with descriptions."""
167
+ try:
168
+ from code_puppy.agents import get_agent_descriptions, get_available_agents
169
+
170
+ agents = get_available_agents()
171
+ descriptions = get_agent_descriptions()
172
+
173
+ result = []
174
+ for agent_name in sorted(agents.keys()):
175
+ desc = descriptions.get(agent_name, agents.get(agent_name, ""))
176
+ result.append((agent_name, desc))
177
+ return result
178
+ except Exception:
179
+ return [("code-puppy", "Default agent")]
180
+
181
+
182
+ def get_available_models_list() -> List[str]:
183
+ """Get list of available models."""
184
+ try:
185
+ from code_puppy.command_line.model_picker_completion import load_model_names
186
+
187
+ models = load_model_names()
188
+ return models if models else ["(default)"]
189
+ except Exception:
190
+ return ["(default)"]
191
+
192
+
193
+ def create_task_wizard() -> Optional[dict]:
194
+ """Run the full task creation wizard.
195
+
196
+ Returns:
197
+ dict with task parameters, or None if cancelled.
198
+ """
199
+ print("\n" + "=" * 60)
200
+ print("📅 CREATE NEW SCHEDULED TASK")
201
+ print("=" * 60)
202
+
203
+ # Step 1: Task Name
204
+ name_input = TextInputMenu("Task name", placeholder="e.g., Daily Code Review")
205
+ task_name = name_input.run()
206
+ if not task_name:
207
+ print("\n ❌ Cancelled - task name required.")
208
+ return None
209
+
210
+ # Step 2: Schedule Type
211
+ schedule_menu = SelectionMenu(
212
+ "Select schedule type:",
213
+ choices=[
214
+ "Every 15 minutes",
215
+ "Every 30 minutes",
216
+ "Every hour",
217
+ "Every 2 hours",
218
+ "Every 6 hours",
219
+ "Daily",
220
+ "Custom interval...",
221
+ ],
222
+ descriptions=[
223
+ "Run 4 times per hour",
224
+ "Run twice per hour",
225
+ "Run once per hour",
226
+ "Run 12 times per day",
227
+ "Run 4 times per day",
228
+ "Run once per day",
229
+ "Specify custom interval like 45m, 3h, 2d",
230
+ ],
231
+ )
232
+ schedule_choice = schedule_menu.run()
233
+ if not schedule_choice:
234
+ print("\n ❌ Cancelled.")
235
+ return None
236
+
237
+ # Map choice to schedule type and value
238
+ schedule_map = {
239
+ "Every 15 minutes": ("interval", "15m"),
240
+ "Every 30 minutes": ("interval", "30m"),
241
+ "Every hour": ("hourly", "1h"),
242
+ "Every 2 hours": ("interval", "2h"),
243
+ "Every 6 hours": ("interval", "6h"),
244
+ "Daily": ("daily", "24h"),
245
+ }
246
+
247
+ if schedule_choice == "Custom interval...":
248
+ interval_input = TextInputMenu(
249
+ "Enter interval (e.g., 45m, 3h, 2d)", default="1h"
250
+ )
251
+ custom_interval = interval_input.run()
252
+ if not custom_interval:
253
+ print("\n ❌ Cancelled.")
254
+ return None
255
+ schedule_type = "interval"
256
+ schedule_value = custom_interval
257
+ else:
258
+ schedule_type, schedule_value = schedule_map[schedule_choice]
259
+
260
+ # Step 3: Agent Selection
261
+ agents = get_available_agents_list()
262
+ agent_names = [a[0] for a in agents]
263
+ agent_descs = [a[1] for a in agents]
264
+
265
+ # Put code-puppy first if it exists
266
+ if "code-puppy" in agent_names:
267
+ idx = agent_names.index("code-puppy")
268
+ agent_names.insert(0, agent_names.pop(idx))
269
+ agent_descs.insert(0, agent_descs.pop(idx))
270
+
271
+ agent_menu = SelectionMenu(
272
+ "Select agent:", choices=agent_names, descriptions=agent_descs
273
+ )
274
+ selected_agent = agent_menu.run()
275
+ if not selected_agent:
276
+ print("\n ❌ Cancelled.")
277
+ return None
278
+
279
+ # Step 4: Model Selection
280
+ models = get_available_models_list()
281
+ models.insert(0, "(use default model)")
282
+
283
+ model_menu = SelectionMenu("Select model:", choices=models, descriptions=None)
284
+ selected_model = model_menu.run()
285
+ if selected_model is None:
286
+ print("\n ❌ Cancelled.")
287
+ return None
288
+
289
+ if selected_model == "(use default model)":
290
+ selected_model = ""
291
+
292
+ # Step 5: Prompt
293
+ print()
294
+ prompt_input = MultilineInputMenu("Enter the prompt for this task:")
295
+ task_prompt = prompt_input.run()
296
+ if not task_prompt:
297
+ print("\n ❌ Cancelled - prompt required.")
298
+ return None
299
+
300
+ # Step 6: Working Directory
301
+ workdir_input = TextInputMenu(
302
+ "Working directory", default=".", placeholder="current directory"
303
+ )
304
+ working_dir = workdir_input.run()
305
+ if working_dir is None:
306
+ print("\n ❌ Cancelled.")
307
+ return None
308
+
309
+ # Summary
310
+ print("\n" + "-" * 60)
311
+ print("📋 TASK SUMMARY")
312
+ print("-" * 60)
313
+ print(f" Name: {task_name}")
314
+ print(f" Schedule: {schedule_type} ({schedule_value})")
315
+ print(f" Agent: {selected_agent}")
316
+ print(f" Model: {selected_model or '(default)'}")
317
+ print(f" Directory: {working_dir}")
318
+ print(f" Prompt: {task_prompt[:50]}{'...' if len(task_prompt) > 50 else ''}")
319
+ print("-" * 60)
320
+
321
+ # Confirm
322
+ from code_puppy.command_line.utils import safe_input
323
+
324
+ try:
325
+ confirm = safe_input("\n Create this task? (Y/n): ").strip().lower()
326
+ if confirm and confirm not in ("y", "yes"):
327
+ print("\n ❌ Cancelled.")
328
+ return None
329
+ except (KeyboardInterrupt, EOFError):
330
+ print("\n ❌ Cancelled.")
331
+ return None
332
+
333
+ return {
334
+ "name": task_name,
335
+ "prompt": task_prompt,
336
+ "agent": selected_agent,
337
+ "model": selected_model,
338
+ "schedule_type": schedule_type,
339
+ "schedule_value": schedule_value,
340
+ "working_directory": working_dir,
341
+ }
@@ -0,0 +1,6 @@
1
+ """Shell command safety checking plugin.
2
+
3
+ This plugin provides AI-powered safety assessment for shell commands
4
+ executed in yolo_mode. It helps prevent accidental execution of
5
+ dangerous commands that could cause data loss or system damage.
6
+ """
@@ -0,0 +1,69 @@
1
+ """Shell command safety assessment agent.
2
+
3
+ This agent provides rapid risk assessment of shell commands before execution.
4
+ It's designed to be ultra-lightweight with a concise prompt (<200 tokens) and
5
+ uses structured output for reliable parsing.
6
+ """
7
+
8
+ from typing import TYPE_CHECKING, List
9
+
10
+ from code_puppy.agents.base_agent import BaseAgent
11
+
12
+ if TYPE_CHECKING:
13
+ pass
14
+
15
+
16
+ class ShellSafetyAgent(BaseAgent):
17
+ """Lightweight agent for assessing shell command safety risks.
18
+
19
+ This agent evaluates shell commands for potential risks including:
20
+ - File system destruction (rm -rf, dd, format, mkfs)
21
+ - Database operations (DROP, TRUNCATE, unfiltered UPDATE/DELETE)
22
+ - Privilege escalation (sudo, su, chmod 777)
23
+ - Network operations (wget/curl to unknown hosts)
24
+ - Data exfiltration patterns
25
+
26
+ The agent returns structured output with a risk level and brief reasoning.
27
+ """
28
+
29
+ @property
30
+ def name(self) -> str:
31
+ """Agent name for internal use."""
32
+ return "shell_safety_checker"
33
+
34
+ @property
35
+ def display_name(self) -> str:
36
+ """User-facing display name."""
37
+ return "Shell Safety Checker 🛡️"
38
+
39
+ @property
40
+ def description(self) -> str:
41
+ """Agent description."""
42
+ return "Lightweight agent that assesses shell command safety risks"
43
+
44
+ def get_system_prompt(self) -> str:
45
+ """Get the ultra-concise system prompt for shell safety assessment.
46
+
47
+ This prompt is kept under 200 tokens for fast inference and low cost.
48
+ """
49
+ return """You are a shell command safety analyzer. Assess risk levels concisely.
50
+
51
+ **Risk Levels:**
52
+ - none: Completely safe (ls, pwd, echo, cat readonly files)
53
+ - low: Minimal risk (mkdir, touch, git status, read-only queries)
54
+ - medium: Moderate risk (file edits, package installs, service restarts)
55
+ - high: Significant risk (rm files, UPDATE/DELETE without WHERE, TRUNCATE, chmod dangerous permissions)
56
+ - critical: Severe/destructive (rm -rf, DROP TABLE/DATABASE, dd, format, mkfs, bq delete dataset, unfiltered mass deletes)
57
+
58
+ **Evaluate:**
59
+ - Scope (single file vs. entire system)
60
+ - Reversibility (can it be undone?)
61
+ - Data loss potential
62
+ - Privilege requirements
63
+ - Database destruction patterns
64
+
65
+ **Output:** Risk level + reasoning (max 1 sentence)."""
66
+
67
+ def get_available_tools(self) -> List[str]:
68
+ """This agent uses no tools - pure reasoning only."""
69
+ return []
@@ -0,0 +1,156 @@
1
+ """Caching layer for shell command safety assessments.
2
+
3
+ This module provides an LRU cache for recently assessed commands to avoid redundant API calls.
4
+
5
+ The approach is simple and secure: let the LLM assess ALL commands and cache
6
+ those assessments. This eliminates the security risks of pre-defined whitelists
7
+ while providing the performance benefits of caching.
8
+ """
9
+
10
+ from collections import OrderedDict
11
+ from dataclasses import dataclass
12
+ from typing import Optional, Tuple
13
+
14
+ # Maximum number of cached assessments (LRU eviction after this)
15
+ MAX_CACHE_SIZE = 200
16
+
17
+
18
+ @dataclass
19
+ class CachedAssessment:
20
+ """A cached safety assessment result."""
21
+
22
+ risk: str
23
+ reasoning: str
24
+
25
+
26
+ class CommandSafetyCache:
27
+ """LRU cache for shell command safety assessments.
28
+
29
+ This cache stores previous LLM assessments to avoid redundant API calls.
30
+ It uses an OrderedDict for O(1) LRU eviction.
31
+ """
32
+
33
+ def __init__(self, max_size: int = MAX_CACHE_SIZE):
34
+ self._cache: OrderedDict[Tuple[str, Optional[str]], CachedAssessment] = (
35
+ OrderedDict()
36
+ )
37
+ self._max_size = max_size
38
+ self._hits = 0
39
+ self._misses = 0
40
+
41
+ def _make_key(self, command: str, cwd: Optional[str]) -> Tuple[str, Optional[str]]:
42
+ """Create a cache key from command and cwd."""
43
+ # Normalize command (strip whitespace)
44
+ return (command.strip(), cwd)
45
+
46
+ def get(
47
+ self, command: str, cwd: Optional[str] = None
48
+ ) -> Optional[CachedAssessment]:
49
+ """Get a cached assessment if it exists.
50
+
51
+ Args:
52
+ command: The shell command
53
+ cwd: Optional working directory
54
+
55
+ Returns:
56
+ CachedAssessment if found, None otherwise
57
+ """
58
+ key = self._make_key(command, cwd)
59
+ if key in self._cache:
60
+ # Move to end (most recently used)
61
+ self._cache.move_to_end(key)
62
+ self._hits += 1
63
+ return self._cache[key]
64
+ self._misses += 1
65
+ return None
66
+
67
+ def put(
68
+ self, command: str, cwd: Optional[str], assessment: CachedAssessment
69
+ ) -> None:
70
+ """Store an assessment in the cache.
71
+
72
+ Args:
73
+ command: The shell command
74
+ cwd: Optional working directory
75
+ assessment: The assessment result to cache
76
+ """
77
+ key = self._make_key(command, cwd)
78
+
79
+ # If already exists, update and move to end
80
+ if key in self._cache:
81
+ self._cache.move_to_end(key)
82
+ self._cache[key] = assessment
83
+ return
84
+
85
+ # Evict oldest if at capacity
86
+ while len(self._cache) >= self._max_size:
87
+ self._cache.popitem(last=False)
88
+
89
+ self._cache[key] = assessment
90
+
91
+ def clear(self) -> None:
92
+ """Clear all cached assessments."""
93
+ self._cache.clear()
94
+ self._hits = 0
95
+ self._misses = 0
96
+
97
+ @property
98
+ def stats(self) -> dict:
99
+ """Get cache statistics."""
100
+ total = self._hits + self._misses
101
+ hit_rate = (self._hits / total * 100) if total > 0 else 0
102
+ return {
103
+ "size": len(self._cache),
104
+ "max_size": self._max_size,
105
+ "hits": self._hits,
106
+ "misses": self._misses,
107
+ "hit_rate": f"{hit_rate:.1f}%",
108
+ }
109
+
110
+
111
+ # Global cache instance (singleton for the session)
112
+ _cache = CommandSafetyCache()
113
+
114
+
115
+ def get_cache_stats() -> dict:
116
+ """Get statistics about the cache performance."""
117
+ return _cache.stats
118
+
119
+
120
+ def get_cached_assessment(
121
+ command: str, cwd: Optional[str] = None
122
+ ) -> Optional[CachedAssessment]:
123
+ """Get a cached command safety assessment.
124
+
125
+ Cache-only approach: use the LLM cache for speed, but let the LLM
126
+ determine safety for all commands. No pre-defined whitelists.
127
+
128
+ Args:
129
+ command: The shell command to check
130
+ cwd: Optional working directory
131
+
132
+ Returns:
133
+ CachedAssessment if found in cache, None if needs LLM assessment
134
+ """
135
+ return _cache.get(command, cwd)
136
+
137
+
138
+ def cache_assessment(
139
+ command: str, cwd: Optional[str], risk: str, reasoning: str
140
+ ) -> None:
141
+ """Cache an LLM assessment result.
142
+
143
+ Cache all LLM assessments since the same command should get
144
+ the same assessment, providing both security and performance.
145
+
146
+ Args:
147
+ command: The shell command
148
+ cwd: Optional working directory
149
+ risk: The assessed risk level
150
+ reasoning: The assessment reasoning
151
+ """
152
+ assessment = CachedAssessment(
153
+ risk=risk,
154
+ reasoning=reasoning,
155
+ )
156
+ _cache.put(command, cwd, assessment)