code-puppy 0.0.287__py3-none-any.whl → 0.0.323__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 (110) hide show
  1. code_puppy/__init__.py +3 -1
  2. code_puppy/agents/agent_code_puppy.py +5 -4
  3. code_puppy/agents/agent_creator_agent.py +22 -18
  4. code_puppy/agents/agent_manager.py +2 -2
  5. code_puppy/agents/base_agent.py +496 -102
  6. code_puppy/callbacks.py +8 -0
  7. code_puppy/chatgpt_codex_client.py +283 -0
  8. code_puppy/cli_runner.py +795 -0
  9. code_puppy/command_line/add_model_menu.py +19 -16
  10. code_puppy/command_line/attachments.py +10 -5
  11. code_puppy/command_line/autosave_menu.py +269 -41
  12. code_puppy/command_line/colors_menu.py +515 -0
  13. code_puppy/command_line/command_handler.py +10 -24
  14. code_puppy/command_line/config_commands.py +106 -25
  15. code_puppy/command_line/core_commands.py +32 -20
  16. code_puppy/command_line/mcp/add_command.py +3 -16
  17. code_puppy/command_line/mcp/base.py +0 -3
  18. code_puppy/command_line/mcp/catalog_server_installer.py +15 -15
  19. code_puppy/command_line/mcp/custom_server_form.py +66 -5
  20. code_puppy/command_line/mcp/custom_server_installer.py +17 -17
  21. code_puppy/command_line/mcp/edit_command.py +15 -22
  22. code_puppy/command_line/mcp/handler.py +7 -2
  23. code_puppy/command_line/mcp/help_command.py +2 -2
  24. code_puppy/command_line/mcp/install_command.py +10 -14
  25. code_puppy/command_line/mcp/install_menu.py +2 -6
  26. code_puppy/command_line/mcp/list_command.py +2 -2
  27. code_puppy/command_line/mcp/logs_command.py +174 -65
  28. code_puppy/command_line/mcp/remove_command.py +2 -2
  29. code_puppy/command_line/mcp/restart_command.py +7 -2
  30. code_puppy/command_line/mcp/search_command.py +16 -10
  31. code_puppy/command_line/mcp/start_all_command.py +16 -6
  32. code_puppy/command_line/mcp/start_command.py +12 -10
  33. code_puppy/command_line/mcp/status_command.py +4 -5
  34. code_puppy/command_line/mcp/stop_all_command.py +5 -1
  35. code_puppy/command_line/mcp/stop_command.py +6 -4
  36. code_puppy/command_line/mcp/test_command.py +2 -2
  37. code_puppy/command_line/mcp/wizard_utils.py +20 -16
  38. code_puppy/command_line/model_settings_menu.py +53 -7
  39. code_puppy/command_line/motd.py +1 -1
  40. code_puppy/command_line/pin_command_completion.py +82 -7
  41. code_puppy/command_line/prompt_toolkit_completion.py +32 -9
  42. code_puppy/command_line/session_commands.py +11 -4
  43. code_puppy/config.py +217 -53
  44. code_puppy/error_logging.py +118 -0
  45. code_puppy/gemini_code_assist.py +385 -0
  46. code_puppy/keymap.py +126 -0
  47. code_puppy/main.py +5 -745
  48. code_puppy/mcp_/__init__.py +17 -0
  49. code_puppy/mcp_/blocking_startup.py +63 -36
  50. code_puppy/mcp_/captured_stdio_server.py +1 -1
  51. code_puppy/mcp_/config_wizard.py +4 -4
  52. code_puppy/mcp_/dashboard.py +15 -6
  53. code_puppy/mcp_/managed_server.py +25 -5
  54. code_puppy/mcp_/manager.py +65 -0
  55. code_puppy/mcp_/mcp_logs.py +224 -0
  56. code_puppy/mcp_/registry.py +6 -6
  57. code_puppy/messaging/__init__.py +184 -2
  58. code_puppy/messaging/bus.py +610 -0
  59. code_puppy/messaging/commands.py +167 -0
  60. code_puppy/messaging/markdown_patches.py +57 -0
  61. code_puppy/messaging/message_queue.py +3 -3
  62. code_puppy/messaging/messages.py +470 -0
  63. code_puppy/messaging/renderers.py +43 -141
  64. code_puppy/messaging/rich_renderer.py +900 -0
  65. code_puppy/messaging/spinner/console_spinner.py +39 -2
  66. code_puppy/model_factory.py +292 -53
  67. code_puppy/model_utils.py +57 -48
  68. code_puppy/models.json +19 -5
  69. code_puppy/plugins/__init__.py +152 -10
  70. code_puppy/plugins/chatgpt_oauth/config.py +20 -12
  71. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +5 -6
  72. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +3 -3
  73. code_puppy/plugins/chatgpt_oauth/test_plugin.py +30 -13
  74. code_puppy/plugins/chatgpt_oauth/utils.py +180 -65
  75. code_puppy/plugins/claude_code_oauth/config.py +15 -11
  76. code_puppy/plugins/claude_code_oauth/register_callbacks.py +28 -0
  77. code_puppy/plugins/claude_code_oauth/utils.py +6 -1
  78. code_puppy/plugins/example_custom_command/register_callbacks.py +2 -2
  79. code_puppy/plugins/oauth_puppy_html.py +3 -0
  80. code_puppy/plugins/shell_safety/agent_shell_safety.py +1 -134
  81. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  82. code_puppy/plugins/shell_safety/register_callbacks.py +77 -3
  83. code_puppy/prompts/codex_system_prompt.md +310 -0
  84. code_puppy/pydantic_patches.py +131 -0
  85. code_puppy/session_storage.py +2 -1
  86. code_puppy/status_display.py +7 -5
  87. code_puppy/terminal_utils.py +126 -0
  88. code_puppy/tools/agent_tools.py +131 -70
  89. code_puppy/tools/browser/browser_control.py +10 -14
  90. code_puppy/tools/browser/browser_interactions.py +20 -28
  91. code_puppy/tools/browser/browser_locators.py +27 -29
  92. code_puppy/tools/browser/browser_navigation.py +9 -9
  93. code_puppy/tools/browser/browser_screenshot.py +12 -14
  94. code_puppy/tools/browser/browser_scripts.py +17 -29
  95. code_puppy/tools/browser/browser_workflows.py +24 -25
  96. code_puppy/tools/browser/camoufox_manager.py +22 -26
  97. code_puppy/tools/command_runner.py +410 -88
  98. code_puppy/tools/common.py +51 -38
  99. code_puppy/tools/file_modifications.py +98 -24
  100. code_puppy/tools/file_operations.py +113 -202
  101. code_puppy/version_checker.py +28 -13
  102. {code_puppy-0.0.287.data → code_puppy-0.0.323.data}/data/code_puppy/models.json +19 -5
  103. {code_puppy-0.0.287.dist-info → code_puppy-0.0.323.dist-info}/METADATA +3 -8
  104. code_puppy-0.0.323.dist-info/RECORD +168 -0
  105. code_puppy/tui_state.py +0 -55
  106. code_puppy-0.0.287.dist-info/RECORD +0 -153
  107. {code_puppy-0.0.287.data → code_puppy-0.0.323.data}/data/code_puppy/models_dev_api.json +0 -0
  108. {code_puppy-0.0.287.dist-info → code_puppy-0.0.323.dist-info}/WHEEL +0 -0
  109. {code_puppy-0.0.287.dist-info → code_puppy-0.0.323.dist-info}/entry_points.txt +0 -0
  110. {code_puppy-0.0.287.dist-info → code_puppy-0.0.323.dist-info}/licenses/LICENSE +0 -0
@@ -8,11 +8,11 @@ appropriately for their respective interfaces.
8
8
  import asyncio
9
9
  import threading
10
10
  from abc import ABC, abstractmethod
11
- from io import StringIO
12
11
  from typing import Optional
13
12
 
14
13
  from rich.console import Console
15
14
  from rich.markdown import Markdown
15
+ from rich.markup import escape as escape_rich_markup
16
16
 
17
17
  from .message_queue import MessageQueue, MessageType, UIMessage
18
18
 
@@ -64,7 +64,10 @@ class MessageRenderer(ABC):
64
64
  break
65
65
  except Exception as e:
66
66
  # Log error but continue processing
67
- print(f"Error rendering message: {e}")
67
+ # Note: Using sys.stderr - can't use messaging in renderer
68
+ import sys
69
+
70
+ sys.stderr.write(f"Error rendering message: {e}\n")
68
71
 
69
72
 
70
73
  class InteractiveRenderer(MessageRenderer):
@@ -103,10 +106,18 @@ class InteractiveRenderer(MessageRenderer):
103
106
  # Special handling for agent responses - they'll be rendered as markdown
104
107
  style = None
105
108
  elif message.type == MessageType.SYSTEM:
106
- style = None
109
+ style = "dim"
107
110
  else:
108
111
  style = None
109
112
 
113
+ # Make version messages dim regardless of message type
114
+ if isinstance(message.content, str):
115
+ if (
116
+ "Current version:" in message.content
117
+ or "Latest version:" in message.content
118
+ ):
119
+ style = "dim"
120
+
110
121
  # Render the content
111
122
  if isinstance(message.content, str):
112
123
  if message.type == MessageType.AGENT_RESPONSE:
@@ -116,11 +127,15 @@ class InteractiveRenderer(MessageRenderer):
116
127
  self.console.print(markdown)
117
128
  except Exception:
118
129
  # Fallback to plain text if markdown parsing fails
119
- self.console.print(message.content)
130
+ safe_content = escape_rich_markup(message.content)
131
+ self.console.print(safe_content)
120
132
  elif style:
121
- self.console.print(message.content, style=style)
133
+ # Escape Rich markup to prevent crashes from malformed tags
134
+ safe_content = escape_rich_markup(message.content)
135
+ self.console.print(safe_content, style=style)
122
136
  else:
123
- self.console.print(message.content)
137
+ safe_content = escape_rich_markup(message.content)
138
+ self.console.print(safe_content)
124
139
  else:
125
140
  # For complex Rich objects (Tables, Markdown, Text, etc.)
126
141
  self.console.print(message.content)
@@ -135,139 +150,12 @@ class InteractiveRenderer(MessageRenderer):
135
150
  # This renderer is not currently used in practice, but if it were:
136
151
  # We would need async input handling here
137
152
  # For now, just render as a system message
138
- self.console.print(f"[bold cyan]INPUT REQUESTED:[/bold cyan] {message.content}")
153
+ safe_content = escape_rich_markup(str(message.content))
154
+ self.console.print(f"[bold cyan]INPUT REQUESTED:[/bold cyan] {safe_content}")
139
155
  if hasattr(self.console.file, "flush"):
140
156
  self.console.file.flush()
141
157
 
142
158
 
143
- class TUIRenderer(MessageRenderer):
144
- """Renderer for TUI mode that adds messages to the chat view."""
145
-
146
- def __init__(self, queue: MessageQueue, tui_app=None):
147
- super().__init__(queue)
148
- self.tui_app = tui_app
149
-
150
- def set_tui_app(self, app):
151
- """Set the TUI app reference."""
152
- self.tui_app = app
153
-
154
- async def render_message(self, message: UIMessage):
155
- """Render a message in the TUI chat view."""
156
- if not self.tui_app:
157
- return
158
-
159
- # Handle human input requests
160
- if message.type == MessageType.HUMAN_INPUT_REQUEST:
161
- await self._handle_human_input_request(message)
162
- return
163
-
164
- # Extract group_id from message metadata (fixing the key name)
165
- group_id = message.metadata.get("message_group") if message.metadata else None
166
-
167
- # For INFO messages with Rich objects (like Markdown), preserve them for proper rendering
168
- if message.type == MessageType.INFO and hasattr(
169
- message.content, "__rich_console__"
170
- ):
171
- # Pass the Rich object directly to maintain markdown formatting
172
- self.tui_app.add_system_message_rich(
173
- message.content, message_group=group_id
174
- )
175
- return
176
-
177
- # Convert content to string for TUI display (for all other cases)
178
- if hasattr(message.content, "__rich_console__"):
179
- # For Rich objects, render to plain text using a Console
180
- string_io = StringIO()
181
- # Use markup=False to prevent interpretation of square brackets as markup
182
- temp_console = Console(
183
- file=string_io, width=80, legacy_windows=False, markup=False
184
- )
185
- temp_console.print(message.content)
186
- content_str = string_io.getvalue().rstrip("\n")
187
- else:
188
- content_str = str(message.content)
189
-
190
- # Map message types to TUI message types - ALL get group_id now
191
- if message.type in (MessageType.ERROR,):
192
- self.tui_app.add_error_message(content_str, message_group=group_id)
193
- elif message.type in (
194
- MessageType.SYSTEM,
195
- MessageType.INFO,
196
- MessageType.WARNING,
197
- MessageType.SUCCESS,
198
- ):
199
- self.tui_app.add_system_message(content_str, message_group=group_id)
200
- elif message.type == MessageType.AGENT_REASONING:
201
- # Agent reasoning messages should use the dedicated method
202
- self.tui_app.add_agent_reasoning_message(
203
- content_str, message_group=group_id
204
- )
205
- elif message.type == MessageType.PLANNED_NEXT_STEPS:
206
- # Agent reasoning messages should use the dedicated method
207
- self.tui_app.add_planned_next_steps_message(
208
- content_str, message_group=group_id
209
- )
210
- elif message.type in (
211
- MessageType.TOOL_OUTPUT,
212
- MessageType.COMMAND_OUTPUT,
213
- MessageType.AGENT_RESPONSE,
214
- ):
215
- # These are typically agent/tool outputs
216
- self.tui_app.add_agent_message(content_str, message_group=group_id)
217
- else:
218
- # Default to system message
219
- self.tui_app.add_system_message(content_str, message_group=group_id)
220
-
221
- async def _handle_human_input_request(self, message: UIMessage):
222
- """Handle a human input request in TUI mode."""
223
- try:
224
- # Check if tui_app is available
225
- if not self.tui_app:
226
- prompt_id = (
227
- message.metadata.get("prompt_id") if message.metadata else None
228
- )
229
- if prompt_id:
230
- from code_puppy.messaging import provide_prompt_response
231
-
232
- provide_prompt_response(prompt_id, "")
233
- return
234
-
235
- prompt_id = message.metadata.get("prompt_id") if message.metadata else None
236
- if not prompt_id:
237
- self.tui_app.add_error_message("Error: Invalid human input request")
238
- return
239
-
240
- # For now, use a simple fallback instead of modal to avoid crashes
241
- self.tui_app.add_system_message(
242
- f"[yellow]INPUT NEEDED:[/yellow] {str(message.content)}"
243
- )
244
- self.tui_app.add_system_message(
245
- "[dim]This would normally show a modal, but using fallback to prevent crashes[/dim]"
246
- )
247
-
248
- # Provide empty response for now to unblock the waiting thread
249
- from code_puppy.messaging import provide_prompt_response
250
-
251
- provide_prompt_response(prompt_id, "")
252
-
253
- except Exception as e:
254
- print(f"Exception in _handle_human_input_request: {e}")
255
- import traceback
256
-
257
- traceback.print_exc()
258
- # Last resort - provide empty response to prevent hanging
259
- try:
260
- prompt_id = (
261
- message.metadata.get("prompt_id") if message.metadata else None
262
- )
263
- if prompt_id:
264
- from code_puppy.messaging import provide_prompt_response
265
-
266
- provide_prompt_response(prompt_id, "")
267
- except Exception:
268
- pass # Can't do anything more
269
-
270
-
271
159
  class SynchronousInteractiveRenderer:
272
160
  """
273
161
  Synchronous renderer for interactive mode that doesn't require async.
@@ -350,10 +238,18 @@ class SynchronousInteractiveRenderer:
350
238
  # Special handling for agent responses - they'll be rendered as markdown
351
239
  style = None
352
240
  elif message.type == MessageType.SYSTEM:
353
- style = None
241
+ style = "dim"
354
242
  else:
355
243
  style = None
356
244
 
245
+ # Make version messages dim regardless of message type
246
+ if isinstance(message.content, str):
247
+ if (
248
+ "Current version:" in message.content
249
+ or "Latest version:" in message.content
250
+ ):
251
+ style = "dim"
252
+
357
253
  # Render the content
358
254
  if isinstance(message.content, str):
359
255
  if message.type == MessageType.AGENT_RESPONSE:
@@ -363,11 +259,16 @@ class SynchronousInteractiveRenderer:
363
259
  self.console.print(markdown)
364
260
  except Exception:
365
261
  # Fallback to plain text if markdown parsing fails
366
- self.console.print(message.content)
262
+ safe_content = escape_rich_markup(message.content)
263
+ self.console.print(safe_content)
367
264
  elif style:
368
- self.console.print(message.content, style=style)
265
+ # Escape Rich markup to prevent crashes from malformed tags
266
+ # in shell output or other user-provided content
267
+ safe_content = escape_rich_markup(message.content)
268
+ self.console.print(safe_content, style=style)
369
269
  else:
370
- self.console.print(message.content)
270
+ safe_content = escape_rich_markup(message.content)
271
+ self.console.print(safe_content)
371
272
  else:
372
273
  # For complex Rich objects (Tables, Markdown, Text, etc.)
373
274
  self.console.print(message.content)
@@ -386,8 +287,9 @@ class SynchronousInteractiveRenderer:
386
287
  )
387
288
  return
388
289
 
389
- # Display the prompt
390
- self.console.print(f"[bold cyan]{message.content}[/bold cyan]")
290
+ # Display the prompt - escape to prevent markup injection
291
+ safe_content = escape_rich_markup(str(message.content))
292
+ self.console.print(f"[bold cyan]{safe_content}[/bold cyan]")
391
293
  if hasattr(self.console.file, "flush"):
392
294
  self.console.file.flush()
393
295