code-puppy 0.0.749__tar.gz → 0.0.751__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {code_puppy-0.0.749 → code_puppy-0.0.751}/PKG-INFO +2 -2
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_builder.py +5 -1
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_runtime.py +14 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/base_agent.py +16 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/json_agent.py +7 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/callbacks.py +46 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/cli_runner.py +6 -1
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/model_switching.py +55 -1
- code_puppy-0.0.751/code_puppy/platform_utils.py +42 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/splash.py +73 -37
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/subagent_invocation.py +5 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/pyproject.toml +2 -2
- code_puppy-0.0.749/code_puppy/platform_utils.py +0 -27
- {code_puppy-0.0.749 → code_puppy-0.0.751}/.gitignore +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/LICENSE +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/README.md +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agent_execution_context.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/__init__.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_compaction.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_diagnostics.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_history.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_key_listeners.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_non_streaming_render.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_output_limits.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_run_signals.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/_steer_processor.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/agent_creator_agent.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/agent_helios.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/agent_manager.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/agent_model_judge.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/agent_planning.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/agent_qa_kitten.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/agent_web_retriever.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/event_stream_handler.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/retry_profiles.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/run_stats.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/smooth_stream.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/agents/subagent_stream_handler.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/atomic_io.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/atomic_json.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/chatgpt_codex_client.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/claude_cache_client.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/add_model_menu.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/agent_menu.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/attachments.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/autosave_menu.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/autosave_search.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/clipboard.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/colors_menu.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/command_handler.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/command_registry.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/config_apply.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/config_commands.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/core_commands.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/diff_menu.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/file_index.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/image_utils.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/edit_command.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/install_menu.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/logs_command.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/mcp_servers_store.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/silence_warning_command.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/trust_command.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp_binding_menu.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/mcp_completion.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/model_picker_completion.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/model_settings_menu.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/onboarding_slides.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/onboarding_wizard.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/pagination.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/pin_command_completion.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/session_commands.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/set_menu.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/set_menu_catalog.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/set_menu_render.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/set_menu_schema.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/set_menu_settings.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/set_menu_shims.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/set_menu_values.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/shell_passthrough.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/skills_completion.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/uc_menu.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/config.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/config_file.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/error_logging.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/gemini_code_assist.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/gemini_model.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/hook_engine/README.md +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/hook_engine/__init__.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/hook_engine/aliases.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/hook_engine/engine.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/hook_engine/executor.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/hook_engine/matcher.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/hook_engine/models.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/hook_engine/registry.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/hook_engine/validator.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/http_utils.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/__init__.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/audit.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/catalog.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/formats.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/locale.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/locales/en-US.json +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/locales/es.json +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/locales/fr-CA.json +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/plurals.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/pseudo.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/i18n/translate.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/kennel_provider.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/keymap.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/list_filtering.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/main.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/__init__.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/agent_bindings.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/async_lifecycle.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/blocking_startup.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/circuit_breaker.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/config_wizard.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/dashboard.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/error_isolation.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/examples/retry_example.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/health_monitor.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/managed_server.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/manager.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/mcp_logs.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/project_config.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/registry.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/retry_manager.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/server_registry_catalog.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/status_tracker.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/system_tools.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/tool_arg_coercion.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_/toolset_utils.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_prompts/__init__.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/mcp_prompts/hook_creator.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/__init__.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/bar_painters.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/bar_rendering.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/bottom_bar.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/bus.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/chords.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/commands.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/editor_actions.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/editor_completion.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/editor_display.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/editor_history.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/editor_keys.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/editor_paste.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/external_editor.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/inline_bar.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/line_editor.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/markdown_patches.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/messages.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/pause_controller.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/prompt_prefix_style.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/rich_renderer.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/run_ui.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/run_ui_wiring.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/spinner/__init__.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/subagent_console.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/messaging/transcript_guard.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/model_descriptions.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/model_factory.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/model_utils.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/models.json +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/models_dev_parser.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/observability.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/plugins/__init__.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/plugins/config.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/plugins/oauth_pasteback.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/plugins/oauth_puppy_html.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/plugins/trust.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/plugins/trust_notice.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/provider_credentials.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/provider_identity.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/pydantic_patches.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/reopenable_async_client.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/secret_store.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/secret_store_backends.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/session_format_migration.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/session_lifecycle.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/session_migration.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/session_storage.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/session_surrogate_unpickler.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/skill_provider.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/terminal_utils.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/token_usage.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/__init__.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/agent_tools.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/__init__.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/constants.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/demo_tui.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/handler.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/models.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/registration.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/renderers.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/terminal_ui.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/theme.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/ask_user_question/tui_loop.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/__init__.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_control.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_interactions.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_locator_resolver.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_locators.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_manager.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_navigation.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_page_snapshot.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_screenshot.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_scripts.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_semantic_interactions.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/browser_workflows.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/browser/tool_registry.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/command_runner.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/display.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/file_modifications.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/file_operations.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/file_permission_state.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/fs_access.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/image_tools.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/io_backends.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/model_tools.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/shell_backgrounding.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/skills_tools.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/subagent_context.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/subagent_usage_metrics.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/tools/universal_constructor.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/undo_manager.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/universal_constructor_provider.py +0 -0
- {code_puppy-0.0.749 → code_puppy-0.0.751}/code_puppy/version_checker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: code-puppy
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.751
|
|
4
4
|
Summary: Code generation agent
|
|
5
5
|
Project-URL: repository, https://github.com/mpfaffenberger/code_puppy
|
|
6
6
|
Project-URL: HomePage, https://github.com/mpfaffenberger/code_puppy
|
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
16
16
|
Classifier: Topic :: Software Development :: Code Generators
|
|
17
17
|
Requires-Python: <3.15,>=3.11
|
|
18
18
|
Requires-Dist: agent-client-protocol<0.12,>=0.11.0
|
|
19
|
-
Requires-Dist: anthropic
|
|
19
|
+
Requires-Dist: anthropic<1,>=0.108.0
|
|
20
20
|
Requires-Dist: anyio>=4.7.0
|
|
21
21
|
Requires-Dist: azure-identity>=1.15.0
|
|
22
22
|
Requires-Dist: boto3>=1.43.9
|
|
@@ -637,10 +637,15 @@ def build_pydantic_agent(
|
|
|
637
637
|
model_settings = make_model_settings(resolved_model_name)
|
|
638
638
|
history_processor = make_history_processor(agent)
|
|
639
639
|
steer_processor = make_steer_history_processor(agent)
|
|
640
|
+
logical_agent_name = getattr(agent, "name", None) or agent.__class__.__name__
|
|
640
641
|
|
|
641
642
|
def _new_pydantic_agent(toolsets: List[Any]) -> PydanticAgent:
|
|
642
643
|
return PydanticAgent(
|
|
643
644
|
model=model,
|
|
645
|
+
# Explicit name: without it pydantic-ai infers one from the
|
|
646
|
+
# caller's frame variables, so observability spans read
|
|
647
|
+
# "invoke_agent pydantic_agent" instead of the logical agent name.
|
|
648
|
+
name=logical_agent_name,
|
|
644
649
|
instructions=instructions,
|
|
645
650
|
output_type=output_type,
|
|
646
651
|
retries=3,
|
|
@@ -667,7 +672,6 @@ def build_pydantic_agent(
|
|
|
667
672
|
# tool registry actually produced, and filter MCP to avoid name clashes.
|
|
668
673
|
probe_agent = _new_pydantic_agent(toolsets=[])
|
|
669
674
|
agent_tools = agent.get_available_tools()
|
|
670
|
-
logical_agent_name = getattr(agent, "name", None) or agent.__class__.__name__
|
|
671
675
|
register_tools_for_agent(
|
|
672
676
|
probe_agent,
|
|
673
677
|
agent_tools,
|
|
@@ -693,6 +693,20 @@ async def _run_with_mcp_impl(
|
|
|
693
693
|
# Hook failures must never block the run.
|
|
694
694
|
pass
|
|
695
695
|
|
|
696
|
+
# Let a ``model_select`` hook route THIS turn to a different model (e.g.
|
|
697
|
+
# small-vs-large by complexity) before the pydantic agent is built. This
|
|
698
|
+
# resets any prior turn's auto choice, respects an explicit runtime
|
|
699
|
+
# override, and invalidates the cached agent when the model changes so the
|
|
700
|
+
# build below picks it up. No-op (and near-zero cost) if no plugin
|
|
701
|
+
# registered the hook.
|
|
702
|
+
try:
|
|
703
|
+
from code_puppy.model_switching import resolve_run_model_selection
|
|
704
|
+
|
|
705
|
+
resolve_run_model_selection(agent, prompt, agent._message_history, group_id)
|
|
706
|
+
except Exception:
|
|
707
|
+
# Selection must never block a run.
|
|
708
|
+
pass
|
|
709
|
+
|
|
696
710
|
if agent._code_generation_agent is None:
|
|
697
711
|
build_pydantic_agent(agent)
|
|
698
712
|
pydantic_agent = agent._code_generation_agent
|
|
@@ -72,6 +72,11 @@ class BaseAgent(ABC):
|
|
|
72
72
|
self._last_model_name: Optional[str] = None
|
|
73
73
|
self._runtime_model_name_override: Optional[str] = None
|
|
74
74
|
self._runtime_system_prompt_additions: List[str] = []
|
|
75
|
+
# Model chosen by a ``model_select`` hook for the current run. Slots
|
|
76
|
+
# below an explicit runtime override but above pinned/JSON/global, and
|
|
77
|
+
# is reset at the start of every run (see resolve_run_model_selection),
|
|
78
|
+
# so it never leaks across turns.
|
|
79
|
+
self._auto_model_override: Optional[str] = None
|
|
75
80
|
self._puppy_rules: Optional[str] = None
|
|
76
81
|
self._mcp_servers: List[Any] = []
|
|
77
82
|
self.cur_model: Optional[pydantic_ai.models.Model] = None
|
|
@@ -125,6 +130,14 @@ class BaseAgent(ABC):
|
|
|
125
130
|
"""
|
|
126
131
|
self._runtime_model_name_override = model_name
|
|
127
132
|
|
|
133
|
+
def get_auto_model_override(self) -> Optional[str]:
|
|
134
|
+
"""Return the model chosen by a ``model_select`` hook for this run."""
|
|
135
|
+
return self._auto_model_override
|
|
136
|
+
|
|
137
|
+
def set_auto_model_override(self, model_name: Optional[str]) -> None:
|
|
138
|
+
"""Set the ``model_select``-chosen model for this run (not persisted)."""
|
|
139
|
+
self._auto_model_override = model_name
|
|
140
|
+
|
|
128
141
|
@contextmanager
|
|
129
142
|
def temporary_model_name_override(
|
|
130
143
|
self, model_name: Optional[str]
|
|
@@ -154,6 +167,9 @@ class BaseAgent(ABC):
|
|
|
154
167
|
override = self.get_runtime_model_name_override()
|
|
155
168
|
if override:
|
|
156
169
|
return override
|
|
170
|
+
auto = self.get_auto_model_override()
|
|
171
|
+
if auto:
|
|
172
|
+
return auto
|
|
157
173
|
pinned = get_agent_pinned_model(self.name)
|
|
158
174
|
return pinned if pinned else get_global_model_name()
|
|
159
175
|
|
|
@@ -220,6 +220,13 @@ class JSONAgent(BaseAgent):
|
|
|
220
220
|
if override:
|
|
221
221
|
return override
|
|
222
222
|
|
|
223
|
+
# A ``model_select`` hook choice outranks the JSON ``model`` field so
|
|
224
|
+
# per-turn routing works for JSON agents too (see get_model_name in
|
|
225
|
+
# BaseAgent for the full precedence ladder).
|
|
226
|
+
auto = self.get_auto_model_override()
|
|
227
|
+
if auto:
|
|
228
|
+
return auto
|
|
229
|
+
|
|
223
230
|
result = self._config.get("model")
|
|
224
231
|
if result is None or (isinstance(result, str) and not result.strip()):
|
|
225
232
|
result = super().get_model_name()
|
|
@@ -46,6 +46,7 @@ PhaseType = Literal[
|
|
|
46
46
|
"agent_run_start",
|
|
47
47
|
"agent_run_end",
|
|
48
48
|
"agent_run_result",
|
|
49
|
+
"model_select",
|
|
49
50
|
"register_mcp_catalog_servers",
|
|
50
51
|
"register_browser_types",
|
|
51
52
|
"register_model_providers",
|
|
@@ -132,6 +133,7 @@ _callbacks: Dict[PhaseType, List[CallbackFunc]] = {
|
|
|
132
133
|
"agent_run_start": [],
|
|
133
134
|
"agent_run_end": [],
|
|
134
135
|
"agent_run_result": [],
|
|
136
|
+
"model_select": [],
|
|
135
137
|
"register_mcp_catalog_servers": [],
|
|
136
138
|
"register_browser_types": [],
|
|
137
139
|
"register_model_providers": [],
|
|
@@ -1154,6 +1156,50 @@ async def on_agent_run_start(
|
|
|
1154
1156
|
)
|
|
1155
1157
|
|
|
1156
1158
|
|
|
1159
|
+
def on_model_select(
|
|
1160
|
+
*,
|
|
1161
|
+
agent_name: str,
|
|
1162
|
+
current_model: str | None,
|
|
1163
|
+
prompt: str,
|
|
1164
|
+
messages: List[Any],
|
|
1165
|
+
session_id: str | None = None,
|
|
1166
|
+
) -> str | None:
|
|
1167
|
+
"""Ask plugins to choose the model for the current run.
|
|
1168
|
+
|
|
1169
|
+
Fires once per run, before the pydantic agent is (re)built. Lets a plugin
|
|
1170
|
+
route each turn to a different model based on the agent, the effective
|
|
1171
|
+
("would-be") model, and the message history -- e.g. a small model for
|
|
1172
|
+
trivial turns and a frontier model when it matters, or a cost/latency/
|
|
1173
|
+
failover policy.
|
|
1174
|
+
|
|
1175
|
+
Precedence: an explicit runtime override still wins over this hook; this
|
|
1176
|
+
hook wins over the pinned / JSON / global model. The first callback to
|
|
1177
|
+
return a non-empty string wins; return ``None`` to defer.
|
|
1178
|
+
|
|
1179
|
+
Args:
|
|
1180
|
+
agent_name: Name of the agent about to run.
|
|
1181
|
+
current_model: The model that would be used absent any hook.
|
|
1182
|
+
prompt: The current user prompt after submission hooks have rewritten it.
|
|
1183
|
+
messages: The agent's prior message history for this run.
|
|
1184
|
+
session_id: Optional per-run identifier.
|
|
1185
|
+
|
|
1186
|
+
Returns:
|
|
1187
|
+
A model name to use for this run, or ``None`` to keep ``current_model``.
|
|
1188
|
+
"""
|
|
1189
|
+
results = _trigger_callbacks_sync(
|
|
1190
|
+
"model_select",
|
|
1191
|
+
agent_name=agent_name,
|
|
1192
|
+
current_model=current_model,
|
|
1193
|
+
prompt=prompt,
|
|
1194
|
+
messages=messages,
|
|
1195
|
+
session_id=session_id,
|
|
1196
|
+
)
|
|
1197
|
+
for result in results:
|
|
1198
|
+
if isinstance(result, str) and result.strip():
|
|
1199
|
+
return result
|
|
1200
|
+
return None
|
|
1201
|
+
|
|
1202
|
+
|
|
1157
1203
|
async def on_agent_run_end(
|
|
1158
1204
|
agent_name: str,
|
|
1159
1205
|
model_name: str,
|
|
@@ -274,14 +274,19 @@ async def main():
|
|
|
274
274
|
try:
|
|
275
275
|
import pyfiglet
|
|
276
276
|
|
|
277
|
+
# Width-aware banner: full CODE PUPPY when it fits, PUP when
|
|
278
|
+
# the terminal is too narrow (phones, tight splits).
|
|
279
|
+
banner_columns = display_console.width
|
|
277
280
|
intro_lines = pyfiglet.figlet_format(
|
|
278
|
-
startup_banner_text(), font="ansi_shadow"
|
|
281
|
+
startup_banner_text(banner_columns), font="ansi_shadow"
|
|
279
282
|
).split("\n")
|
|
280
283
|
|
|
281
284
|
# Simple blue to green gradient (top to bottom)
|
|
282
285
|
gradient_colors = ["bright_blue", "bright_cyan", "bright_green"]
|
|
283
286
|
display_console.print("\n")
|
|
284
287
|
|
|
288
|
+
# Left-justified on purpose -- the full-screen splash handles
|
|
289
|
+
# the centered spectacle; this banner tops the scrollback.
|
|
285
290
|
lines = []
|
|
286
291
|
for line_num, line in enumerate(intro_lines):
|
|
287
292
|
if line.strip():
|
|
@@ -2,11 +2,65 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import Optional
|
|
5
|
+
from typing import Any, List, Optional
|
|
6
6
|
|
|
7
7
|
from code_puppy.config import set_model_name
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
def resolve_run_model_selection(
|
|
11
|
+
agent: Any,
|
|
12
|
+
prompt: str,
|
|
13
|
+
messages: List[Any],
|
|
14
|
+
session_id: Optional[str] = None,
|
|
15
|
+
) -> Optional[str]:
|
|
16
|
+
"""Apply the ``model_select`` hook for one run and return the chosen model.
|
|
17
|
+
|
|
18
|
+
Called once at the start of every run, BEFORE the pydantic agent is built.
|
|
19
|
+
|
|
20
|
+
Behaviour / precedence:
|
|
21
|
+
* The per-run auto override is always cleared first, so a choice never
|
|
22
|
+
leaks into a later turn.
|
|
23
|
+
* An explicit runtime override (e.g. ``invoke_agent_with_model``) wins and
|
|
24
|
+
short-circuits the hook entirely.
|
|
25
|
+
* Otherwise the hook is asked to pick a model. If it returns a name that
|
|
26
|
+
differs from the effective model, we install it as the run's auto
|
|
27
|
+
override and invalidate the cached pydantic agent so the build picks up
|
|
28
|
+
the new model.
|
|
29
|
+
|
|
30
|
+
Returns the chosen model name if the hook changed it, else ``None``.
|
|
31
|
+
Never raises -- a misbehaving selector must not break a run.
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
# Reset any previous turn's auto choice so it can't leak forward.
|
|
35
|
+
if agent.get_auto_model_override():
|
|
36
|
+
agent.set_auto_model_override(None)
|
|
37
|
+
agent._code_generation_agent = None
|
|
38
|
+
|
|
39
|
+
# Explicit runtime override always wins -- don't even ask the hook.
|
|
40
|
+
if agent.get_runtime_model_name_override():
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
from code_puppy.callbacks import on_model_select
|
|
44
|
+
|
|
45
|
+
current = agent.get_model_name()
|
|
46
|
+
selected = on_model_select(
|
|
47
|
+
agent_name=getattr(agent, "name", None),
|
|
48
|
+
current_model=current,
|
|
49
|
+
prompt=prompt,
|
|
50
|
+
messages=messages or [],
|
|
51
|
+
session_id=session_id,
|
|
52
|
+
)
|
|
53
|
+
if selected and selected != current:
|
|
54
|
+
agent.set_auto_model_override(selected)
|
|
55
|
+
# Force a rebuild so the new model is actually used this turn.
|
|
56
|
+
agent._code_generation_agent = None
|
|
57
|
+
return selected
|
|
58
|
+
except Exception:
|
|
59
|
+
# A broken selector must never block the agent run.
|
|
60
|
+
pass
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
10
64
|
def _get_effective_agent_model(agent) -> Optional[str]:
|
|
11
65
|
"""Safely fetch the effective model name for an agent."""
|
|
12
66
|
try:
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Small platform-detection helpers shared by UI entry points."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
# ``CODE PUPPY`` rendered in pyfiglet's ansi_shadow spans 79 columns
|
|
10
|
+
# (pinned by tests/test_platform_utils.py). Baked so banner selection
|
|
11
|
+
# stays import-light -- no pyfiglet needed just to pick a label.
|
|
12
|
+
_FULL_BANNER = "CODE PUPPY"
|
|
13
|
+
_FULL_BANNER_WIDTH = 79
|
|
14
|
+
_COMPACT_BANNER = "PUP"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def is_android() -> bool:
|
|
18
|
+
"""Return whether Code Puppy is running on Android or in Termux.
|
|
19
|
+
|
|
20
|
+
Current Android Python builds expose an ``android`` platform name, while
|
|
21
|
+
older Termux builds may report ``linux``. Environment markers cover that
|
|
22
|
+
compatibility gap without making each UI entry point invent its own check.
|
|
23
|
+
"""
|
|
24
|
+
if sys.platform.startswith("android"):
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
return bool(
|
|
28
|
+
os.environ.get("TERMUX_VERSION")
|
|
29
|
+
or (os.environ.get("ANDROID_ROOT") and os.environ.get("ANDROID_DATA"))
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def startup_banner_text(columns: int | None = None) -> str:
|
|
34
|
+
"""Return the widest startup banner label the terminal can fit.
|
|
35
|
+
|
|
36
|
+
Width-based rather than platform-based: a phone terminal in landscape
|
|
37
|
+
earns the full banner, and a squeezed desktop split gets the compact
|
|
38
|
+
one. ``columns`` defaults to the detected terminal width.
|
|
39
|
+
"""
|
|
40
|
+
if columns is None:
|
|
41
|
+
columns = shutil.get_terminal_size(fallback=(80, 24)).columns
|
|
42
|
+
return _FULL_BANNER if columns >= _FULL_BANNER_WIDTH else _COMPACT_BANNER
|
|
@@ -7,10 +7,11 @@ and stop it the moment they finish. Do not import anything from
|
|
|
7
7
|
``code_puppy`` here -- that would defeat the entire point.
|
|
8
8
|
|
|
9
9
|
The animation is pure art (no text), so it stays outside the i18n seam.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
It runs full-screen on the alternate screen buffer, centered both axes;
|
|
11
|
+
leaving the alt screen on stop() restores the user's terminal exactly as
|
|
12
|
+
it was. Stray import-time output (e.g. plugin warnings emitted while
|
|
13
|
+
``cli_runner`` loads) is buffered and replayed afterwards. Everything
|
|
14
|
+
fails soft: a broken terminal gets a no-op splash, never a crashed CLI.
|
|
14
15
|
"""
|
|
15
16
|
|
|
16
17
|
from __future__ import annotations
|
|
@@ -49,33 +50,40 @@ _PYRAMID = (
|
|
|
49
50
|
_CHARS = (" ", "\u2591", "\u2592", "\u2588")
|
|
50
51
|
_PYRAMID_WIDTH = 44
|
|
51
52
|
|
|
52
|
-
# "
|
|
53
|
-
# font as the
|
|
54
|
-
# stdlib and this module must stay import-light. Solid blocks render as
|
|
55
|
-
# neon core tier; the box-drawing shadow trim renders as the mid
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"\u2588\u2588\
|
|
59
|
-
"\u2588\u2588\
|
|
60
|
-
"\u2588\u2588\
|
|
61
|
-
"\u2588\u2588\u2551 \
|
|
62
|
-
"\u255a\
|
|
53
|
+
# "CODE PUPPY" / "PUP" pre-rendered in pyfiglet's ansi_shadow (the same
|
|
54
|
+
# font as the scrollback banner). Baked as constants: pyfiglet is not
|
|
55
|
+
# stdlib and this module must stay import-light. Solid blocks render as
|
|
56
|
+
# the neon core tier; the box-drawing shadow trim renders as the mid
|
|
57
|
+
# glow tier.
|
|
58
|
+
_BANNER_FULL = (
|
|
59
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557",
|
|
60
|
+
"\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255d\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255d \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255a\u2588\u2588\u2557 \u2588\u2588\u2554\u255d",
|
|
61
|
+
"\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d \u255a\u2588\u2588\u2588\u2588\u2554\u255d",
|
|
62
|
+
"\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255d \u2588\u2588\u2554\u2550\u2550\u2550\u255d \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255d \u2588\u2588\u2554\u2550\u2550\u2550\u255d \u255a\u2588\u2588\u2554\u255d",
|
|
63
|
+
"\u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551",
|
|
64
|
+
" \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u255d \u255a\u2550\u255d \u255a\u2550\u255d",
|
|
63
65
|
)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
67
|
-
"\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\
|
|
68
|
-
"\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d
|
|
69
|
-
"\u2588\u2588\u2554\u2550\u2550\u2550\u255d
|
|
70
|
-
"\u2588\u2588\u2551
|
|
71
|
-
"\u255a\u2550\u255d
|
|
66
|
+
_BANNER_FULL_WIDTH = 79
|
|
67
|
+
_BANNER_COMPACT = (
|
|
68
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
69
|
+
"\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
|
|
70
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d",
|
|
71
|
+
"\u2588\u2588\u2554\u2550\u2550\u2550\u255d \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u255d",
|
|
72
|
+
"\u2588\u2588\u2551 \u255a\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255d\u2588\u2588\u2551",
|
|
73
|
+
"\u255a\u2550\u255d \u255a\u2550\u2550\u2550\u2550\u2550\u255d \u255a\u2550\u255d",
|
|
72
74
|
)
|
|
73
|
-
|
|
75
|
+
_BANNER_COMPACT_WIDTH = 25
|
|
74
76
|
_SHADOW_TRIM = frozenset("\u2550\u2551\u2554\u2557\u255a\u255d")
|
|
75
77
|
|
|
76
78
|
_RESET = "\x1b[0m"
|
|
77
79
|
_HIDE_CURSOR = "\x1b[?25l"
|
|
78
80
|
_SHOW_CURSOR = "\x1b[?25h"
|
|
81
|
+
# Alternate screen buffer (like vim/less): the splash owns a blank
|
|
82
|
+
# full-screen canvas, and leaving it restores the user's scrollback
|
|
83
|
+
# untouched -- no erase bookkeeping needed on the way out.
|
|
84
|
+
_ALT_SCREEN_ON = "\x1b[?1049h"
|
|
85
|
+
_ALT_SCREEN_OFF = "\x1b[?1049l"
|
|
86
|
+
_CLEAR_SCREEN = "\x1b[2J"
|
|
79
87
|
# Synchronized output (DEC 2026): terminals that support it (iTerm2, kitty,
|
|
80
88
|
# WezTerm, Ghostty, Alacritty, ...) render the whole frame atomically --
|
|
81
89
|
# zero flicker. Terminals that don't simply ignore the markers.
|
|
@@ -119,13 +127,13 @@ def _compose_rows(columns: int, lines: int):
|
|
|
119
127
|
|
|
120
128
|
Returns a list of (kind, content) rows: ``art`` rows are pyramid tier
|
|
121
129
|
digits, ``text`` rows are literal figlet glyphs. Degrades gracefully:
|
|
122
|
-
|
|
130
|
+
pyramid + "CODE PUPPY" -> pyramid + "PUP" -> pyramid only.
|
|
123
131
|
"""
|
|
124
|
-
|
|
125
|
-
|
|
132
|
+
full = [(line, _BANNER_FULL_WIDTH) for line in _BANNER_FULL]
|
|
133
|
+
compact = [(line, _BANNER_COMPACT_WIDTH) for line in _BANNER_COMPACT]
|
|
126
134
|
variants = (
|
|
127
|
-
(
|
|
128
|
-
(
|
|
135
|
+
(full, _BANNER_FULL_WIDTH),
|
|
136
|
+
(compact, _BANNER_COMPACT_WIDTH),
|
|
129
137
|
([], _PYRAMID_WIDTH),
|
|
130
138
|
)
|
|
131
139
|
for text_rows, text_width in variants:
|
|
@@ -146,6 +154,22 @@ def _compose_rows(columns: int, lines: int):
|
|
|
146
154
|
return rows
|
|
147
155
|
|
|
148
156
|
|
|
157
|
+
def _center_rows(rows, columns: int):
|
|
158
|
+
"""Pad every row so the artwork block sits horizontally centered.
|
|
159
|
+
|
|
160
|
+
Art rows pad with '0' (the empty glow tier) and text rows with spaces,
|
|
161
|
+
so ``_build_frame`` needs no knowledge of centering at all.
|
|
162
|
+
"""
|
|
163
|
+
block_width = max((len(content) for _, content in rows), default=0)
|
|
164
|
+
pad = max(0, (columns - block_width) // 2)
|
|
165
|
+
if not pad:
|
|
166
|
+
return list(rows)
|
|
167
|
+
return [
|
|
168
|
+
(kind, ("0" * pad if kind == "art" else " " * pad) + content)
|
|
169
|
+
for kind, content in rows
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
|
|
149
173
|
def _build_frame(phase: int, truecolor: bool, rows) -> str:
|
|
150
174
|
"""Render one frame as newline-joined lines with NO trailing newline.
|
|
151
175
|
|
|
@@ -249,20 +273,25 @@ class _Splash:
|
|
|
249
273
|
target=self._run, name="code-puppy-splash", daemon=True
|
|
250
274
|
)
|
|
251
275
|
size = shutil.get_terminal_size(fallback=(80, 24))
|
|
252
|
-
self._rows = _compose_rows(size.columns, size.lines)
|
|
276
|
+
self._rows = _center_rows(_compose_rows(size.columns, size.lines), size.columns)
|
|
253
277
|
self._height = len(self._rows)
|
|
278
|
+
# 1-based row where the frame starts, vertically centering the art.
|
|
279
|
+
self._top_row = max(1, (size.lines - self._height) // 2 + 1)
|
|
254
280
|
self._stopped = False
|
|
255
281
|
self._thread.start()
|
|
256
282
|
|
|
257
283
|
def _run(self) -> None:
|
|
258
284
|
phase = 0
|
|
259
|
-
#
|
|
260
|
-
#
|
|
261
|
-
#
|
|
262
|
-
reposition = f"\
|
|
285
|
+
# The alt screen is ours alone, so every frame repaints from the
|
|
286
|
+
# same absolute, vertically-centered origin. The frame has no
|
|
287
|
+
# trailing newline, so nothing ever scrolls.
|
|
288
|
+
reposition = f"\x1b[{self._top_row};1H"
|
|
263
289
|
try:
|
|
264
290
|
first = _build_frame(phase, self._truecolor, self._rows)
|
|
265
|
-
self._stream.write(
|
|
291
|
+
self._stream.write(
|
|
292
|
+
f"{_ALT_SCREEN_ON}{_HIDE_CURSOR}{_CLEAR_SCREEN}"
|
|
293
|
+
f"{_SYNC_START}{reposition}{first}{_SYNC_END}"
|
|
294
|
+
)
|
|
266
295
|
self._stream.flush()
|
|
267
296
|
while not self._stop_event.wait(_FRAME_SECONDS):
|
|
268
297
|
phase = (phase + 2) % _SHEEN_PERIOD
|
|
@@ -289,8 +318,9 @@ class _Splash:
|
|
|
289
318
|
self._stop_event.set()
|
|
290
319
|
self._thread.join(timeout=1.0)
|
|
291
320
|
try:
|
|
292
|
-
#
|
|
293
|
-
|
|
321
|
+
# Leaving the alt screen restores the pre-splash terminal
|
|
322
|
+
# content and cursor position in one move; nothing to erase.
|
|
323
|
+
self._stream.write(f"{_ALT_SCREEN_OFF}{_SHOW_CURSOR}")
|
|
294
324
|
self._stream.flush()
|
|
295
325
|
except Exception:
|
|
296
326
|
pass
|
|
@@ -358,6 +388,12 @@ def start_splash(
|
|
|
358
388
|
return _NullSplash()
|
|
359
389
|
if not _wants_splash(sys.argv):
|
|
360
390
|
return _NullSplash()
|
|
391
|
+
# _compose_rows degrades CODE PUPPY -> PUP -> bare pyramid,
|
|
392
|
+
# but below even the pyramid's footprint there is nothing
|
|
393
|
+
# honest left to draw: stay silent, never clip art.
|
|
394
|
+
size = shutil.get_terminal_size(fallback=(80, 24))
|
|
395
|
+
if size.columns < _PYRAMID_WIDTH + 2 or size.lines < len(_PYRAMID) + 2:
|
|
396
|
+
return _NullSplash()
|
|
361
397
|
if not _enable_windows_vt(stream):
|
|
362
398
|
return _NullSplash()
|
|
363
399
|
return _Splash(stream, min_seconds=min_seconds)
|
|
@@ -406,6 +406,11 @@ async def _invoke_agent_impl(
|
|
|
406
406
|
# (e.g. DBOS) may swap them via the agent_run_context hook.
|
|
407
407
|
temp_agent = Agent(
|
|
408
408
|
model=model,
|
|
409
|
+
# Explicit name: without it pydantic-ai infers one from the
|
|
410
|
+
# caller's frame variables, so every sub-agent's observability
|
|
411
|
+
# span reads "invoke_agent temp_agent" instead of the logical
|
|
412
|
+
# agent name (e.g. "invoke_agent web-retriever").
|
|
413
|
+
name=agent_name,
|
|
409
414
|
instructions=instructions,
|
|
410
415
|
output_type=str,
|
|
411
416
|
retries=3,
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "code-puppy"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.751"
|
|
8
8
|
description = "Code generation agent"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11,<3.15"
|
|
@@ -32,7 +32,7 @@ dependencies = [
|
|
|
32
32
|
"termflow-md>=0.1.11",
|
|
33
33
|
"Pillow>=10.0.0",
|
|
34
34
|
"azure-identity>=1.15.0",
|
|
35
|
-
"anthropic>=0.108.0",
|
|
35
|
+
"anthropic>=0.108.0,<1",
|
|
36
36
|
"boto3>=1.43.9",
|
|
37
37
|
"keyring>=24.0.0",
|
|
38
38
|
"agent-client-protocol>=0.11.0,<0.12",
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"""Small platform-detection helpers shared by UI entry points."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
import sys
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def is_android() -> bool:
|
|
10
|
-
"""Return whether Code Puppy is running on Android or in Termux.
|
|
11
|
-
|
|
12
|
-
Current Android Python builds expose an ``android`` platform name, while
|
|
13
|
-
older Termux builds may report ``linux``. Environment markers cover that
|
|
14
|
-
compatibility gap without making each UI entry point invent its own check.
|
|
15
|
-
"""
|
|
16
|
-
if sys.platform.startswith("android"):
|
|
17
|
-
return True
|
|
18
|
-
|
|
19
|
-
return bool(
|
|
20
|
-
os.environ.get("TERMUX_VERSION")
|
|
21
|
-
or (os.environ.get("ANDROID_ROOT") and os.environ.get("ANDROID_DATA"))
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def startup_banner_text() -> str:
|
|
26
|
-
"""Return the platform-appropriate startup banner label."""
|
|
27
|
-
return "PUP" if is_android() else "CODE PUPPY"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|