code-puppy 0.0.214__py3-none-any.whl → 0.0.366__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 (231) hide show
  1. code_puppy/__init__.py +7 -1
  2. code_puppy/agents/__init__.py +2 -0
  3. code_puppy/agents/agent_c_reviewer.py +59 -6
  4. code_puppy/agents/agent_code_puppy.py +7 -1
  5. code_puppy/agents/agent_code_reviewer.py +12 -2
  6. code_puppy/agents/agent_cpp_reviewer.py +73 -6
  7. code_puppy/agents/agent_creator_agent.py +45 -4
  8. code_puppy/agents/agent_golang_reviewer.py +92 -3
  9. code_puppy/agents/agent_javascript_reviewer.py +101 -8
  10. code_puppy/agents/agent_manager.py +81 -4
  11. code_puppy/agents/agent_pack_leader.py +383 -0
  12. code_puppy/agents/agent_planning.py +163 -0
  13. code_puppy/agents/agent_python_programmer.py +165 -0
  14. code_puppy/agents/agent_python_reviewer.py +28 -6
  15. code_puppy/agents/agent_qa_expert.py +98 -6
  16. code_puppy/agents/agent_qa_kitten.py +12 -7
  17. code_puppy/agents/agent_security_auditor.py +113 -3
  18. code_puppy/agents/agent_terminal_qa.py +323 -0
  19. code_puppy/agents/agent_typescript_reviewer.py +106 -7
  20. code_puppy/agents/base_agent.py +802 -176
  21. code_puppy/agents/event_stream_handler.py +350 -0
  22. code_puppy/agents/pack/__init__.py +34 -0
  23. code_puppy/agents/pack/bloodhound.py +304 -0
  24. code_puppy/agents/pack/husky.py +321 -0
  25. code_puppy/agents/pack/retriever.py +393 -0
  26. code_puppy/agents/pack/shepherd.py +348 -0
  27. code_puppy/agents/pack/terrier.py +287 -0
  28. code_puppy/agents/pack/watchdog.py +367 -0
  29. code_puppy/agents/prompt_reviewer.py +145 -0
  30. code_puppy/agents/subagent_stream_handler.py +276 -0
  31. code_puppy/api/__init__.py +13 -0
  32. code_puppy/api/app.py +169 -0
  33. code_puppy/api/main.py +21 -0
  34. code_puppy/api/pty_manager.py +446 -0
  35. code_puppy/api/routers/__init__.py +12 -0
  36. code_puppy/api/routers/agents.py +36 -0
  37. code_puppy/api/routers/commands.py +217 -0
  38. code_puppy/api/routers/config.py +74 -0
  39. code_puppy/api/routers/sessions.py +232 -0
  40. code_puppy/api/templates/terminal.html +361 -0
  41. code_puppy/api/websocket.py +154 -0
  42. code_puppy/callbacks.py +142 -4
  43. code_puppy/chatgpt_codex_client.py +283 -0
  44. code_puppy/claude_cache_client.py +586 -0
  45. code_puppy/cli_runner.py +916 -0
  46. code_puppy/command_line/add_model_menu.py +1079 -0
  47. code_puppy/command_line/agent_menu.py +395 -0
  48. code_puppy/command_line/attachments.py +10 -5
  49. code_puppy/command_line/autosave_menu.py +605 -0
  50. code_puppy/command_line/clipboard.py +527 -0
  51. code_puppy/command_line/colors_menu.py +520 -0
  52. code_puppy/command_line/command_handler.py +176 -738
  53. code_puppy/command_line/command_registry.py +150 -0
  54. code_puppy/command_line/config_commands.py +715 -0
  55. code_puppy/command_line/core_commands.py +792 -0
  56. code_puppy/command_line/diff_menu.py +863 -0
  57. code_puppy/command_line/load_context_completion.py +15 -22
  58. code_puppy/command_line/mcp/base.py +0 -3
  59. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  60. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  61. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  62. code_puppy/command_line/mcp/edit_command.py +148 -0
  63. code_puppy/command_line/mcp/handler.py +9 -4
  64. code_puppy/command_line/mcp/help_command.py +6 -5
  65. code_puppy/command_line/mcp/install_command.py +15 -26
  66. code_puppy/command_line/mcp/install_menu.py +685 -0
  67. code_puppy/command_line/mcp/list_command.py +2 -2
  68. code_puppy/command_line/mcp/logs_command.py +174 -65
  69. code_puppy/command_line/mcp/remove_command.py +2 -2
  70. code_puppy/command_line/mcp/restart_command.py +12 -4
  71. code_puppy/command_line/mcp/search_command.py +16 -10
  72. code_puppy/command_line/mcp/start_all_command.py +18 -6
  73. code_puppy/command_line/mcp/start_command.py +47 -25
  74. code_puppy/command_line/mcp/status_command.py +4 -5
  75. code_puppy/command_line/mcp/stop_all_command.py +7 -1
  76. code_puppy/command_line/mcp/stop_command.py +8 -4
  77. code_puppy/command_line/mcp/test_command.py +2 -2
  78. code_puppy/command_line/mcp/wizard_utils.py +20 -16
  79. code_puppy/command_line/mcp_completion.py +174 -0
  80. code_puppy/command_line/model_picker_completion.py +75 -25
  81. code_puppy/command_line/model_settings_menu.py +884 -0
  82. code_puppy/command_line/motd.py +14 -8
  83. code_puppy/command_line/onboarding_slides.py +179 -0
  84. code_puppy/command_line/onboarding_wizard.py +340 -0
  85. code_puppy/command_line/pin_command_completion.py +329 -0
  86. code_puppy/command_line/prompt_toolkit_completion.py +463 -63
  87. code_puppy/command_line/session_commands.py +296 -0
  88. code_puppy/command_line/utils.py +54 -0
  89. code_puppy/config.py +898 -112
  90. code_puppy/error_logging.py +118 -0
  91. code_puppy/gemini_code_assist.py +385 -0
  92. code_puppy/gemini_model.py +602 -0
  93. code_puppy/http_utils.py +210 -148
  94. code_puppy/keymap.py +128 -0
  95. code_puppy/main.py +5 -698
  96. code_puppy/mcp_/__init__.py +17 -0
  97. code_puppy/mcp_/async_lifecycle.py +35 -4
  98. code_puppy/mcp_/blocking_startup.py +70 -43
  99. code_puppy/mcp_/captured_stdio_server.py +2 -2
  100. code_puppy/mcp_/config_wizard.py +4 -4
  101. code_puppy/mcp_/dashboard.py +15 -6
  102. code_puppy/mcp_/managed_server.py +65 -38
  103. code_puppy/mcp_/manager.py +146 -52
  104. code_puppy/mcp_/mcp_logs.py +224 -0
  105. code_puppy/mcp_/registry.py +6 -6
  106. code_puppy/mcp_/server_registry_catalog.py +24 -5
  107. code_puppy/messaging/__init__.py +199 -2
  108. code_puppy/messaging/bus.py +610 -0
  109. code_puppy/messaging/commands.py +167 -0
  110. code_puppy/messaging/markdown_patches.py +57 -0
  111. code_puppy/messaging/message_queue.py +17 -48
  112. code_puppy/messaging/messages.py +500 -0
  113. code_puppy/messaging/queue_console.py +1 -24
  114. code_puppy/messaging/renderers.py +43 -146
  115. code_puppy/messaging/rich_renderer.py +1027 -0
  116. code_puppy/messaging/spinner/__init__.py +21 -5
  117. code_puppy/messaging/spinner/console_spinner.py +86 -51
  118. code_puppy/messaging/subagent_console.py +461 -0
  119. code_puppy/model_factory.py +634 -83
  120. code_puppy/model_utils.py +167 -0
  121. code_puppy/models.json +66 -68
  122. code_puppy/models_dev_api.json +1 -0
  123. code_puppy/models_dev_parser.py +592 -0
  124. code_puppy/plugins/__init__.py +164 -10
  125. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  126. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  127. code_puppy/plugins/antigravity_oauth/antigravity_model.py +704 -0
  128. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  129. code_puppy/plugins/antigravity_oauth/constants.py +136 -0
  130. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  131. code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
  132. code_puppy/plugins/antigravity_oauth/storage.py +271 -0
  133. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  134. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  135. code_puppy/plugins/antigravity_oauth/transport.py +767 -0
  136. code_puppy/plugins/antigravity_oauth/utils.py +169 -0
  137. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  138. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  139. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
  140. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +94 -0
  141. code_puppy/plugins/chatgpt_oauth/test_plugin.py +293 -0
  142. code_puppy/plugins/chatgpt_oauth/utils.py +489 -0
  143. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  144. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  145. code_puppy/plugins/claude_code_oauth/__init__.py +6 -0
  146. code_puppy/plugins/claude_code_oauth/config.py +50 -0
  147. code_puppy/plugins/claude_code_oauth/register_callbacks.py +308 -0
  148. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  149. code_puppy/plugins/claude_code_oauth/utils.py +518 -0
  150. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  151. code_puppy/plugins/customizable_commands/register_callbacks.py +169 -0
  152. code_puppy/plugins/example_custom_command/README.md +280 -0
  153. code_puppy/plugins/example_custom_command/register_callbacks.py +2 -2
  154. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  155. code_puppy/plugins/file_permission_handler/register_callbacks.py +523 -0
  156. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  157. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  158. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  159. code_puppy/plugins/oauth_puppy_html.py +228 -0
  160. code_puppy/plugins/shell_safety/__init__.py +6 -0
  161. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  162. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  163. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  164. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  165. code_puppy/prompts/codex_system_prompt.md +310 -0
  166. code_puppy/pydantic_patches.py +131 -0
  167. code_puppy/reopenable_async_client.py +8 -8
  168. code_puppy/round_robin_model.py +9 -12
  169. code_puppy/session_storage.py +2 -1
  170. code_puppy/status_display.py +21 -4
  171. code_puppy/summarization_agent.py +41 -13
  172. code_puppy/terminal_utils.py +418 -0
  173. code_puppy/tools/__init__.py +37 -1
  174. code_puppy/tools/agent_tools.py +536 -52
  175. code_puppy/tools/browser/__init__.py +37 -0
  176. code_puppy/tools/browser/browser_control.py +19 -23
  177. code_puppy/tools/browser/browser_interactions.py +41 -48
  178. code_puppy/tools/browser/browser_locators.py +36 -38
  179. code_puppy/tools/browser/browser_manager.py +316 -0
  180. code_puppy/tools/browser/browser_navigation.py +16 -16
  181. code_puppy/tools/browser/browser_screenshot.py +79 -143
  182. code_puppy/tools/browser/browser_scripts.py +32 -42
  183. code_puppy/tools/browser/browser_workflows.py +44 -27
  184. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  185. code_puppy/tools/browser/terminal_command_tools.py +521 -0
  186. code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
  187. code_puppy/tools/browser/terminal_tools.py +525 -0
  188. code_puppy/tools/command_runner.py +930 -147
  189. code_puppy/tools/common.py +1113 -5
  190. code_puppy/tools/display.py +84 -0
  191. code_puppy/tools/file_modifications.py +288 -89
  192. code_puppy/tools/file_operations.py +226 -154
  193. code_puppy/tools/subagent_context.py +158 -0
  194. code_puppy/uvx_detection.py +242 -0
  195. code_puppy/version_checker.py +30 -11
  196. code_puppy-0.0.366.data/data/code_puppy/models.json +110 -0
  197. code_puppy-0.0.366.data/data/code_puppy/models_dev_api.json +1 -0
  198. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/METADATA +149 -75
  199. code_puppy-0.0.366.dist-info/RECORD +217 -0
  200. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/WHEEL +1 -1
  201. code_puppy/command_line/mcp/add_command.py +0 -183
  202. code_puppy/messaging/spinner/textual_spinner.py +0 -106
  203. code_puppy/tools/browser/camoufox_manager.py +0 -216
  204. code_puppy/tools/browser/vqa_agent.py +0 -70
  205. code_puppy/tui/__init__.py +0 -10
  206. code_puppy/tui/app.py +0 -1105
  207. code_puppy/tui/components/__init__.py +0 -21
  208. code_puppy/tui/components/chat_view.py +0 -551
  209. code_puppy/tui/components/command_history_modal.py +0 -218
  210. code_puppy/tui/components/copy_button.py +0 -139
  211. code_puppy/tui/components/custom_widgets.py +0 -63
  212. code_puppy/tui/components/human_input_modal.py +0 -175
  213. code_puppy/tui/components/input_area.py +0 -167
  214. code_puppy/tui/components/sidebar.py +0 -309
  215. code_puppy/tui/components/status_bar.py +0 -185
  216. code_puppy/tui/messages.py +0 -27
  217. code_puppy/tui/models/__init__.py +0 -8
  218. code_puppy/tui/models/chat_message.py +0 -25
  219. code_puppy/tui/models/command_history.py +0 -89
  220. code_puppy/tui/models/enums.py +0 -24
  221. code_puppy/tui/screens/__init__.py +0 -17
  222. code_puppy/tui/screens/autosave_picker.py +0 -175
  223. code_puppy/tui/screens/help.py +0 -130
  224. code_puppy/tui/screens/mcp_install_wizard.py +0 -803
  225. code_puppy/tui/screens/settings.py +0 -306
  226. code_puppy/tui/screens/tools.py +0 -74
  227. code_puppy/tui_state.py +0 -55
  228. code_puppy-0.0.214.data/data/code_puppy/models.json +0 -112
  229. code_puppy-0.0.214.dist-info/RECORD +0 -131
  230. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/entry_points.txt +0 -0
  231. {code_puppy-0.0.214.dist-info → code_puppy-0.0.366.dist-info}/licenses/LICENSE +0 -0
code_puppy/main.py CHANGED
@@ -1,703 +1,10 @@
1
- import argparse
2
- import asyncio
3
- import os
4
- import subprocess
5
- import sys
6
- import time
7
- import webbrowser
8
- from pathlib import Path
1
+ """Main entry point for Code Puppy CLI.
9
2
 
10
- from dbos import DBOS, DBOSConfig
11
- from rich.console import Console, ConsoleOptions, RenderResult
12
- from rich.markdown import CodeBlock, Markdown
13
- from rich.syntax import Syntax
14
- from rich.text import Text
15
-
16
- from code_puppy import __version__, callbacks, plugins
17
- from code_puppy.agents import get_current_agent
18
- from code_puppy.command_line.prompt_toolkit_completion import (
19
- get_input_with_combined_completion,
20
- get_prompt_with_active_model,
21
- )
22
- from code_puppy.command_line.attachments import parse_prompt_attachments
23
- from code_puppy.config import (
24
- AUTOSAVE_DIR,
25
- COMMAND_HISTORY_FILE,
26
- DBOS_DATABASE_URL,
27
- get_use_dbos,
28
- ensure_config_exists,
29
- finalize_autosave_session,
30
- initialize_command_history_file,
31
- save_command_to_history,
32
- )
33
- from code_puppy.session_storage import restore_autosave_interactively
34
- from code_puppy.http_utils import find_available_port
35
- from code_puppy.tools.common import console
36
-
37
- # message_history_accumulator and prune_interrupted_tool_calls have been moved to BaseAgent class
38
- from code_puppy.tui_state import is_tui_mode, set_tui_mode
39
- from code_puppy.version_checker import default_version_mismatch_behavior
40
-
41
- plugins.load_plugin_callbacks()
42
-
43
-
44
- async def main():
45
- parser = argparse.ArgumentParser(description="Code Puppy - A code generation agent")
46
- parser.add_argument(
47
- "--version",
48
- "-v",
49
- action="version",
50
- version=f"{__version__}",
51
- help="Show version and exit",
52
- )
53
- parser.add_argument(
54
- "--interactive",
55
- "-i",
56
- action="store_true",
57
- help="Run in interactive mode",
58
- )
59
- parser.add_argument("--tui", "-t", action="store_true", help="Run in TUI mode")
60
- parser.add_argument(
61
- "--web",
62
- "-w",
63
- action="store_true",
64
- help="Run in web mode (serves TUI in browser)",
65
- )
66
- parser.add_argument(
67
- "--prompt",
68
- "-p",
69
- type=str,
70
- help="Execute a single prompt and exit (no interactive mode)",
71
- )
72
- parser.add_argument(
73
- "--agent",
74
- "-a",
75
- type=str,
76
- help="Specify which agent to use (e.g., --agent code-puppy)",
77
- )
78
- parser.add_argument(
79
- "command", nargs="*", help="Run a single command (deprecated, use -p instead)"
80
- )
81
- args = parser.parse_args()
82
-
83
- if args.tui or args.web:
84
- set_tui_mode(True)
85
- elif args.interactive or args.command or args.prompt:
86
- set_tui_mode(False)
87
-
88
- message_renderer = None
89
- if not is_tui_mode():
90
- from rich.console import Console
91
-
92
- from code_puppy.messaging import (
93
- SynchronousInteractiveRenderer,
94
- get_global_queue,
95
- )
96
-
97
- message_queue = get_global_queue()
98
- display_console = Console() # Separate console for rendering messages
99
- message_renderer = SynchronousInteractiveRenderer(
100
- message_queue, display_console
101
- )
102
- message_renderer.start()
103
-
104
- if (
105
- not args.tui
106
- and not args.interactive
107
- and not args.web
108
- and not args.command
109
- and not args.prompt
110
- ):
111
- pass
112
-
113
- initialize_command_history_file()
114
- if args.web:
115
- from rich.console import Console
116
-
117
- direct_console = Console()
118
- try:
119
- # Find an available port for the web server
120
- available_port = find_available_port()
121
- if available_port is None:
122
- direct_console.print(
123
- "[bold red]Error:[/bold red] No available ports in range 8090-9010!"
124
- )
125
- sys.exit(1)
126
- python_executable = sys.executable
127
- serve_command = f"{python_executable} -m code_puppy --tui"
128
- textual_serve_cmd = [
129
- "textual",
130
- "serve",
131
- "-c",
132
- serve_command,
133
- "--port",
134
- str(available_port),
135
- ]
136
- direct_console.print(
137
- "[bold blue]🌐 Starting Code Puppy web interface...[/bold blue]"
138
- )
139
- direct_console.print(f"[dim]Running: {' '.join(textual_serve_cmd)}[/dim]")
140
- web_url = f"http://localhost:{available_port}"
141
- direct_console.print(
142
- f"[green]Web interface will be available at: {web_url}[/green]"
143
- )
144
- direct_console.print("[yellow]Press Ctrl+C to stop the server.[/yellow]\n")
145
- process = subprocess.Popen(textual_serve_cmd)
146
- time.sleep(0.3)
147
- try:
148
- direct_console.print(
149
- "[cyan]🚀 Opening web interface in your default browser...[/cyan]"
150
- )
151
- webbrowser.open(web_url)
152
- direct_console.print("[green]✅ Browser opened successfully![/green]\n")
153
- except Exception as e:
154
- direct_console.print(
155
- f"[yellow]⚠️ Could not automatically open browser: {e}[/yellow]"
156
- )
157
- direct_console.print(
158
- f"[yellow]Please manually open: {web_url}[/yellow]\n"
159
- )
160
- result = process.wait()
161
- sys.exit(result)
162
- except Exception as e:
163
- direct_console.print(
164
- f"[bold red]Error starting web interface:[/bold red] {str(e)}"
165
- )
166
- sys.exit(1)
167
- from code_puppy.messaging import emit_system_message
168
-
169
- emit_system_message("🐶 Code Puppy is Loading...")
170
-
171
- available_port = find_available_port()
172
- if available_port is None:
173
- error_msg = "Error: No available ports in range 8090-9010!"
174
- emit_system_message(f"[bold red]{error_msg}[/bold red]")
175
- return
176
-
177
- ensure_config_exists()
178
-
179
- # Handle agent selection from command line
180
- if args.agent:
181
- from code_puppy.agents.agent_manager import (
182
- set_current_agent,
183
- get_available_agents,
184
- )
185
-
186
- agent_name = args.agent.lower()
187
- try:
188
- # First check if the agent exists by getting available agents
189
- available_agents = get_available_agents()
190
- if agent_name not in available_agents:
191
- emit_system_message(
192
- f"[bold red]Error:[/bold red] Agent '{agent_name}' not found"
193
- )
194
- emit_system_message(
195
- f"Available agents: {', '.join(available_agents.keys())}"
196
- )
197
- sys.exit(1)
198
-
199
- # Agent exists, set it
200
- set_current_agent(agent_name)
201
- emit_system_message(f"🤖 Using agent: {agent_name}")
202
- except Exception as e:
203
- emit_system_message(f"[bold red]Error setting agent:[/bold red] {str(e)}")
204
- sys.exit(1)
205
-
206
- current_version = __version__
207
-
208
- no_version_update = os.getenv("NO_VERSION_UPDATE", "").lower() in (
209
- "1",
210
- "true",
211
- "yes",
212
- "on",
213
- )
214
- if no_version_update:
215
- version_msg = f"Current version: {current_version}"
216
- update_disabled_msg = (
217
- "Update phase disabled because NO_VERSION_UPDATE is set to 1 or true"
218
- )
219
- emit_system_message(version_msg)
220
- emit_system_message(f"[dim]{update_disabled_msg}[/dim]")
221
- else:
222
- if len(callbacks.get_callbacks("version_check")):
223
- await callbacks.on_version_check(current_version)
224
- else:
225
- default_version_mismatch_behavior(current_version)
226
-
227
- await callbacks.on_startup()
228
-
229
- # Initialize DBOS if not disabled
230
- if get_use_dbos():
231
- dbos_message = f"Initializing DBOS with database at: {DBOS_DATABASE_URL}"
232
- emit_system_message(dbos_message)
233
-
234
- dbos_config: DBOSConfig = {
235
- "name": "dbos-code-puppy",
236
- "system_database_url": DBOS_DATABASE_URL,
237
- "run_admin_server": False,
238
- "conductor_key": os.environ.get(
239
- "DBOS_CONDUCTOR_KEY"
240
- ), # Optional, if set in env, connect to conductor
241
- "log_level": os.environ.get(
242
- "DBOS_LOG_LEVEL", "ERROR"
243
- ), # Default to ERROR level to suppress verbose logs
244
- "application_version": current_version, # Match DBOS app version to Code Puppy version
245
- }
246
- try:
247
- DBOS(config=dbos_config)
248
- DBOS.launch()
249
- except Exception as e:
250
- emit_system_message(f"[bold red]Error initializing DBOS:[/bold red] {e}")
251
- sys.exit(1)
252
- else:
253
- emit_system_message("DBOS is disabled. Running without durable execution.")
254
-
255
- global shutdown_flag
256
- shutdown_flag = False
257
- try:
258
- initial_command = None
259
- prompt_only_mode = False
260
-
261
- if args.prompt:
262
- initial_command = args.prompt
263
- prompt_only_mode = True
264
- elif args.command:
265
- initial_command = " ".join(args.command)
266
- prompt_only_mode = False
267
-
268
- if prompt_only_mode:
269
- await execute_single_prompt(initial_command, message_renderer)
270
- elif is_tui_mode():
271
- try:
272
- from code_puppy.tui import run_textual_ui
273
-
274
- await run_textual_ui(initial_command=initial_command)
275
- except ImportError:
276
- from code_puppy.messaging import emit_error, emit_warning
277
-
278
- emit_error(
279
- "Error: Textual UI not available. Install with: pip install textual"
280
- )
281
- emit_warning("Falling back to interactive mode...")
282
- await interactive_mode(message_renderer)
283
- except Exception as e:
284
- from code_puppy.messaging import emit_error, emit_warning
285
-
286
- emit_error(f"TUI Error: {str(e)}")
287
- emit_warning("Falling back to interactive mode...")
288
- await interactive_mode(message_renderer)
289
- elif args.interactive or initial_command:
290
- await interactive_mode(message_renderer, initial_command=initial_command)
291
- else:
292
- await prompt_then_interactive_mode(message_renderer)
293
- finally:
294
- if message_renderer:
295
- message_renderer.stop()
296
- await callbacks.on_shutdown()
297
- if get_use_dbos():
298
- DBOS.destroy()
299
-
300
-
301
- # Add the file handling functionality for interactive mode
302
- async def interactive_mode(message_renderer, initial_command: str = None) -> None:
303
- from code_puppy.command_line.command_handler import handle_command
304
-
305
- """Run the agent in interactive mode."""
306
-
307
- display_console = message_renderer.console
308
- from code_puppy.messaging import emit_info, emit_system_message
309
-
310
- emit_info("[bold green]Code Puppy[/bold green] - Interactive Mode")
311
- emit_system_message("Type '/exit' or '/quit' to exit the interactive mode.")
312
- emit_system_message("Type 'clear' to reset the conversation history.")
313
- emit_system_message("[dim]Type /help to view all commands[/dim]")
314
- emit_system_message(
315
- "Type [bold blue]@[/bold blue] for path completion, or [bold blue]/m[/bold blue] to pick a model. Toggle multiline with [bold blue]Alt+M[/bold blue] or [bold blue]F2[/bold blue]; newline: [bold blue]Ctrl+J[/bold blue]."
316
- )
317
- emit_system_message(
318
- "Press [bold red]Ctrl+C[/bold red] during processing to cancel the current task or inference."
319
- )
320
- try:
321
- from code_puppy.command_line.motd import print_motd
322
-
323
- print_motd(console, force=False)
324
- except Exception as e:
325
- from code_puppy.messaging import emit_warning
326
-
327
- emit_warning(f"MOTD error: {e}")
328
- from code_puppy.messaging import emit_info
329
-
330
- emit_info("[bold cyan]Initializing agent...[/bold cyan]")
331
-
332
- # Initialize the runtime agent manager
333
- if initial_command:
334
- from code_puppy.agents import get_current_agent
335
- from code_puppy.messaging import emit_info, emit_system_message
336
-
337
- agent = get_current_agent()
338
- emit_info(
339
- f"[bold blue]Processing initial command:[/bold blue] {initial_command}"
340
- )
341
-
342
- try:
343
- # Check if any tool is waiting for user input before showing spinner
344
- try:
345
- from code_puppy.tools.command_runner import is_awaiting_user_input
346
-
347
- awaiting_input = is_awaiting_user_input()
348
- except ImportError:
349
- awaiting_input = False
350
-
351
- # Run with or without spinner based on whether we're awaiting input
352
- response = await run_prompt_with_attachments(
353
- agent,
354
- initial_command,
355
- spinner_console=display_console,
356
- use_spinner=not awaiting_input,
357
- )
358
- if response is not None:
359
- agent_response = response.output
360
-
361
- emit_system_message(
362
- f"\n[bold purple]AGENT RESPONSE: [/bold purple]\n{agent_response}"
363
- )
364
- emit_system_message("\n" + "=" * 50)
365
- emit_info("[bold green]🐶 Continuing in Interactive Mode[/bold green]")
366
- emit_system_message(
367
- "Your command and response are preserved in the conversation history."
368
- )
369
- emit_system_message("=" * 50 + "\n")
370
-
371
- except Exception as e:
372
- from code_puppy.messaging import emit_error
373
-
374
- emit_error(f"Error processing initial command: {str(e)}")
375
-
376
- # Check if prompt_toolkit is installed
377
- try:
378
- from code_puppy.messaging import emit_system_message
379
-
380
- emit_system_message(
381
- "[dim]Using prompt_toolkit for enhanced tab completion[/dim]"
382
- )
383
- except ImportError:
384
- from code_puppy.messaging import emit_warning
385
-
386
- emit_warning("Warning: prompt_toolkit not installed. Installing now...")
387
- try:
388
- import subprocess
389
-
390
- subprocess.check_call(
391
- [sys.executable, "-m", "pip", "install", "prompt_toolkit"]
392
- )
393
- from code_puppy.messaging import emit_success
394
-
395
- emit_success("Successfully installed prompt_toolkit")
396
- except Exception as e:
397
- from code_puppy.messaging import emit_error, emit_warning
398
-
399
- emit_error(f"Error installing prompt_toolkit: {e}")
400
- emit_warning("Falling back to basic input without tab completion")
401
-
402
- await restore_autosave_interactively(Path(AUTOSAVE_DIR))
403
-
404
- while True:
405
- from code_puppy.agents.agent_manager import get_current_agent
406
- from code_puppy.messaging import emit_info
407
-
408
- # Get the custom prompt from the current agent, or use default
409
- current_agent = get_current_agent()
410
- user_prompt = current_agent.get_user_prompt() or "Enter your coding task:"
411
-
412
- emit_info(f"[bold blue]{user_prompt}[/bold blue]")
413
-
414
- try:
415
- # Use prompt_toolkit for enhanced input with path completion
416
- try:
417
- # Use the async version of get_input_with_combined_completion
418
- task = await get_input_with_combined_completion(
419
- get_prompt_with_active_model(), history_file=COMMAND_HISTORY_FILE
420
- )
421
- except ImportError:
422
- # Fall back to basic input if prompt_toolkit is not available
423
- task = input(">>> ")
424
-
425
- except (KeyboardInterrupt, EOFError):
426
- # Handle Ctrl+C or Ctrl+D
427
- from code_puppy.messaging import emit_warning
428
-
429
- emit_warning("\nInput cancelled")
430
- continue
431
-
432
- # Check for exit commands (plain text or command form)
433
- if task.strip().lower() in ["exit", "quit"] or task.strip().lower() in [
434
- "/exit",
435
- "/quit",
436
- ]:
437
- from code_puppy.messaging import emit_success
438
-
439
- emit_success("Goodbye!")
440
- # The renderer is stopped in the finally block of main().
441
- break
442
-
443
- # Check for clear command (supports both `clear` and `/clear`)
444
- if task.strip().lower() in ("clear", "/clear"):
445
- from code_puppy.messaging import (
446
- emit_info,
447
- emit_system_message,
448
- emit_warning,
449
- )
450
-
451
- agent = get_current_agent()
452
- new_session_id = finalize_autosave_session()
453
- agent.clear_message_history()
454
- emit_warning("Conversation history cleared!")
455
- emit_system_message("The agent will not remember previous interactions.\n")
456
- emit_info(f"[dim]Auto-save session rotated to: {new_session_id}[/dim]")
457
- continue
458
-
459
- # Parse attachments first so leading paths aren't misread as commands
460
- processed_for_commands = parse_prompt_attachments(task)
461
- cleaned_for_commands = (processed_for_commands.prompt or "").strip()
462
-
463
- # Handle / commands based on cleaned prompt (after stripping attachments)
464
- if cleaned_for_commands.startswith("/"):
465
- try:
466
- command_result = handle_command(cleaned_for_commands)
467
- except Exception as e:
468
- from code_puppy.messaging import emit_error
469
-
470
- emit_error(f"Command error: {e}")
471
- # Continue interactive loop instead of exiting
472
- continue
473
- if command_result is True:
474
- continue
475
- elif isinstance(command_result, str):
476
- # Command returned a prompt to execute
477
- task = command_result
478
- elif command_result is False:
479
- # Command not recognized, continue with normal processing
480
- pass
481
-
482
- if task.strip():
483
- # Write to the secret file for permanent history with timestamp
484
- save_command_to_history(task)
485
-
486
- try:
487
- prettier_code_blocks()
488
-
489
- # No need to get agent directly - use manager's run methods
490
-
491
- # Use our custom helper to enable attachment handling with spinner support
492
- result = await run_prompt_with_attachments(
493
- current_agent,
494
- task,
495
- spinner_console=message_renderer.console,
496
- )
497
- # Check if the task was cancelled (but don't show message if we just killed processes)
498
- if result is None:
499
- continue
500
- # Get the structured response
501
- agent_response = result.output
502
- from code_puppy.messaging import emit_info
503
-
504
- emit_system_message(
505
- f"\n[bold purple]AGENT RESPONSE: [/bold purple]\n{agent_response}"
506
- )
507
-
508
- # Auto-save session if enabled
509
- from code_puppy.config import auto_save_session_if_enabled
510
-
511
- auto_save_session_if_enabled()
512
-
513
- # Ensure console output is flushed before next prompt
514
- # This fixes the issue where prompt doesn't appear after agent response
515
- display_console.file.flush() if hasattr(
516
- display_console.file, "flush"
517
- ) else None
518
- import time
519
-
520
- time.sleep(0.1) # Brief pause to ensure all messages are rendered
521
-
522
- except Exception:
523
- from code_puppy.messaging.queue_console import get_queue_console
524
-
525
- get_queue_console().print_exception()
526
-
527
-
528
- def prettier_code_blocks():
529
- class SimpleCodeBlock(CodeBlock):
530
- def __rich_console__(
531
- self, console: Console, options: ConsoleOptions
532
- ) -> RenderResult:
533
- code = str(self.text).rstrip()
534
- yield Text(self.lexer_name, style="dim")
535
- syntax = Syntax(
536
- code,
537
- self.lexer_name,
538
- theme=self.theme,
539
- background_color="default",
540
- line_numbers=True,
541
- )
542
- yield syntax
543
- yield Text(f"/{self.lexer_name}", style="dim")
544
-
545
- Markdown.elements["fence"] = SimpleCodeBlock
546
-
547
-
548
- async def run_prompt_with_attachments(
549
- agent,
550
- raw_prompt: str,
551
- *,
552
- spinner_console=None,
553
- use_spinner: bool = True,
554
- ):
555
- """Run the agent after parsing CLI attachments for image/document support."""
556
- from code_puppy.messaging import emit_system_message, emit_warning
557
-
558
- processed_prompt = parse_prompt_attachments(raw_prompt)
559
-
560
- for warning in processed_prompt.warnings:
561
- emit_warning(warning)
562
-
563
- summary_parts = []
564
- if processed_prompt.attachments:
565
- summary_parts.append(f"binary files: {len(processed_prompt.attachments)}")
566
- if processed_prompt.link_attachments:
567
- summary_parts.append(f"urls: {len(processed_prompt.link_attachments)}")
568
- if summary_parts:
569
- emit_system_message(
570
- "[dim]Attachments detected -> " + ", ".join(summary_parts) + "[/dim]"
571
- )
572
-
573
- if not processed_prompt.prompt:
574
- emit_warning(
575
- "Prompt is empty after removing attachments; add instructions and retry."
576
- )
577
- return None
578
-
579
- attachments = [attachment.content for attachment in processed_prompt.attachments]
580
- link_attachments = [link.url_part for link in processed_prompt.link_attachments]
581
-
582
- if use_spinner and spinner_console is not None:
583
- from code_puppy.messaging.spinner import ConsoleSpinner
584
-
585
- with ConsoleSpinner(console=spinner_console):
586
- return await agent.run_with_mcp(
587
- processed_prompt.prompt,
588
- attachments=attachments,
589
- link_attachments=link_attachments,
590
- )
591
-
592
- return await agent.run_with_mcp(
593
- processed_prompt.prompt,
594
- attachments=attachments,
595
- link_attachments=link_attachments,
596
- )
597
-
598
-
599
- async def execute_single_prompt(prompt: str, message_renderer) -> None:
600
- """Execute a single prompt and exit (for -p flag)."""
601
- from code_puppy.messaging import emit_info, emit_system_message
602
-
603
- emit_info(f"[bold blue]Executing prompt:[/bold blue] {prompt}")
604
-
605
- try:
606
- # Get agent through runtime manager and use helper for attachments
607
- agent = get_current_agent()
608
- response = await run_prompt_with_attachments(
609
- agent,
610
- prompt,
611
- spinner_console=message_renderer.console,
612
- )
613
- if response is None:
614
- return
615
-
616
- agent_response = response.output
617
- emit_system_message(
618
- f"\n[bold purple]AGENT RESPONSE: [/bold purple]\n{agent_response}"
619
- )
620
-
621
- except asyncio.CancelledError:
622
- from code_puppy.messaging import emit_warning
623
-
624
- emit_warning("Execution cancelled by user")
625
- except Exception as e:
626
- from code_puppy.messaging import emit_error
627
-
628
- emit_error(f"Error executing prompt: {str(e)}")
629
-
630
-
631
- async def prompt_then_interactive_mode(message_renderer) -> None:
632
- """Prompt user for input, execute it, then continue in interactive mode."""
633
- from code_puppy.messaging import emit_info, emit_system_message
634
-
635
- emit_info("[bold green]🐶 Code Puppy[/bold green] - Enter your request")
636
- emit_system_message(
637
- "After processing your request, you'll continue in interactive mode."
638
- )
639
-
640
- try:
641
- # Get user input
642
- from code_puppy.command_line.prompt_toolkit_completion import (
643
- get_input_with_combined_completion,
644
- get_prompt_with_active_model,
645
- )
646
- from code_puppy.config import COMMAND_HISTORY_FILE
647
-
648
- emit_info("[bold blue]What would you like me to help you with?[/bold blue]")
649
-
650
- try:
651
- # Use prompt_toolkit for enhanced input with path completion
652
- user_prompt = await get_input_with_combined_completion(
653
- get_prompt_with_active_model(), history_file=COMMAND_HISTORY_FILE
654
- )
655
- except ImportError:
656
- # Fall back to basic input if prompt_toolkit is not available
657
- user_prompt = input(">>> ")
658
-
659
- if user_prompt.strip():
660
- # Execute the prompt
661
- await execute_single_prompt(user_prompt, message_renderer)
662
-
663
- # Transition to interactive mode
664
- emit_system_message("\n" + "=" * 50)
665
- emit_info("[bold green]🐶 Continuing in Interactive Mode[/bold green]")
666
- emit_system_message(
667
- "Your request and response are preserved in the conversation history."
668
- )
669
- emit_system_message("=" * 50 + "\n")
670
-
671
- # Continue in interactive mode with the initial command as history
672
- await interactive_mode(message_renderer, initial_command=user_prompt)
673
- else:
674
- # No input provided, just go to interactive mode
675
- await interactive_mode(message_renderer)
676
-
677
- except (KeyboardInterrupt, EOFError):
678
- from code_puppy.messaging import emit_warning
679
-
680
- emit_warning("\nInput cancelled. Starting interactive mode...")
681
- await interactive_mode(message_renderer)
682
- except Exception as e:
683
- from code_puppy.messaging import emit_error
684
-
685
- emit_error(f"Error in prompt mode: {str(e)}")
686
- emit_info("Falling back to interactive mode...")
687
- await interactive_mode(message_renderer)
688
-
689
-
690
- def main_entry():
691
- """Entry point for the installed CLI tool."""
692
- try:
693
- asyncio.run(main())
694
- except KeyboardInterrupt:
695
- # Just exit gracefully with no error message
696
- callbacks.on_shutdown()
697
- if get_use_dbos():
698
- DBOS.destroy()
699
- return 0
3
+ This module re-exports the main_entry function from cli_runner for backwards compatibility.
4
+ All the actual logic lives in cli_runner.py.
5
+ """
700
6
 
7
+ from code_puppy.cli_runner import main_entry
701
8
 
702
9
  if __name__ == "__main__":
703
10
  main_entry()