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
@@ -27,6 +27,8 @@ def get_commands_help():
27
27
  )
28
28
  def handle_show_command(command: str) -> bool:
29
29
  """Show current puppy configuration."""
30
+ from rich.text import Text
31
+
30
32
  from code_puppy.agents import get_current_agent
31
33
  from code_puppy.command_line.model_picker_completion import get_active_model
32
34
  from code_puppy.config import (
@@ -79,14 +81,14 @@ def handle_show_command(command: str) -> bool:
79
81
  [bold]temperature:[/bold] [cyan]{effective_temperature if effective_temperature is not None else "(model default)"}[/cyan]{" (per-model)" if effective_temperature != global_temperature and effective_temperature is not None else ""}
80
82
 
81
83
  """
82
- emit_info(status_msg)
84
+ emit_info(Text.from_markup(status_msg))
83
85
  return True
84
86
 
85
87
 
86
88
  @register_command(
87
89
  name="reasoning",
88
90
  description="Set OpenAI reasoning effort for GPT-5 models (e.g., /reasoning high)",
89
- usage="/reasoning <low|medium|high>",
91
+ usage="/reasoning <minimal|low|medium|high|xhigh>",
90
92
  category="config",
91
93
  )
92
94
  def handle_reasoning_command(command: str) -> bool:
@@ -95,7 +97,7 @@ def handle_reasoning_command(command: str) -> bool:
95
97
 
96
98
  tokens = command.split()
97
99
  if len(tokens) != 2:
98
- emit_warning("Usage: /reasoning <low|medium|high>")
100
+ emit_warning("Usage: /reasoning <minimal|low|medium|high|xhigh>")
99
101
  return True
100
102
 
101
103
  effort = tokens[1]
@@ -171,6 +173,8 @@ def handle_verbosity_command(command: str) -> bool:
171
173
  )
172
174
  def handle_set_command(command: str) -> bool:
173
175
  """Set configuration values."""
176
+ from rich.text import Text
177
+
174
178
  from code_puppy.config import set_config_value
175
179
  from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning
176
180
 
@@ -197,14 +201,18 @@ def handle_set_command(command: str) -> bool:
197
201
  "\n [cyan]auto_save_session[/cyan] Auto-save chat after every response (true/false)"
198
202
  )
199
203
  emit_warning(
200
- f"Usage: /set KEY=VALUE or /set KEY VALUE\nConfig keys: {', '.join(config_keys)}\n[dim]Note: compaction_strategy can be 'summarization' or 'truncation'[/dim]{session_help}"
204
+ Text.from_markup(
205
+ f"Usage: /set KEY=VALUE or /set KEY VALUE\nConfig keys: {', '.join(config_keys)}\n[dim]Note: compaction_strategy can be 'summarization' or 'truncation'[/dim]{session_help}"
206
+ )
201
207
  )
202
208
  return True
203
209
  if key:
204
210
  # Check if we're toggling DBOS enablement
205
211
  if key == "enable_dbos":
206
212
  emit_info(
207
- "[yellow]āš ļø DBOS configuration changed. Please restart Code Puppy for this change to take effect.[/yellow]"
213
+ Text.from_markup(
214
+ "[yellow]āš ļø DBOS configuration changed. Please restart Code Puppy for this change to take effect.[/yellow]"
215
+ )
208
216
  )
209
217
 
210
218
  set_config_value(key, value)
@@ -216,7 +224,7 @@ def handle_set_command(command: str) -> bool:
216
224
  try:
217
225
  current_agent = get_current_agent()
218
226
  current_agent.reload_code_generation_agent()
219
- emit_info("[dim]Agent reloaded with updated config[/dim]")
227
+ emit_info("Agent reloaded with updated config")
220
228
  except Exception as reload_error:
221
229
  emit_warning(f"Config saved but agent reload failed: {reload_error}")
222
230
  else:
@@ -224,6 +232,23 @@ def handle_set_command(command: str) -> bool:
224
232
  return True
225
233
 
226
234
 
235
+ def _get_json_agents_pinned_to_model(model_name: str) -> list:
236
+ """Get JSON agents that have this model pinned in their JSON file."""
237
+ from code_puppy.agents.json_agent import discover_json_agents
238
+
239
+ pinned = []
240
+ json_agents = discover_json_agents()
241
+ for agent_name, agent_path in json_agents.items():
242
+ try:
243
+ with open(agent_path, "r") as f:
244
+ agent_data = json.load(f)
245
+ if agent_data.get("model") == model_name:
246
+ pinned.append(agent_name)
247
+ except Exception:
248
+ continue
249
+ return pinned
250
+
251
+
227
252
  @register_command(
228
253
  name="pin_model",
229
254
  description="Pin a specific model to an agent",
@@ -252,17 +277,17 @@ def handle_pin_model_command(command: str) -> bool:
252
277
 
253
278
  emit_info("Available models:")
254
279
  for model in available_models:
255
- emit_info(f" [cyan]{model}[/cyan]")
280
+ emit_info(f" {model}")
256
281
 
257
282
  if builtin_agents:
258
283
  emit_info("\nAvailable built-in agents:")
259
284
  for agent_name, description in builtin_agents.items():
260
- emit_info(f" [cyan]{agent_name}[/cyan] - {description}")
285
+ emit_info(f" {agent_name} - {description}")
261
286
 
262
287
  if json_agents:
263
288
  emit_info("\nAvailable JSON agents:")
264
289
  for agent_name, agent_path in json_agents.items():
265
- emit_info(f" [cyan]{agent_name}[/cyan] ({agent_path})")
290
+ emit_info(f" {agent_name} ({agent_path})")
266
291
  return True
267
292
 
268
293
  agent_name = tokens[1].lower()
@@ -298,12 +323,12 @@ def handle_pin_model_command(command: str) -> bool:
298
323
  if builtin_agents:
299
324
  emit_info("Available built-in agents:")
300
325
  for name, desc in builtin_agents.items():
301
- emit_info(f" [cyan]{name}[/cyan] - {desc}")
326
+ emit_info(f" {name} - {desc}")
302
327
 
303
328
  if json_agents:
304
329
  emit_info("\nAvailable JSON agents:")
305
330
  for name, path in json_agents.items():
306
- emit_info(f" [cyan]{name}[/cyan] ({path})")
331
+ emit_info(f" {name} ({path})")
307
332
  return True
308
333
 
309
334
  # Handle different agent types
@@ -359,6 +384,7 @@ def handle_pin_model_command(command: str) -> bool:
359
384
  def handle_unpin_command(command: str) -> bool:
360
385
  """Unpin a model from an agent (resets to default)."""
361
386
  from code_puppy.agents.json_agent import discover_json_agents
387
+ from code_puppy.config import get_agent_pinned_model
362
388
  from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning
363
389
 
364
390
  tokens = command.split()
@@ -377,12 +403,26 @@ def handle_unpin_command(command: str) -> bool:
377
403
  if builtin_agents:
378
404
  emit_info("Available built-in agents:")
379
405
  for agent_name, description in builtin_agents.items():
380
- emit_info(f" [cyan]{agent_name}[/cyan] - {description}")
406
+ pinned_model = get_agent_pinned_model(agent_name)
407
+ if pinned_model:
408
+ emit_info(f" {agent_name} - {description} [→ {pinned_model}]")
409
+ else:
410
+ emit_info(f" {agent_name} - {description}")
381
411
 
382
412
  if json_agents:
383
413
  emit_info("\nAvailable JSON agents:")
384
414
  for agent_name, agent_path in json_agents.items():
385
- emit_info(f" [cyan]{agent_name}[/cyan] ({agent_path})")
415
+ # Read the JSON file to check for pinned model
416
+ try:
417
+ with open(agent_path, "r") as f:
418
+ agent_config = json.load(f)
419
+ pinned_model = agent_config.get("model")
420
+ if pinned_model:
421
+ emit_info(f" {agent_name} ({agent_path}) [→ {pinned_model}]")
422
+ else:
423
+ emit_info(f" {agent_name} ({agent_path})")
424
+ except Exception:
425
+ emit_info(f" {agent_name} ({agent_path})")
386
426
  return True
387
427
 
388
428
  agent_name_input = tokens[1].lower()
@@ -422,12 +462,12 @@ def handle_unpin_command(command: str) -> bool:
422
462
  if builtin_agents:
423
463
  emit_info("Available built-in agents:")
424
464
  for name, desc in builtin_agents.items():
425
- emit_info(f" [cyan]{name}[/cyan] - {desc}")
465
+ emit_info(f" {name} - {desc}")
426
466
 
427
467
  if json_agents:
428
468
  emit_info("\nAvailable JSON agents:")
429
469
  for name, path in json_agents.items():
430
- emit_info(f" [cyan]{name}[/cyan] ({path})")
470
+ emit_info(f" {name} ({path})")
431
471
  return True
432
472
 
433
473
  try:
@@ -507,6 +547,37 @@ def handle_diff_command(command: str) -> bool:
507
547
  return True
508
548
 
509
549
 
550
+ @register_command(
551
+ name="colors",
552
+ description="Configure banner colors for tool outputs (THINKING, SHELL COMMAND, etc.)",
553
+ usage="/colors",
554
+ category="config",
555
+ )
556
+ def handle_colors_command(command: str) -> bool:
557
+ """Configure banner colors via interactive TUI."""
558
+ import asyncio
559
+ import concurrent.futures
560
+
561
+ from code_puppy.command_line.colors_menu import interactive_colors_picker
562
+ from code_puppy.config import set_banner_color
563
+ from code_puppy.messaging import emit_error, emit_success
564
+
565
+ # Show interactive picker for banner color configuration
566
+ with concurrent.futures.ThreadPoolExecutor() as executor:
567
+ future = executor.submit(lambda: asyncio.run(interactive_colors_picker()))
568
+ result = future.result(timeout=300) # 5 min timeout
569
+
570
+ if result:
571
+ # Apply the changes
572
+ try:
573
+ for banner_name, color in result.items():
574
+ set_banner_color(banner_name, color)
575
+ emit_success("Banner colors saved! šŸŽØ")
576
+ except Exception as e:
577
+ emit_error(f"Failed to apply banner color settings: {e}")
578
+ return True
579
+
580
+
510
581
  # ============================================================================
511
582
  # UTILITY FUNCTIONS
512
583
  # ============================================================================
@@ -518,6 +589,8 @@ def _show_color_options(color_type: str):
518
589
  # ============================================================================
519
590
 
520
591
  """Show available Rich color options organized by category."""
592
+ from rich.text import Text
593
+
521
594
  from code_puppy.messaging import emit_info
522
595
 
523
596
  # Standard Rich colors organized by category
@@ -575,11 +648,15 @@ def _show_color_options(color_type: str):
575
648
  ("sea_green1", "🟢"),
576
649
  ]
577
650
  emit_info(
578
- "[bold white on green]šŸŽØ Recommended Colors for Additions:[/bold white on green]"
651
+ Text.from_markup(
652
+ "[bold white on green]šŸŽØ Recommended Colors for Additions:[/bold white on green]"
653
+ )
579
654
  )
580
655
  for color, emoji in suggestions:
581
656
  emit_info(
582
- f" [cyan]{color:<16}[/cyan] [white on {color}]ā– ā– ā– ā– ā– ā– ā– ā– ā– ā– [/white on {color}] {emoji}"
657
+ Text.from_markup(
658
+ f" [cyan]{color:<16}[/cyan] [white on {color}]ā– ā– ā– ā– ā– ā– ā– ā– ā– ā– [/white on {color}] {emoji}"
659
+ )
583
660
  )
584
661
  elif color_type == "deletions":
585
662
  suggestions = [
@@ -590,22 +667,26 @@ def _show_color_options(color_type: str):
590
667
  ("dark_red", "šŸ”“"),
591
668
  ]
592
669
  emit_info(
593
- "[bold white on orange1]šŸŽØ Recommended Colors for Deletions:[/bold white on orange1]"
670
+ Text.from_markup(
671
+ "[bold white on orange1]šŸŽØ Recommended Colors for Deletions:[/bold white on orange1]"
672
+ )
594
673
  )
595
674
  for color, emoji in suggestions:
596
675
  emit_info(
597
- f" [cyan]{color:<16}[/cyan] [white on {color}]ā– ā– ā– ā– ā– ā– ā– ā– ā– ā– [/white on {color}] {emoji}"
676
+ Text.from_markup(
677
+ f" [cyan]{color:<16}[/cyan] [white on {color}]ā– ā– ā– ā– ā– ā– ā– ā– ā– ā– [/white on {color}] {emoji}"
678
+ )
598
679
  )
599
680
 
600
- emit_info("\n[bold]šŸŽØ All Available Rich Colors:[/bold]")
681
+ emit_info("\nšŸŽØ All Available Rich Colors:")
601
682
  for category, colors in color_categories.items():
602
- emit_info(f"\n[cyan]{category}:[/cyan]")
683
+ emit_info(f"\n{category}:")
603
684
  # Display in columns for better readability
604
685
  for i in range(0, len(colors), 4):
605
686
  row = colors[i : i + 4]
606
687
  row_text = " ".join([f"[{color}]ā– [/{color}] {color}" for color, _ in row])
607
- emit_info(f" {row_text}")
688
+ emit_info(Text.from_markup(f" {row_text}"))
608
689
 
609
- emit_info("\n[yellow]Usage:[/yellow] [cyan]/diff {color_type} <color_name>[/cyan]")
610
- emit_info("[dim]All diffs use white text on your chosen background colors[/dim]")
611
- emit_info("[dim]You can also use hex colors like #ff0000 or rgb(255,0,0)[/dim]")
690
+ emit_info("\nUsage: /diff {color_type} <color_name>")
691
+ emit_info("All diffs use white text on your chosen background colors")
692
+ emit_info("You can also use hex colors like #ff0000 or rgb(255,0,0)")
@@ -145,6 +145,8 @@ def handle_exit_command(command: str) -> bool:
145
145
  )
146
146
  def handle_agent_command(command: str) -> bool:
147
147
  """Handle agent switching."""
148
+ from rich.text import Text
149
+
148
150
  from code_puppy.agents import (
149
151
  get_agent_descriptions,
150
152
  get_available_agents,
@@ -199,9 +201,11 @@ def handle_agent_command(command: str) -> bool:
199
201
  f"Switched to agent: {new_agent.display_name}",
200
202
  message_group=group_id,
201
203
  )
202
- emit_info(f"[dim]{new_agent.description}[/dim]", message_group=group_id)
204
+ emit_info(f"{new_agent.description}", message_group=group_id)
203
205
  emit_info(
204
- f"[dim]Auto-save session rotated to: {new_session_id}[/dim]",
206
+ Text.from_markup(
207
+ f"[dim]Auto-save session rotated to: {new_session_id}[/dim]"
208
+ ),
205
209
  message_group=group_id,
206
210
  )
207
211
  else:
@@ -224,15 +228,19 @@ def handle_agent_command(command: str) -> bool:
224
228
  group_id = str(uuid.uuid4())
225
229
 
226
230
  emit_info(
227
- f"[bold green]Current Agent:[/bold green] {current_agent.display_name}",
231
+ Text.from_markup(
232
+ f"[bold green]Current Agent:[/bold green] {current_agent.display_name}"
233
+ ),
228
234
  message_group=group_id,
229
235
  )
230
236
  emit_info(
231
- f"[dim]{current_agent.description}[/dim]\n", message_group=group_id
237
+ Text.from_markup(f"[dim]{current_agent.description}[/dim]\n"),
238
+ message_group=group_id,
232
239
  )
233
240
 
234
241
  emit_info(
235
- "[bold magenta]Available Agents:[/bold magenta]", message_group=group_id
242
+ Text.from_markup("[bold magenta]Available Agents:[/bold magenta]"),
243
+ message_group=group_id,
236
244
  )
237
245
  for name, display_name in available_agents.items():
238
246
  description = descriptions.get(name, "No description")
@@ -240,13 +248,15 @@ def handle_agent_command(command: str) -> bool:
240
248
  " [green]← current[/green]" if name == current_agent.name else ""
241
249
  )
242
250
  emit_info(
243
- f" [cyan]{name:<12}[/cyan] {display_name}{current_marker}",
251
+ Text.from_markup(
252
+ f" [cyan]{name:<12}[/cyan] {display_name}{current_marker}"
253
+ ),
244
254
  message_group=group_id,
245
255
  )
246
- emit_info(f" [dim]{description}[/dim]", message_group=group_id)
256
+ emit_info(f" {description}", message_group=group_id)
247
257
 
248
258
  emit_info(
249
- "\n[yellow]Usage:[/yellow] /agent <agent-name>",
259
+ Text.from_markup("\n[yellow]Usage:[/yellow] /agent <agent-name>"),
250
260
  message_group=group_id,
251
261
  )
252
262
  return True
@@ -290,9 +300,11 @@ def handle_agent_command(command: str) -> bool:
290
300
  f"Switched to agent: {new_agent.display_name}",
291
301
  message_group=group_id,
292
302
  )
293
- emit_info(f"[dim]{new_agent.description}[/dim]", message_group=group_id)
303
+ emit_info(f"{new_agent.description}", message_group=group_id)
294
304
  emit_info(
295
- f"[dim]Auto-save session rotated to: {new_session_id}[/dim]",
305
+ Text.from_markup(
306
+ f"[dim]Auto-save session rotated to: {new_session_id}[/dim]"
307
+ ),
296
308
  message_group=group_id,
297
309
  )
298
310
  return True
@@ -366,10 +378,10 @@ async def interactive_agent_picker() -> str | None:
366
378
  set_awaiting_user_input(True)
367
379
  time.sleep(0.3) # Let spinners fully stop
368
380
 
369
- console = Console()
370
- console.print()
371
- console.print(panel)
372
- console.print()
381
+ local_console = Console()
382
+ emit_info("")
383
+ local_console.print(panel)
384
+ emit_info("")
373
385
 
374
386
  # Flush output before prompt_toolkit takes control
375
387
  sys.stdout.flush()
@@ -401,7 +413,7 @@ async def interactive_agent_picker() -> str | None:
401
413
  selected_agent = agent_name
402
414
 
403
415
  except (KeyboardInterrupt, EOFError):
404
- console.print("\n[bold red]āŠ— Cancelled by user[/bold red]")
416
+ emit_error("Cancelled by user")
405
417
  selected_agent = None
406
418
 
407
419
  finally:
@@ -460,10 +472,10 @@ async def interactive_model_picker() -> str | None:
460
472
  set_awaiting_user_input(True)
461
473
  time.sleep(0.3) # Let spinners fully stop
462
474
 
463
- console = Console()
464
- console.print()
465
- console.print(panel)
466
- console.print()
475
+ local_console = Console()
476
+ emit_info("")
477
+ local_console.print(panel)
478
+ emit_info("")
467
479
 
468
480
  # Flush output before prompt_toolkit takes control
469
481
  sys.stdout.flush()
@@ -490,7 +502,7 @@ async def interactive_model_picker() -> str | None:
490
502
  selected_model = selected_model[:-10].strip()
491
503
 
492
504
  except (KeyboardInterrupt, EOFError):
493
- console.print("\n[bold red]āŠ— Cancelled by user[/bold red]")
505
+ emit_error("Cancelled by user")
494
506
  selected_model = None
495
507
 
496
508
  finally:
@@ -7,8 +7,7 @@ import logging
7
7
  import os
8
8
  from typing import List, Optional
9
9
 
10
- from code_puppy.messaging import emit_info
11
- from code_puppy.tui_state import is_tui_mode
10
+ from code_puppy.messaging import emit_error, emit_info
12
11
 
13
12
  from .base import MCPCommandBase
14
13
  from .wizard_utils import run_interactive_install_wizard
@@ -42,18 +41,6 @@ class AddCommand(MCPCommandBase):
42
41
  if group_id is None:
43
42
  group_id = self.generate_group_id()
44
43
 
45
- # Check if in TUI mode and guide user to use Ctrl+T instead
46
- if is_tui_mode() and not args:
47
- emit_info(
48
- "šŸ’” In TUI mode, press Ctrl+T to open the MCP Install Wizard",
49
- message_group=group_id,
50
- )
51
- emit_info(
52
- " The wizard provides a better interface for browsing and installing MCP servers.",
53
- message_group=group_id,
54
- )
55
- return
56
-
57
44
  try:
58
45
  if args:
59
46
  # Parse JSON from arguments
@@ -115,7 +102,7 @@ class AddCommand(MCPCommandBase):
115
102
  emit_info("Required module not available", message_group=group_id)
116
103
  except Exception as e:
117
104
  logger.error(f"Error in add command: {e}")
118
- emit_info(f"[red]Error adding server: {e}[/red]", message_group=group_id)
105
+ emit_error(f"Error adding server: {e}", message_group=group_id)
119
106
 
120
107
  def _add_server_from_json(self, config_dict: dict, group_id: str) -> bool:
121
108
  """
@@ -179,5 +166,5 @@ class AddCommand(MCPCommandBase):
179
166
 
180
167
  except Exception as e:
181
168
  logger.error(f"Error adding server from JSON: {e}")
182
- emit_info(f"[red]Failed to add server: {e}[/red]", message_group=group_id)
169
+ emit_error(f"Failed to add server: {e}", message_group=group_id)
183
170
  return False
@@ -6,8 +6,6 @@ Provides base classes and common utilities used across all MCP command modules.
6
6
 
7
7
  import logging
8
8
 
9
- from rich.console import Console
10
-
11
9
  from code_puppy.mcp_.manager import get_mcp_manager
12
10
 
13
11
  # Configure logging
@@ -24,7 +22,6 @@ class MCPCommandBase:
24
22
 
25
23
  def __init__(self):
26
24
  """Initialize the base command handler."""
27
- self.console = Console()
28
25
  self.manager = get_mcp_manager()
29
26
  logger.debug(f"Initialized {self.__class__.__name__}")
30
27
 
@@ -7,7 +7,7 @@ MCP servers from the catalog.
7
7
  import os
8
8
  from typing import Dict, Optional
9
9
 
10
- from code_puppy.messaging import emit_warning
10
+ from code_puppy.messaging import emit_info, emit_success, emit_warning
11
11
 
12
12
  # Helpful hints for common environment variables
13
13
  ENV_VAR_HINTS = {
@@ -46,8 +46,8 @@ def prompt_for_server_config(manager, server) -> Optional[Dict]:
46
46
 
47
47
  from .utils import find_server_id_by_name
48
48
 
49
- print(f"\nšŸ“¦ Installing: {server.display_name}\n")
50
- print(f" {server.description}\n")
49
+ emit_info(f"\nšŸ“¦ Installing: {server.display_name}\n")
50
+ emit_info(f" {server.description}\n")
51
51
 
52
52
  # Get custom name
53
53
  default_name = server.name
@@ -55,7 +55,7 @@ def prompt_for_server_config(manager, server) -> Optional[Dict]:
55
55
  name_input = input(f" Server name [{default_name}]: ").strip()
56
56
  server_name = name_input if name_input else default_name
57
57
  except (KeyboardInterrupt, EOFError):
58
- print("")
58
+ emit_info("")
59
59
  emit_warning("Installation cancelled")
60
60
  return None
61
61
 
@@ -70,7 +70,7 @@ def prompt_for_server_config(manager, server) -> Optional[Dict]:
70
70
  emit_warning("Installation cancelled")
71
71
  return None
72
72
  except (KeyboardInterrupt, EOFError):
73
- print("")
73
+ emit_info("")
74
74
  emit_warning("Installation cancelled")
75
75
  return None
76
76
 
@@ -80,17 +80,17 @@ def prompt_for_server_config(manager, server) -> Optional[Dict]:
80
80
  # Collect environment variables
81
81
  required_env_vars = server.get_environment_vars()
82
82
  if required_env_vars:
83
- print("\n šŸ”‘ Environment Variables:")
83
+ emit_info("\n šŸ”‘ Environment Variables:")
84
84
  for var in required_env_vars:
85
85
  current_value = os.environ.get(var, "")
86
86
  if current_value:
87
- print(f" āœ“ {var}: Already set")
87
+ emit_info(f" āœ“ {var}: Already set")
88
88
  env_vars[var] = current_value
89
89
  else:
90
90
  try:
91
91
  hint = get_env_var_hint(var)
92
92
  if hint:
93
- print(f" {hint}")
93
+ emit_info(f" {hint}")
94
94
  value = input(f" Enter {var}: ").strip()
95
95
  if value:
96
96
  env_vars[var] = value
@@ -98,14 +98,14 @@ def prompt_for_server_config(manager, server) -> Optional[Dict]:
98
98
  set_config_value(var, value)
99
99
  os.environ[var] = value
100
100
  except (KeyboardInterrupt, EOFError):
101
- print("")
101
+ emit_info("")
102
102
  emit_warning("Installation cancelled")
103
103
  return None
104
104
 
105
105
  # Collect command line arguments
106
106
  required_cmd_args = server.get_command_line_args()
107
107
  if required_cmd_args:
108
- print("\n āš™ļø Configuration:")
108
+ emit_info("\n āš™ļø Configuration:")
109
109
  for arg_config in required_cmd_args:
110
110
  name = arg_config.get("name", "")
111
111
  prompt_text = arg_config.get("prompt", name)
@@ -128,7 +128,7 @@ def prompt_for_server_config(manager, server) -> Optional[Dict]:
128
128
  emit_warning(f"Required value '{name}' not provided")
129
129
  return None
130
130
  except (KeyboardInterrupt, EOFError):
131
- print("")
131
+ emit_info("")
132
132
  emit_warning("Installation cancelled")
133
133
  return None
134
134
 
@@ -161,16 +161,16 @@ def install_catalog_server(manager, server, config: Dict) -> bool:
161
161
  # Generate a group ID for messages
162
162
  group_id = f"mcp-install-{uuid.uuid4().hex[:8]}"
163
163
 
164
- print(f"\n šŸ“¦ Installing {server.display_name} as '{server_name}'...")
164
+ emit_info(f"\n šŸ“¦ Installing {server.display_name} as '{server_name}'...")
165
165
 
166
166
  success = install_server_from_catalog(
167
167
  manager, server, server_name, env_vars, cmd_args, group_id
168
168
  )
169
169
 
170
170
  if success:
171
- print(f"\n āœ… Successfully installed '{server_name}'!")
172
- print(f" Use '/mcp start {server_name}' to start the server.\n")
171
+ emit_success(f"\n āœ… Successfully installed '{server_name}'!")
172
+ emit_info(f" Use '/mcp start {server_name}' to start the server.\n")
173
173
  else:
174
- print("\n āŒ Installation failed.\n")
174
+ emit_warning("\n āŒ Installation failed.\n")
175
175
 
176
176
  return success