claude-mpm 4.1.1__py3-none-any.whl → 4.1.2__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.
- claude_mpm/BUILD_NUMBER +1 -1
- claude_mpm/VERSION +1 -1
- claude_mpm/__main__.py +1 -1
- claude_mpm/agents/BASE_PM.md +74 -46
- claude_mpm/agents/INSTRUCTIONS.md +11 -153
- claude_mpm/agents/WORKFLOW.md +61 -321
- claude_mpm/agents/__init__.py +11 -11
- claude_mpm/agents/agent_loader.py +23 -20
- claude_mpm/agents/agent_loader_integration.py +1 -1
- claude_mpm/agents/agents_metadata.py +27 -0
- claude_mpm/agents/async_agent_loader.py +5 -8
- claude_mpm/agents/base_agent_loader.py +36 -25
- claude_mpm/agents/frontmatter_validator.py +6 -6
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/system_agent_config.py +9 -9
- claude_mpm/agents/templates/api_qa.json +47 -2
- claude_mpm/agents/templates/imagemagick.json +256 -0
- claude_mpm/agents/templates/qa.json +41 -2
- claude_mpm/agents/templates/ticketing.json +5 -5
- claude_mpm/agents/templates/web_qa.json +50 -2
- claude_mpm/cli/__init__.py +51 -46
- claude_mpm/cli/__main__.py +1 -1
- claude_mpm/cli/commands/__init__.py +10 -12
- claude_mpm/cli/commands/agent_manager.py +186 -181
- claude_mpm/cli/commands/agents.py +271 -268
- claude_mpm/cli/commands/aggregate.py +30 -29
- claude_mpm/cli/commands/cleanup.py +50 -44
- claude_mpm/cli/commands/cleanup_orphaned_agents.py +25 -25
- claude_mpm/cli/commands/config.py +162 -127
- claude_mpm/cli/commands/doctor.py +52 -62
- claude_mpm/cli/commands/info.py +37 -25
- claude_mpm/cli/commands/mcp.py +3 -7
- claude_mpm/cli/commands/mcp_command_router.py +14 -18
- claude_mpm/cli/commands/mcp_install_commands.py +28 -23
- claude_mpm/cli/commands/mcp_pipx_config.py +58 -49
- claude_mpm/cli/commands/mcp_server_commands.py +23 -17
- claude_mpm/cli/commands/memory.py +192 -141
- claude_mpm/cli/commands/monitor.py +117 -88
- claude_mpm/cli/commands/run.py +120 -84
- claude_mpm/cli/commands/run_config_checker.py +4 -5
- claude_mpm/cli/commands/socketio_monitor.py +17 -19
- claude_mpm/cli/commands/tickets.py +92 -92
- claude_mpm/cli/parser.py +1 -5
- claude_mpm/cli/parsers/__init__.py +1 -1
- claude_mpm/cli/parsers/agent_manager_parser.py +50 -98
- claude_mpm/cli/parsers/agents_parser.py +2 -3
- claude_mpm/cli/parsers/base_parser.py +7 -5
- claude_mpm/cli/parsers/mcp_parser.py +4 -2
- claude_mpm/cli/parsers/monitor_parser.py +26 -18
- claude_mpm/cli/shared/__init__.py +10 -10
- claude_mpm/cli/shared/argument_patterns.py +57 -71
- claude_mpm/cli/shared/base_command.py +61 -53
- claude_mpm/cli/shared/error_handling.py +62 -58
- claude_mpm/cli/shared/output_formatters.py +78 -77
- claude_mpm/cli/startup_logging.py +204 -172
- claude_mpm/cli/utils.py +10 -11
- claude_mpm/cli_module/__init__.py +1 -1
- claude_mpm/cli_module/args.py +1 -1
- claude_mpm/cli_module/migration_example.py +5 -5
- claude_mpm/config/__init__.py +9 -9
- claude_mpm/config/agent_config.py +15 -14
- claude_mpm/config/experimental_features.py +4 -4
- claude_mpm/config/paths.py +0 -1
- claude_mpm/config/socketio_config.py +5 -6
- claude_mpm/constants.py +1 -2
- claude_mpm/core/__init__.py +8 -8
- claude_mpm/core/agent_name_normalizer.py +1 -1
- claude_mpm/core/agent_registry.py +20 -23
- claude_mpm/core/agent_session_manager.py +3 -3
- claude_mpm/core/base_service.py +7 -15
- claude_mpm/core/cache.py +4 -6
- claude_mpm/core/claude_runner.py +85 -113
- claude_mpm/core/config.py +43 -28
- claude_mpm/core/config_aliases.py +0 -9
- claude_mpm/core/config_constants.py +52 -30
- claude_mpm/core/constants.py +0 -1
- claude_mpm/core/container.py +18 -27
- claude_mpm/core/exceptions.py +2 -2
- claude_mpm/core/factories.py +10 -12
- claude_mpm/core/framework_loader.py +581 -280
- claude_mpm/core/hook_manager.py +26 -22
- claude_mpm/core/hook_performance_config.py +58 -47
- claude_mpm/core/injectable_service.py +1 -1
- claude_mpm/core/interactive_session.py +61 -152
- claude_mpm/core/interfaces.py +1 -100
- claude_mpm/core/lazy.py +5 -5
- claude_mpm/core/log_manager.py +587 -0
- claude_mpm/core/logger.py +125 -8
- claude_mpm/core/logging_config.py +15 -15
- claude_mpm/core/minimal_framework_loader.py +5 -8
- claude_mpm/core/oneshot_session.py +15 -33
- claude_mpm/core/optimized_agent_loader.py +4 -6
- claude_mpm/core/optimized_startup.py +2 -1
- claude_mpm/core/output_style_manager.py +147 -106
- claude_mpm/core/pm_hook_interceptor.py +0 -1
- claude_mpm/core/service_registry.py +11 -8
- claude_mpm/core/session_manager.py +1 -2
- claude_mpm/core/shared/__init__.py +1 -1
- claude_mpm/core/shared/config_loader.py +101 -97
- claude_mpm/core/shared/path_resolver.py +72 -68
- claude_mpm/core/shared/singleton_manager.py +56 -50
- claude_mpm/core/socketio_pool.py +26 -6
- claude_mpm/core/tool_access_control.py +4 -5
- claude_mpm/core/typing_utils.py +50 -59
- claude_mpm/core/unified_agent_registry.py +14 -19
- claude_mpm/core/unified_config.py +4 -6
- claude_mpm/core/unified_paths.py +197 -109
- claude_mpm/dashboard/open_dashboard.py +2 -4
- claude_mpm/experimental/cli_enhancements.py +51 -36
- claude_mpm/generators/agent_profile_generator.py +2 -4
- claude_mpm/hooks/base_hook.py +1 -2
- claude_mpm/hooks/claude_hooks/connection_pool.py +72 -26
- claude_mpm/hooks/claude_hooks/event_handlers.py +93 -38
- claude_mpm/hooks/claude_hooks/hook_handler.py +130 -76
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +104 -77
- claude_mpm/hooks/claude_hooks/memory_integration.py +2 -4
- claude_mpm/hooks/claude_hooks/response_tracking.py +15 -11
- claude_mpm/hooks/claude_hooks/tool_analysis.py +12 -18
- claude_mpm/hooks/memory_integration_hook.py +5 -5
- claude_mpm/hooks/tool_call_interceptor.py +1 -1
- claude_mpm/hooks/validation_hooks.py +4 -4
- claude_mpm/init.py +4 -9
- claude_mpm/models/__init__.py +2 -2
- claude_mpm/models/agent_session.py +11 -14
- claude_mpm/scripts/mcp_server.py +20 -11
- claude_mpm/scripts/mcp_wrapper.py +5 -5
- claude_mpm/scripts/mpm_doctor.py +321 -0
- claude_mpm/scripts/socketio_daemon.py +28 -25
- claude_mpm/scripts/socketio_daemon_hardened.py +298 -258
- claude_mpm/scripts/socketio_server_manager.py +116 -95
- claude_mpm/services/__init__.py +49 -49
- claude_mpm/services/agent_capabilities_service.py +12 -18
- claude_mpm/services/agents/__init__.py +22 -22
- claude_mpm/services/agents/agent_builder.py +140 -119
- claude_mpm/services/agents/deployment/__init__.py +3 -3
- claude_mpm/services/agents/deployment/agent_config_provider.py +9 -9
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +19 -20
- claude_mpm/services/agents/deployment/agent_definition_factory.py +1 -5
- claude_mpm/services/agents/deployment/agent_deployment.py +136 -106
- claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -8
- claude_mpm/services/agents/deployment/agent_environment_manager.py +2 -7
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +6 -10
- claude_mpm/services/agents/deployment/agent_format_converter.py +11 -15
- claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +2 -3
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +5 -5
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +13 -19
- claude_mpm/services/agents/deployment/agent_restore_handler.py +0 -1
- claude_mpm/services/agents/deployment/agent_template_builder.py +26 -35
- claude_mpm/services/agents/deployment/agent_validator.py +0 -1
- claude_mpm/services/agents/deployment/agent_version_manager.py +7 -9
- claude_mpm/services/agents/deployment/agent_versioning.py +3 -3
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +6 -7
- claude_mpm/services/agents/deployment/async_agent_deployment.py +51 -38
- claude_mpm/services/agents/deployment/config/__init__.py +1 -1
- claude_mpm/services/agents/deployment/config/deployment_config.py +7 -8
- claude_mpm/services/agents/deployment/deployment_type_detector.py +1 -1
- claude_mpm/services/agents/deployment/deployment_wrapper.py +18 -18
- claude_mpm/services/agents/deployment/facade/__init__.py +1 -1
- claude_mpm/services/agents/deployment/facade/deployment_executor.py +0 -3
- claude_mpm/services/agents/deployment/facade/deployment_facade.py +3 -4
- claude_mpm/services/agents/deployment/interface_adapter.py +5 -7
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +345 -276
- claude_mpm/services/agents/deployment/pipeline/__init__.py +2 -2
- claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +6 -4
- claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +3 -3
- claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +2 -2
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +14 -13
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +0 -1
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +8 -9
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +1 -1
- claude_mpm/services/agents/deployment/processors/__init__.py +1 -1
- claude_mpm/services/agents/deployment/processors/agent_processor.py +20 -16
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +5 -12
- claude_mpm/services/agents/deployment/results/__init__.py +1 -1
- claude_mpm/services/agents/deployment/results/deployment_result_builder.py +1 -1
- claude_mpm/services/agents/deployment/strategies/__init__.py +2 -2
- claude_mpm/services/agents/deployment/strategies/base_strategy.py +1 -7
- claude_mpm/services/agents/deployment/strategies/project_strategy.py +1 -4
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +2 -3
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +3 -7
- claude_mpm/services/agents/deployment/validation/__init__.py +1 -1
- claude_mpm/services/agents/deployment/validation/agent_validator.py +1 -1
- claude_mpm/services/agents/deployment/validation/template_validator.py +2 -2
- claude_mpm/services/agents/deployment/validation/validation_result.py +2 -6
- claude_mpm/services/agents/loading/__init__.py +1 -1
- claude_mpm/services/agents/loading/agent_profile_loader.py +6 -12
- claude_mpm/services/agents/loading/base_agent_manager.py +5 -5
- claude_mpm/services/agents/loading/framework_agent_loader.py +2 -4
- claude_mpm/services/agents/management/__init__.py +1 -1
- claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -3
- claude_mpm/services/agents/management/agent_management_service.py +5 -9
- claude_mpm/services/agents/memory/__init__.py +4 -4
- claude_mpm/services/agents/memory/agent_memory_manager.py +280 -160
- claude_mpm/services/agents/memory/agent_persistence_service.py +0 -2
- claude_mpm/services/agents/memory/content_manager.py +44 -38
- claude_mpm/services/agents/memory/template_generator.py +4 -6
- claude_mpm/services/agents/registry/__init__.py +10 -6
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +30 -27
- claude_mpm/services/agents/registry/modification_tracker.py +3 -6
- claude_mpm/services/async_session_logger.py +1 -2
- claude_mpm/services/claude_session_logger.py +1 -2
- claude_mpm/services/command_deployment_service.py +173 -0
- claude_mpm/services/command_handler_service.py +20 -22
- claude_mpm/services/core/__init__.py +25 -25
- claude_mpm/services/core/base.py +0 -5
- claude_mpm/services/core/interfaces/__init__.py +32 -32
- claude_mpm/services/core/interfaces/agent.py +0 -21
- claude_mpm/services/core/interfaces/communication.py +0 -27
- claude_mpm/services/core/interfaces/infrastructure.py +0 -56
- claude_mpm/services/core/interfaces/service.py +0 -29
- claude_mpm/services/diagnostics/__init__.py +1 -1
- claude_mpm/services/diagnostics/checks/__init__.py +6 -6
- claude_mpm/services/diagnostics/checks/agent_check.py +89 -80
- claude_mpm/services/diagnostics/checks/base_check.py +12 -16
- claude_mpm/services/diagnostics/checks/claude_desktop_check.py +84 -81
- claude_mpm/services/diagnostics/checks/common_issues_check.py +99 -91
- claude_mpm/services/diagnostics/checks/configuration_check.py +82 -77
- claude_mpm/services/diagnostics/checks/filesystem_check.py +67 -68
- claude_mpm/services/diagnostics/checks/installation_check.py +254 -94
- claude_mpm/services/diagnostics/checks/mcp_check.py +90 -88
- claude_mpm/services/diagnostics/checks/monitor_check.py +75 -76
- claude_mpm/services/diagnostics/checks/startup_log_check.py +67 -73
- claude_mpm/services/diagnostics/diagnostic_runner.py +67 -59
- claude_mpm/services/diagnostics/doctor_reporter.py +107 -70
- claude_mpm/services/diagnostics/models.py +21 -19
- claude_mpm/services/event_aggregator.py +10 -17
- claude_mpm/services/event_bus/__init__.py +1 -1
- claude_mpm/services/event_bus/config.py +54 -35
- claude_mpm/services/event_bus/event_bus.py +76 -71
- claude_mpm/services/event_bus/relay.py +74 -64
- claude_mpm/services/events/__init__.py +11 -11
- claude_mpm/services/events/consumers/__init__.py +3 -3
- claude_mpm/services/events/consumers/dead_letter.py +71 -63
- claude_mpm/services/events/consumers/logging.py +39 -37
- claude_mpm/services/events/consumers/metrics.py +56 -57
- claude_mpm/services/events/consumers/socketio.py +82 -81
- claude_mpm/services/events/core.py +110 -99
- claude_mpm/services/events/interfaces.py +56 -72
- claude_mpm/services/events/producers/__init__.py +1 -1
- claude_mpm/services/events/producers/hook.py +38 -38
- claude_mpm/services/events/producers/system.py +46 -44
- claude_mpm/services/exceptions.py +81 -80
- claude_mpm/services/framework_claude_md_generator/__init__.py +2 -4
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +3 -5
- claude_mpm/services/framework_claude_md_generator/content_validator.py +1 -1
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +4 -4
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +0 -1
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +0 -2
- claude_mpm/services/framework_claude_md_generator/version_manager.py +4 -5
- claude_mpm/services/hook_service.py +6 -9
- claude_mpm/services/infrastructure/__init__.py +1 -1
- claude_mpm/services/infrastructure/context_preservation.py +8 -12
- claude_mpm/services/infrastructure/monitoring.py +21 -23
- claude_mpm/services/mcp_gateway/__init__.py +37 -37
- claude_mpm/services/mcp_gateway/auto_configure.py +95 -103
- claude_mpm/services/mcp_gateway/config/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/config/config_loader.py +23 -25
- claude_mpm/services/mcp_gateway/config/config_schema.py +5 -5
- claude_mpm/services/mcp_gateway/config/configuration.py +9 -6
- claude_mpm/services/mcp_gateway/core/__init__.py +10 -10
- claude_mpm/services/mcp_gateway/core/base.py +0 -3
- claude_mpm/services/mcp_gateway/core/interfaces.py +1 -38
- claude_mpm/services/mcp_gateway/core/process_pool.py +99 -93
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +65 -62
- claude_mpm/services/mcp_gateway/core/startup_verification.py +75 -74
- claude_mpm/services/mcp_gateway/main.py +2 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +5 -8
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +1 -1
- claude_mpm/services/mcp_gateway/server/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +12 -19
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +4 -3
- claude_mpm/services/mcp_gateway/server/stdio_server.py +79 -71
- claude_mpm/services/mcp_gateway/tools/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +5 -6
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +13 -22
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +79 -78
- claude_mpm/services/mcp_gateway/tools/hello_world.py +12 -14
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +42 -49
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +51 -55
- claude_mpm/services/memory/__init__.py +3 -3
- claude_mpm/services/memory/builder.py +3 -6
- claude_mpm/services/memory/cache/__init__.py +1 -1
- claude_mpm/services/memory/cache/shared_prompt_cache.py +3 -5
- claude_mpm/services/memory/cache/simple_cache.py +1 -1
- claude_mpm/services/memory/indexed_memory.py +5 -7
- claude_mpm/services/memory/optimizer.py +7 -10
- claude_mpm/services/memory/router.py +8 -9
- claude_mpm/services/memory_hook_service.py +48 -34
- claude_mpm/services/monitor_build_service.py +77 -73
- claude_mpm/services/port_manager.py +130 -108
- claude_mpm/services/project/analyzer.py +12 -10
- claude_mpm/services/project/registry.py +11 -11
- claude_mpm/services/recovery_manager.py +10 -19
- claude_mpm/services/response_tracker.py +0 -1
- claude_mpm/services/runner_configuration_service.py +19 -20
- claude_mpm/services/session_management_service.py +7 -11
- claude_mpm/services/shared/__init__.py +1 -1
- claude_mpm/services/shared/async_service_base.py +58 -50
- claude_mpm/services/shared/config_service_base.py +73 -67
- claude_mpm/services/shared/lifecycle_service_base.py +82 -78
- claude_mpm/services/shared/manager_base.py +94 -82
- claude_mpm/services/shared/service_factory.py +96 -98
- claude_mpm/services/socketio/__init__.py +3 -3
- claude_mpm/services/socketio/client_proxy.py +5 -5
- claude_mpm/services/socketio/event_normalizer.py +199 -181
- claude_mpm/services/socketio/handlers/__init__.py +3 -3
- claude_mpm/services/socketio/handlers/base.py +5 -4
- claude_mpm/services/socketio/handlers/connection.py +163 -136
- claude_mpm/services/socketio/handlers/file.py +13 -14
- claude_mpm/services/socketio/handlers/git.py +12 -7
- claude_mpm/services/socketio/handlers/hook.py +49 -44
- claude_mpm/services/socketio/handlers/memory.py +0 -1
- claude_mpm/services/socketio/handlers/project.py +0 -1
- claude_mpm/services/socketio/handlers/registry.py +37 -19
- claude_mpm/services/socketio/migration_utils.py +98 -84
- claude_mpm/services/socketio/server/__init__.py +1 -1
- claude_mpm/services/socketio/server/broadcaster.py +81 -87
- claude_mpm/services/socketio/server/core.py +65 -54
- claude_mpm/services/socketio/server/eventbus_integration.py +95 -56
- claude_mpm/services/socketio/server/main.py +64 -38
- claude_mpm/services/socketio_client_manager.py +10 -12
- claude_mpm/services/subprocess_launcher_service.py +4 -7
- claude_mpm/services/system_instructions_service.py +13 -14
- claude_mpm/services/ticket_manager.py +2 -2
- claude_mpm/services/utility_service.py +5 -13
- claude_mpm/services/version_control/__init__.py +16 -16
- claude_mpm/services/version_control/branch_strategy.py +5 -8
- claude_mpm/services/version_control/conflict_resolution.py +9 -23
- claude_mpm/services/version_control/git_operations.py +5 -7
- claude_mpm/services/version_control/semantic_versioning.py +16 -17
- claude_mpm/services/version_control/version_parser.py +13 -18
- claude_mpm/services/version_service.py +10 -11
- claude_mpm/storage/__init__.py +1 -1
- claude_mpm/storage/state_storage.py +22 -28
- claude_mpm/utils/__init__.py +6 -6
- claude_mpm/utils/agent_dependency_loader.py +47 -33
- claude_mpm/utils/config_manager.py +11 -14
- claude_mpm/utils/dependency_cache.py +1 -1
- claude_mpm/utils/dependency_manager.py +13 -17
- claude_mpm/utils/dependency_strategies.py +8 -10
- claude_mpm/utils/environment_context.py +3 -9
- claude_mpm/utils/error_handler.py +3 -13
- claude_mpm/utils/file_utils.py +1 -1
- claude_mpm/utils/path_operations.py +8 -12
- claude_mpm/utils/robust_installer.py +110 -33
- claude_mpm/utils/subprocess_utils.py +5 -6
- claude_mpm/validation/agent_validator.py +3 -6
- claude_mpm/validation/frontmatter_validator.py +1 -1
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/METADATA +1 -1
- claude_mpm-4.1.2.dist-info/RECORD +498 -0
- claude_mpm-4.1.1.dist-info/RECORD +0 -494
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/top_level.txt +0 -0
|
@@ -4,32 +4,32 @@ Common error handling patterns for CLI commands.
|
|
|
4
4
|
|
|
5
5
|
import traceback
|
|
6
6
|
from functools import wraps
|
|
7
|
-
from typing import Any, Callable
|
|
7
|
+
from typing import Any, Callable, Optional
|
|
8
8
|
|
|
9
9
|
from ...core.logger import get_logger
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class CLIErrorHandler:
|
|
13
13
|
"""Centralized error handling for CLI commands."""
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
def __init__(self, command_name: str):
|
|
16
16
|
"""
|
|
17
17
|
Initialize error handler.
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
Args:
|
|
20
20
|
command_name: Name of the command for logging context
|
|
21
21
|
"""
|
|
22
22
|
self.command_name = command_name
|
|
23
23
|
self.logger = get_logger(f"cli.{command_name}")
|
|
24
|
-
|
|
25
|
-
def handle_error(self, error: Exception, context: str = None) -> int:
|
|
24
|
+
|
|
25
|
+
def handle_error(self, error: Exception, context: Optional[str] = None) -> int:
|
|
26
26
|
"""
|
|
27
27
|
Handle an error with appropriate logging and user feedback.
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
Args:
|
|
30
30
|
error: The exception that occurred
|
|
31
31
|
context: Additional context about when the error occurred
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
Returns:
|
|
34
34
|
Appropriate exit code
|
|
35
35
|
"""
|
|
@@ -37,60 +37,59 @@ class CLIErrorHandler:
|
|
|
37
37
|
error_msg = str(error)
|
|
38
38
|
if context:
|
|
39
39
|
error_msg = f"{context}: {error_msg}"
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
# Determine error type and appropriate response
|
|
42
42
|
if isinstance(error, KeyboardInterrupt):
|
|
43
43
|
self.logger.info("Operation cancelled by user")
|
|
44
44
|
print("\nOperation cancelled by user.")
|
|
45
45
|
return 130 # Standard exit code for SIGINT
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
|
|
47
|
+
if isinstance(error, FileNotFoundError):
|
|
48
48
|
self.logger.error(f"File not found: {error}")
|
|
49
49
|
print(f"Error: File not found - {error}")
|
|
50
50
|
return 2
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
|
|
52
|
+
if isinstance(error, PermissionError):
|
|
53
53
|
self.logger.error(f"Permission denied: {error}")
|
|
54
54
|
print(f"Error: Permission denied - {error}")
|
|
55
55
|
return 13
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
|
|
57
|
+
if isinstance(error, ValueError):
|
|
58
58
|
self.logger.error(f"Invalid value: {error}")
|
|
59
59
|
print(f"Error: Invalid value - {error}")
|
|
60
60
|
return 22
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
61
|
+
|
|
62
|
+
# Generic error handling
|
|
63
|
+
self.logger.error(f"Command failed: {error}", exc_info=True)
|
|
64
|
+
print(f"Error: {error_msg}")
|
|
65
|
+
|
|
66
|
+
# Show traceback in debug mode
|
|
67
|
+
if self.logger.isEnabledFor(10): # DEBUG level
|
|
68
|
+
traceback.print_exc()
|
|
69
|
+
|
|
70
|
+
return 1
|
|
71
|
+
|
|
73
72
|
def handle_validation_error(self, message: str) -> int:
|
|
74
73
|
"""
|
|
75
74
|
Handle validation errors.
|
|
76
|
-
|
|
75
|
+
|
|
77
76
|
Args:
|
|
78
77
|
message: Validation error message
|
|
79
|
-
|
|
78
|
+
|
|
80
79
|
Returns:
|
|
81
80
|
Exit code for validation errors
|
|
82
81
|
"""
|
|
83
82
|
self.logger.error(f"Validation error: {message}")
|
|
84
83
|
print(f"Error: {message}")
|
|
85
84
|
return 22 # Invalid argument exit code
|
|
86
|
-
|
|
85
|
+
|
|
87
86
|
def handle_config_error(self, error: Exception) -> int:
|
|
88
87
|
"""
|
|
89
88
|
Handle configuration-related errors.
|
|
90
|
-
|
|
89
|
+
|
|
91
90
|
Args:
|
|
92
91
|
error: Configuration error
|
|
93
|
-
|
|
92
|
+
|
|
94
93
|
Returns:
|
|
95
94
|
Exit code for configuration errors
|
|
96
95
|
"""
|
|
@@ -103,46 +102,49 @@ class CLIErrorHandler:
|
|
|
103
102
|
def handle_cli_errors(command_name: str):
|
|
104
103
|
"""
|
|
105
104
|
Decorator to add standard error handling to CLI command functions.
|
|
106
|
-
|
|
105
|
+
|
|
107
106
|
Args:
|
|
108
107
|
command_name: Name of the command for error context
|
|
109
|
-
|
|
108
|
+
|
|
110
109
|
Returns:
|
|
111
110
|
Decorated function with error handling
|
|
112
111
|
"""
|
|
112
|
+
|
|
113
113
|
def decorator(func: Callable) -> Callable:
|
|
114
114
|
@wraps(func)
|
|
115
115
|
def wrapper(*args, **kwargs) -> int:
|
|
116
116
|
error_handler = CLIErrorHandler(command_name)
|
|
117
|
-
|
|
117
|
+
|
|
118
118
|
try:
|
|
119
119
|
result = func(*args, **kwargs)
|
|
120
|
-
|
|
120
|
+
|
|
121
121
|
# Handle different return types
|
|
122
122
|
if isinstance(result, int):
|
|
123
123
|
return result
|
|
124
|
-
|
|
124
|
+
if hasattr(result, "exit_code"):
|
|
125
125
|
return result.exit_code
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
return 0 # Success
|
|
127
|
+
|
|
129
128
|
except Exception as e:
|
|
130
129
|
return error_handler.handle_error(e)
|
|
131
|
-
|
|
130
|
+
|
|
132
131
|
return wrapper
|
|
132
|
+
|
|
133
133
|
return decorator
|
|
134
134
|
|
|
135
135
|
|
|
136
|
-
def safe_execute(
|
|
136
|
+
def safe_execute(
|
|
137
|
+
func: Callable, *args, error_handler: CLIErrorHandler = None, **kwargs
|
|
138
|
+
) -> Any:
|
|
137
139
|
"""
|
|
138
140
|
Safely execute a function with error handling.
|
|
139
|
-
|
|
141
|
+
|
|
140
142
|
Args:
|
|
141
143
|
func: Function to execute
|
|
142
144
|
*args: Function arguments
|
|
143
145
|
error_handler: Optional error handler
|
|
144
146
|
**kwargs: Function keyword arguments
|
|
145
|
-
|
|
147
|
+
|
|
146
148
|
Returns:
|
|
147
149
|
Function result or None if error occurred
|
|
148
150
|
"""
|
|
@@ -161,78 +163,80 @@ def safe_execute(func: Callable, *args, error_handler: CLIErrorHandler = None, *
|
|
|
161
163
|
def validate_file_exists(file_path: str, error_handler: CLIErrorHandler = None) -> bool:
|
|
162
164
|
"""
|
|
163
165
|
Validate that a file exists.
|
|
164
|
-
|
|
166
|
+
|
|
165
167
|
Args:
|
|
166
168
|
file_path: Path to validate
|
|
167
169
|
error_handler: Optional error handler
|
|
168
|
-
|
|
170
|
+
|
|
169
171
|
Returns:
|
|
170
172
|
True if file exists, False otherwise
|
|
171
173
|
"""
|
|
172
174
|
from pathlib import Path
|
|
173
|
-
|
|
175
|
+
|
|
174
176
|
path = Path(file_path)
|
|
175
177
|
if not path.exists():
|
|
176
178
|
message = f"File does not exist: {file_path}"
|
|
177
179
|
if error_handler:
|
|
178
180
|
error_handler.handle_validation_error(message)
|
|
179
181
|
return False
|
|
180
|
-
|
|
182
|
+
|
|
181
183
|
if not path.is_file():
|
|
182
184
|
message = f"Path is not a file: {file_path}"
|
|
183
185
|
if error_handler:
|
|
184
186
|
error_handler.handle_validation_error(message)
|
|
185
187
|
return False
|
|
186
|
-
|
|
188
|
+
|
|
187
189
|
return True
|
|
188
190
|
|
|
189
191
|
|
|
190
|
-
def validate_directory_exists(
|
|
192
|
+
def validate_directory_exists(
|
|
193
|
+
dir_path: str, error_handler: CLIErrorHandler = None
|
|
194
|
+
) -> bool:
|
|
191
195
|
"""
|
|
192
196
|
Validate that a directory exists.
|
|
193
|
-
|
|
197
|
+
|
|
194
198
|
Args:
|
|
195
199
|
dir_path: Directory path to validate
|
|
196
200
|
error_handler: Optional error handler
|
|
197
|
-
|
|
201
|
+
|
|
198
202
|
Returns:
|
|
199
203
|
True if directory exists, False otherwise
|
|
200
204
|
"""
|
|
201
205
|
from pathlib import Path
|
|
202
|
-
|
|
206
|
+
|
|
203
207
|
path = Path(dir_path)
|
|
204
208
|
if not path.exists():
|
|
205
209
|
message = f"Directory does not exist: {dir_path}"
|
|
206
210
|
if error_handler:
|
|
207
211
|
error_handler.handle_validation_error(message)
|
|
208
212
|
return False
|
|
209
|
-
|
|
213
|
+
|
|
210
214
|
if not path.is_dir():
|
|
211
215
|
message = f"Path is not a directory: {dir_path}"
|
|
212
216
|
if error_handler:
|
|
213
217
|
error_handler.handle_validation_error(message)
|
|
214
218
|
return False
|
|
215
|
-
|
|
219
|
+
|
|
216
220
|
return True
|
|
217
221
|
|
|
218
222
|
|
|
219
223
|
def confirm_operation(message: str, force: bool = False) -> bool:
|
|
220
224
|
"""
|
|
221
225
|
Ask user for confirmation unless force flag is set.
|
|
222
|
-
|
|
226
|
+
|
|
223
227
|
Args:
|
|
224
228
|
message: Confirmation message
|
|
225
229
|
force: If True, skip confirmation
|
|
226
|
-
|
|
230
|
+
|
|
227
231
|
Returns:
|
|
228
232
|
True if operation should proceed
|
|
229
233
|
"""
|
|
230
234
|
if force:
|
|
231
235
|
return True
|
|
232
|
-
|
|
236
|
+
|
|
233
237
|
try:
|
|
234
238
|
response = input(f"{message} (y/N): ").strip().lower()
|
|
235
|
-
return response in (
|
|
239
|
+
return response in ("y", "yes")
|
|
236
240
|
except (EOFError, KeyboardInterrupt):
|
|
237
241
|
print("\nOperation cancelled.")
|
|
238
242
|
return False
|
|
@@ -3,70 +3,71 @@ Output formatting utilities for CLI commands.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
-
from typing import Any, Dict, List, Union
|
|
6
|
+
from typing import Any, Dict, List, Optional, Union
|
|
7
7
|
|
|
8
8
|
import yaml
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class OutputFormatter:
|
|
12
12
|
"""Handles formatting output in different formats."""
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
@staticmethod
|
|
15
15
|
def format_json(data: Any, indent: int = 2) -> str:
|
|
16
16
|
"""Format data as JSON."""
|
|
17
17
|
return json.dumps(data, indent=indent, default=str)
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
@staticmethod
|
|
20
20
|
def format_yaml(data: Any) -> str:
|
|
21
21
|
"""Format data as YAML."""
|
|
22
22
|
return yaml.dump(data, default_flow_style=False, sort_keys=False)
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
@staticmethod
|
|
25
|
-
def format_table(
|
|
25
|
+
def format_table(
|
|
26
|
+
data: Union[List[Dict], Dict], headers: Optional[List[str]] = None
|
|
27
|
+
) -> str:
|
|
26
28
|
"""Format data as a simple table."""
|
|
27
29
|
if not data:
|
|
28
30
|
return "No data to display"
|
|
29
|
-
|
|
31
|
+
|
|
30
32
|
# Handle single dict
|
|
31
33
|
if isinstance(data, dict):
|
|
32
34
|
data = [data]
|
|
33
|
-
|
|
35
|
+
|
|
34
36
|
# Auto-detect headers if not provided
|
|
35
37
|
if headers is None and data:
|
|
36
38
|
headers = list(data[0].keys())
|
|
37
|
-
|
|
39
|
+
|
|
38
40
|
if not headers:
|
|
39
41
|
return "No data to display"
|
|
40
|
-
|
|
42
|
+
|
|
41
43
|
# Calculate column widths
|
|
42
44
|
col_widths = {}
|
|
43
45
|
for header in headers:
|
|
44
46
|
col_widths[header] = len(header)
|
|
45
47
|
for row in data:
|
|
46
|
-
value = str(row.get(header,
|
|
48
|
+
value = str(row.get(header, ""))
|
|
47
49
|
col_widths[header] = max(col_widths[header], len(value))
|
|
48
|
-
|
|
50
|
+
|
|
49
51
|
# Build table
|
|
50
52
|
lines = []
|
|
51
|
-
|
|
53
|
+
|
|
52
54
|
# Header row
|
|
53
55
|
header_row = " | ".join(header.ljust(col_widths[header]) for header in headers)
|
|
54
56
|
lines.append(header_row)
|
|
55
|
-
|
|
57
|
+
|
|
56
58
|
# Separator row
|
|
57
59
|
separator = " | ".join("-" * col_widths[header] for header in headers)
|
|
58
60
|
lines.append(separator)
|
|
59
|
-
|
|
61
|
+
|
|
60
62
|
# Data rows
|
|
61
63
|
for row in data:
|
|
62
64
|
data_row = " | ".join(
|
|
63
|
-
str(row.get(header,
|
|
64
|
-
for header in headers
|
|
65
|
+
str(row.get(header, "")).ljust(col_widths[header]) for header in headers
|
|
65
66
|
)
|
|
66
67
|
lines.append(data_row)
|
|
67
|
-
|
|
68
|
+
|
|
68
69
|
return "\n".join(lines)
|
|
69
|
-
|
|
70
|
+
|
|
70
71
|
@staticmethod
|
|
71
72
|
def format_text(data: Any) -> str:
|
|
72
73
|
"""Format data as human-readable text."""
|
|
@@ -77,69 +78,69 @@ class OutputFormatter:
|
|
|
77
78
|
lines.append(f"{key}:")
|
|
78
79
|
# Indent nested content
|
|
79
80
|
nested = OutputFormatter.format_text(value)
|
|
80
|
-
for line in nested.split(
|
|
81
|
+
for line in nested.split("\n"):
|
|
81
82
|
if line.strip():
|
|
82
83
|
lines.append(f" {line}")
|
|
83
84
|
else:
|
|
84
85
|
lines.append(f"{key}: {value}")
|
|
85
86
|
return "\n".join(lines)
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
|
|
88
|
+
if isinstance(data, list):
|
|
88
89
|
if not data:
|
|
89
90
|
return "No items"
|
|
90
|
-
|
|
91
|
+
|
|
91
92
|
lines = []
|
|
92
93
|
for i, item in enumerate(data):
|
|
93
94
|
if isinstance(item, dict):
|
|
94
95
|
lines.append(f"Item {i + 1}:")
|
|
95
96
|
nested = OutputFormatter.format_text(item)
|
|
96
|
-
for line in nested.split(
|
|
97
|
+
for line in nested.split("\n"):
|
|
97
98
|
if line.strip():
|
|
98
99
|
lines.append(f" {line}")
|
|
99
100
|
else:
|
|
100
101
|
lines.append(f"- {item}")
|
|
101
102
|
return "\n".join(lines)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return str(data)
|
|
103
|
+
|
|
104
|
+
return str(data)
|
|
105
105
|
|
|
106
106
|
|
|
107
107
|
def format_output(data: Any, format_type: str = "text", **kwargs) -> str:
|
|
108
108
|
"""
|
|
109
109
|
Format data according to the specified format.
|
|
110
|
-
|
|
110
|
+
|
|
111
111
|
Args:
|
|
112
112
|
data: Data to format
|
|
113
113
|
format_type: Output format ('json', 'yaml', 'table', 'text')
|
|
114
114
|
**kwargs: Additional formatting options
|
|
115
|
-
|
|
115
|
+
|
|
116
116
|
Returns:
|
|
117
117
|
Formatted string
|
|
118
118
|
"""
|
|
119
119
|
formatter = OutputFormatter()
|
|
120
|
-
|
|
120
|
+
|
|
121
121
|
if format_type == "json":
|
|
122
122
|
return formatter.format_json(data, **kwargs)
|
|
123
|
-
|
|
123
|
+
if format_type == "yaml":
|
|
124
124
|
return formatter.format_yaml(data)
|
|
125
|
-
|
|
125
|
+
if format_type == "table":
|
|
126
126
|
return formatter.format_table(data, **kwargs)
|
|
127
|
-
|
|
128
|
-
return formatter.format_text(data)
|
|
129
|
-
else:
|
|
130
|
-
# Fallback to text format
|
|
127
|
+
if format_type == "text":
|
|
131
128
|
return formatter.format_text(data)
|
|
129
|
+
# Fallback to text format
|
|
130
|
+
return formatter.format_text(data)
|
|
132
131
|
|
|
133
132
|
|
|
134
|
-
def format_success_message(
|
|
133
|
+
def format_success_message(
|
|
134
|
+
message: str, data: Any = None, format_type: str = "text"
|
|
135
|
+
) -> str:
|
|
135
136
|
"""
|
|
136
137
|
Format a success message with optional data.
|
|
137
|
-
|
|
138
|
+
|
|
138
139
|
Args:
|
|
139
140
|
message: Success message
|
|
140
141
|
data: Optional data to include
|
|
141
142
|
format_type: Output format
|
|
142
|
-
|
|
143
|
+
|
|
143
144
|
Returns:
|
|
144
145
|
Formatted success message
|
|
145
146
|
"""
|
|
@@ -148,24 +149,25 @@ def format_success_message(message: str, data: Any = None, format_type: str = "t
|
|
|
148
149
|
if data is not None:
|
|
149
150
|
result["data"] = data
|
|
150
151
|
return format_output(result, format_type)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
return "\n".join(lines)
|
|
152
|
+
# Text format
|
|
153
|
+
lines = [f"✓ {message}"]
|
|
154
|
+
if data is not None:
|
|
155
|
+
lines.append("")
|
|
156
|
+
lines.append(format_output(data, format_type))
|
|
157
|
+
return "\n".join(lines)
|
|
158
158
|
|
|
159
159
|
|
|
160
|
-
def format_error_message(
|
|
160
|
+
def format_error_message(
|
|
161
|
+
message: str, details: Any = None, format_type: str = "text"
|
|
162
|
+
) -> str:
|
|
161
163
|
"""
|
|
162
164
|
Format an error message with optional details.
|
|
163
|
-
|
|
165
|
+
|
|
164
166
|
Args:
|
|
165
167
|
message: Error message
|
|
166
168
|
details: Optional error details
|
|
167
169
|
format_type: Output format
|
|
168
|
-
|
|
170
|
+
|
|
169
171
|
Returns:
|
|
170
172
|
Formatted error message
|
|
171
173
|
"""
|
|
@@ -174,28 +176,29 @@ def format_error_message(message: str, details: Any = None, format_type: str = "
|
|
|
174
176
|
if details is not None:
|
|
175
177
|
result["details"] = details
|
|
176
178
|
return format_output(result, format_type)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return "\n".join(lines)
|
|
179
|
+
# Text format
|
|
180
|
+
lines = [f"✗ {message}"]
|
|
181
|
+
if details is not None:
|
|
182
|
+
lines.append("")
|
|
183
|
+
lines.append(format_output(details, format_type))
|
|
184
|
+
return "\n".join(lines)
|
|
184
185
|
|
|
185
186
|
|
|
186
|
-
def format_list_output(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
def format_list_output(
|
|
188
|
+
items: List[Any],
|
|
189
|
+
title: Optional[str] = None,
|
|
190
|
+
format_type: str = "text",
|
|
191
|
+
headers: Optional[List[str]] = None,
|
|
192
|
+
) -> str:
|
|
190
193
|
"""
|
|
191
194
|
Format a list of items for output.
|
|
192
|
-
|
|
195
|
+
|
|
193
196
|
Args:
|
|
194
197
|
items: List of items to format
|
|
195
198
|
title: Optional title for the list
|
|
196
199
|
format_type: Output format
|
|
197
200
|
headers: Optional headers for table format
|
|
198
|
-
|
|
201
|
+
|
|
199
202
|
Returns:
|
|
200
203
|
Formatted list output
|
|
201
204
|
"""
|
|
@@ -203,29 +206,27 @@ def format_list_output(items: List[Any],
|
|
|
203
206
|
empty_msg = f"No {title.lower() if title else 'items'} found"
|
|
204
207
|
if format_type in ("json", "yaml"):
|
|
205
208
|
return format_output({"items": [], "message": empty_msg}, format_type)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
+
return empty_msg
|
|
210
|
+
|
|
209
211
|
if format_type in ("json", "yaml"):
|
|
210
212
|
result = {"items": items}
|
|
211
213
|
if title:
|
|
212
214
|
result["title"] = title
|
|
213
215
|
return format_output(result, format_type)
|
|
214
|
-
|
|
215
|
-
|
|
216
|
+
|
|
217
|
+
if format_type == "table":
|
|
216
218
|
output = ""
|
|
217
219
|
if title:
|
|
218
220
|
output += f"{title}\n{'=' * len(title)}\n\n"
|
|
219
221
|
output += format_output(items, "table", headers=headers)
|
|
220
222
|
return output
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return "\n".join(lines)
|
|
223
|
+
|
|
224
|
+
# Text format
|
|
225
|
+
lines = []
|
|
226
|
+
if title:
|
|
227
|
+
lines.append(title)
|
|
228
|
+
lines.append("=" * len(title))
|
|
229
|
+
lines.append("")
|
|
230
|
+
|
|
231
|
+
lines.append(format_output(items, "text"))
|
|
232
|
+
return "\n".join(lines)
|