claude-mpm 4.7.4__py3-none-any.whl → 4.18.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/VERSION +1 -1
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +118 -0
- claude_mpm/agents/BASE_ENGINEER.md +286 -0
- claude_mpm/agents/BASE_PM.md +106 -1
- claude_mpm/agents/OUTPUT_STYLE.md +329 -11
- claude_mpm/agents/PM_INSTRUCTIONS.md +397 -459
- claude_mpm/agents/agent_loader.py +17 -5
- claude_mpm/agents/frontmatter_validator.py +284 -253
- claude_mpm/agents/templates/README.md +465 -0
- claude_mpm/agents/templates/agent-manager.json +4 -1
- claude_mpm/agents/templates/agentic-coder-optimizer.json +13 -3
- claude_mpm/agents/templates/api_qa.json +11 -2
- claude_mpm/agents/templates/circuit_breakers.md +638 -0
- claude_mpm/agents/templates/clerk-ops.json +12 -2
- claude_mpm/agents/templates/code_analyzer.json +8 -2
- claude_mpm/agents/templates/content-agent.json +358 -0
- claude_mpm/agents/templates/dart_engineer.json +15 -2
- claude_mpm/agents/templates/data_engineer.json +15 -2
- claude_mpm/agents/templates/documentation.json +10 -2
- claude_mpm/agents/templates/engineer.json +21 -1
- claude_mpm/agents/templates/gcp_ops_agent.json +12 -2
- claude_mpm/agents/templates/git_file_tracking.md +584 -0
- claude_mpm/agents/templates/golang_engineer.json +270 -0
- claude_mpm/agents/templates/imagemagick.json +4 -1
- claude_mpm/agents/templates/java_engineer.json +346 -0
- claude_mpm/agents/templates/local_ops_agent.json +1227 -6
- claude_mpm/agents/templates/memory_manager.json +4 -1
- claude_mpm/agents/templates/nextjs_engineer.json +141 -133
- claude_mpm/agents/templates/ops.json +12 -2
- claude_mpm/agents/templates/php-engineer.json +270 -174
- claude_mpm/agents/templates/pm_examples.md +474 -0
- claude_mpm/agents/templates/pm_red_flags.md +240 -0
- claude_mpm/agents/templates/product_owner.json +338 -0
- claude_mpm/agents/templates/project_organizer.json +14 -4
- claude_mpm/agents/templates/prompt-engineer.json +13 -2
- claude_mpm/agents/templates/python_engineer.json +174 -81
- claude_mpm/agents/templates/qa.json +11 -2
- claude_mpm/agents/templates/react_engineer.json +16 -3
- claude_mpm/agents/templates/refactoring_engineer.json +12 -2
- claude_mpm/agents/templates/research.json +34 -21
- claude_mpm/agents/templates/response_format.md +583 -0
- claude_mpm/agents/templates/ruby-engineer.json +129 -192
- claude_mpm/agents/templates/rust_engineer.json +270 -0
- claude_mpm/agents/templates/security.json +10 -2
- claude_mpm/agents/templates/svelte-engineer.json +225 -0
- claude_mpm/agents/templates/ticketing.json +10 -2
- claude_mpm/agents/templates/typescript_engineer.json +116 -125
- claude_mpm/agents/templates/validation_templates.md +312 -0
- claude_mpm/agents/templates/vercel_ops_agent.json +12 -2
- claude_mpm/agents/templates/version_control.json +12 -2
- claude_mpm/agents/templates/web_qa.json +11 -2
- claude_mpm/agents/templates/web_ui.json +15 -2
- claude_mpm/cli/__init__.py +34 -614
- claude_mpm/cli/commands/agent_manager.py +25 -12
- claude_mpm/cli/commands/agent_state_manager.py +186 -0
- claude_mpm/cli/commands/agents.py +235 -148
- claude_mpm/cli/commands/agents_detect.py +380 -0
- claude_mpm/cli/commands/agents_recommend.py +309 -0
- claude_mpm/cli/commands/aggregate.py +7 -3
- claude_mpm/cli/commands/analyze.py +9 -4
- claude_mpm/cli/commands/analyze_code.py +7 -2
- claude_mpm/cli/commands/auto_configure.py +570 -0
- claude_mpm/cli/commands/config.py +47 -13
- claude_mpm/cli/commands/configure.py +419 -1571
- claude_mpm/cli/commands/configure_agent_display.py +261 -0
- claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
- claude_mpm/cli/commands/configure_hook_manager.py +225 -0
- claude_mpm/cli/commands/configure_models.py +18 -0
- claude_mpm/cli/commands/configure_navigation.py +167 -0
- claude_mpm/cli/commands/configure_paths.py +104 -0
- claude_mpm/cli/commands/configure_persistence.py +254 -0
- claude_mpm/cli/commands/configure_startup_manager.py +646 -0
- claude_mpm/cli/commands/configure_template_editor.py +497 -0
- claude_mpm/cli/commands/configure_validators.py +73 -0
- claude_mpm/cli/commands/local_deploy.py +537 -0
- claude_mpm/cli/commands/memory.py +54 -20
- claude_mpm/cli/commands/mpm_init.py +585 -196
- claude_mpm/cli/commands/mpm_init_handler.py +37 -3
- claude_mpm/cli/commands/search.py +170 -4
- claude_mpm/cli/commands/upgrade.py +152 -0
- claude_mpm/cli/executor.py +202 -0
- claude_mpm/cli/helpers.py +105 -0
- claude_mpm/cli/interactive/__init__.py +3 -0
- claude_mpm/cli/interactive/skills_wizard.py +491 -0
- claude_mpm/cli/parsers/__init__.py +7 -1
- claude_mpm/cli/parsers/agents_parser.py +9 -0
- claude_mpm/cli/parsers/auto_configure_parser.py +245 -0
- claude_mpm/cli/parsers/base_parser.py +110 -3
- claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
- claude_mpm/cli/parsers/mpm_init_parser.py +65 -5
- claude_mpm/cli/shared/output_formatters.py +28 -19
- claude_mpm/cli/startup.py +481 -0
- claude_mpm/cli/utils.py +52 -1
- claude_mpm/commands/mpm-agents-detect.md +168 -0
- claude_mpm/commands/mpm-agents-recommend.md +214 -0
- claude_mpm/commands/mpm-agents.md +75 -1
- claude_mpm/commands/mpm-auto-configure.md +217 -0
- claude_mpm/commands/mpm-help.md +163 -0
- claude_mpm/commands/mpm-init.md +148 -3
- claude_mpm/commands/mpm-version.md +113 -0
- claude_mpm/commands/mpm.md +1 -0
- claude_mpm/config/agent_config.py +2 -2
- claude_mpm/config/model_config.py +428 -0
- claude_mpm/constants.py +1 -0
- claude_mpm/core/base_service.py +13 -12
- claude_mpm/core/enums.py +452 -0
- claude_mpm/core/factories.py +1 -1
- claude_mpm/core/instruction_reinforcement_hook.py +2 -1
- claude_mpm/core/interactive_session.py +9 -3
- claude_mpm/core/log_manager.py +2 -0
- claude_mpm/core/logging_config.py +6 -2
- claude_mpm/core/oneshot_session.py +8 -4
- claude_mpm/core/optimized_agent_loader.py +3 -3
- claude_mpm/core/output_style_manager.py +12 -192
- claude_mpm/core/service_registry.py +5 -1
- claude_mpm/core/types.py +2 -9
- claude_mpm/core/typing_utils.py +7 -6
- claude_mpm/dashboard/static/js/dashboard.js +0 -14
- claude_mpm/dashboard/templates/index.html +3 -41
- claude_mpm/hooks/__init__.py +20 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +4 -2
- claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +23 -2
- claude_mpm/hooks/failure_learning/__init__.py +60 -0
- claude_mpm/hooks/failure_learning/failure_detection_hook.py +235 -0
- claude_mpm/hooks/failure_learning/fix_detection_hook.py +217 -0
- claude_mpm/hooks/failure_learning/learning_extraction_hook.py +286 -0
- claude_mpm/hooks/instruction_reinforcement.py +7 -2
- claude_mpm/hooks/kuzu_enrichment_hook.py +263 -0
- claude_mpm/hooks/kuzu_memory_hook.py +37 -12
- claude_mpm/hooks/kuzu_response_hook.py +183 -0
- claude_mpm/models/resume_log.py +340 -0
- claude_mpm/services/agents/__init__.py +18 -5
- claude_mpm/services/agents/auto_config_manager.py +796 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
- claude_mpm/services/agents/deployment/agent_validator.py +17 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
- claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
- claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +7 -6
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +5 -3
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +9 -6
- claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
- claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
- claude_mpm/services/agents/local_template_manager.py +1 -1
- claude_mpm/services/agents/memory/agent_memory_manager.py +5 -2
- claude_mpm/services/agents/observers.py +547 -0
- claude_mpm/services/agents/recommender.py +568 -0
- claude_mpm/services/agents/registry/modification_tracker.py +5 -2
- claude_mpm/services/command_handler_service.py +11 -5
- claude_mpm/services/core/__init__.py +33 -1
- claude_mpm/services/core/interfaces/__init__.py +90 -3
- claude_mpm/services/core/interfaces/agent.py +184 -0
- claude_mpm/services/core/interfaces/health.py +172 -0
- claude_mpm/services/core/interfaces/model.py +281 -0
- claude_mpm/services/core/interfaces/process.py +372 -0
- claude_mpm/services/core/interfaces/project.py +121 -0
- claude_mpm/services/core/interfaces/restart.py +307 -0
- claude_mpm/services/core/interfaces/stability.py +260 -0
- claude_mpm/services/core/memory_manager.py +11 -24
- claude_mpm/services/core/models/__init__.py +79 -0
- claude_mpm/services/core/models/agent_config.py +381 -0
- claude_mpm/services/core/models/health.py +162 -0
- claude_mpm/services/core/models/process.py +235 -0
- claude_mpm/services/core/models/restart.py +302 -0
- claude_mpm/services/core/models/stability.py +264 -0
- claude_mpm/services/core/models/toolchain.py +306 -0
- claude_mpm/services/core/path_resolver.py +23 -7
- claude_mpm/services/diagnostics/__init__.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_check.py +25 -24
- claude_mpm/services/diagnostics/checks/claude_code_check.py +24 -23
- claude_mpm/services/diagnostics/checks/common_issues_check.py +25 -24
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -23
- claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
- claude_mpm/services/diagnostics/checks/installation_check.py +30 -29
- claude_mpm/services/diagnostics/checks/instructions_check.py +20 -19
- claude_mpm/services/diagnostics/checks/mcp_check.py +50 -36
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +38 -33
- claude_mpm/services/diagnostics/checks/monitor_check.py +23 -22
- claude_mpm/services/diagnostics/checks/startup_log_check.py +9 -8
- claude_mpm/services/diagnostics/diagnostic_runner.py +6 -5
- claude_mpm/services/diagnostics/doctor_reporter.py +28 -25
- claude_mpm/services/diagnostics/models.py +19 -24
- claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
- claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
- claude_mpm/services/infrastructure/monitoring/base.py +5 -13
- claude_mpm/services/infrastructure/monitoring/network.py +7 -6
- claude_mpm/services/infrastructure/monitoring/process.py +13 -12
- claude_mpm/services/infrastructure/monitoring/resources.py +7 -6
- claude_mpm/services/infrastructure/monitoring/service.py +16 -15
- claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
- claude_mpm/services/local_ops/__init__.py +163 -0
- claude_mpm/services/local_ops/crash_detector.py +257 -0
- claude_mpm/services/local_ops/health_checks/__init__.py +28 -0
- claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
- claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
- claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
- claude_mpm/services/local_ops/health_manager.py +430 -0
- claude_mpm/services/local_ops/log_monitor.py +396 -0
- claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
- claude_mpm/services/local_ops/process_manager.py +595 -0
- claude_mpm/services/local_ops/resource_monitor.py +331 -0
- claude_mpm/services/local_ops/restart_manager.py +401 -0
- claude_mpm/services/local_ops/restart_policy.py +387 -0
- claude_mpm/services/local_ops/state_manager.py +372 -0
- claude_mpm/services/local_ops/unified_manager.py +600 -0
- claude_mpm/services/mcp_config_manager.py +9 -4
- claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
- claude_mpm/services/mcp_gateway/core/base.py +18 -31
- claude_mpm/services/mcp_gateway/main.py +30 -0
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +206 -32
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +30 -28
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +25 -5
- claude_mpm/services/mcp_service_verifier.py +1 -1
- claude_mpm/services/memory/failure_tracker.py +563 -0
- claude_mpm/services/memory_hook_service.py +165 -4
- claude_mpm/services/model/__init__.py +147 -0
- claude_mpm/services/model/base_provider.py +365 -0
- claude_mpm/services/model/claude_provider.py +412 -0
- claude_mpm/services/model/model_router.py +453 -0
- claude_mpm/services/model/ollama_provider.py +415 -0
- claude_mpm/services/monitor/daemon_manager.py +3 -2
- claude_mpm/services/monitor/handlers/dashboard.py +2 -1
- claude_mpm/services/monitor/handlers/hooks.py +2 -1
- claude_mpm/services/monitor/management/lifecycle.py +3 -2
- claude_mpm/services/monitor/server.py +2 -1
- claude_mpm/services/project/__init__.py +23 -0
- claude_mpm/services/project/detection_strategies.py +719 -0
- claude_mpm/services/project/toolchain_analyzer.py +581 -0
- claude_mpm/services/self_upgrade_service.py +342 -0
- claude_mpm/services/session_management_service.py +3 -2
- claude_mpm/services/session_manager.py +205 -1
- claude_mpm/services/shared/async_service_base.py +16 -27
- claude_mpm/services/shared/lifecycle_service_base.py +1 -14
- claude_mpm/services/socketio/handlers/__init__.py +5 -2
- claude_mpm/services/socketio/handlers/hook.py +13 -2
- claude_mpm/services/socketio/handlers/registry.py +4 -2
- claude_mpm/services/socketio/server/main.py +10 -8
- claude_mpm/services/subprocess_launcher_service.py +14 -5
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +8 -7
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +6 -5
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +8 -7
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +7 -6
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +5 -4
- claude_mpm/services/unified/config_strategies/validation_strategy.py +13 -9
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +10 -3
- claude_mpm/services/unified/deployment_strategies/local.py +6 -5
- claude_mpm/services/unified/deployment_strategies/utils.py +6 -5
- claude_mpm/services/unified/deployment_strategies/vercel.py +7 -6
- claude_mpm/services/unified/interfaces.py +3 -1
- claude_mpm/services/unified/unified_analyzer.py +14 -10
- claude_mpm/services/unified/unified_config.py +2 -1
- claude_mpm/services/unified/unified_deployment.py +9 -4
- claude_mpm/services/version_service.py +104 -1
- claude_mpm/skills/__init__.py +21 -0
- claude_mpm/skills/bundled/__init__.py +6 -0
- claude_mpm/skills/bundled/api-documentation.md +393 -0
- claude_mpm/skills/bundled/async-testing.md +571 -0
- claude_mpm/skills/bundled/code-review.md +143 -0
- claude_mpm/skills/bundled/database-migration.md +199 -0
- claude_mpm/skills/bundled/docker-containerization.md +194 -0
- claude_mpm/skills/bundled/express-local-dev.md +1429 -0
- claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
- claude_mpm/skills/bundled/git-workflow.md +414 -0
- claude_mpm/skills/bundled/imagemagick.md +204 -0
- claude_mpm/skills/bundled/json-data-handling.md +223 -0
- claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
- claude_mpm/skills/bundled/pdf.md +141 -0
- claude_mpm/skills/bundled/performance-profiling.md +567 -0
- claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
- claude_mpm/skills/bundled/security-scanning.md +327 -0
- claude_mpm/skills/bundled/systematic-debugging.md +473 -0
- claude_mpm/skills/bundled/test-driven-development.md +378 -0
- claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
- claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
- claude_mpm/skills/bundled/xlsx.md +157 -0
- claude_mpm/skills/registry.py +286 -0
- claude_mpm/skills/skill_manager.py +310 -0
- claude_mpm/storage/state_storage.py +15 -15
- claude_mpm/tools/code_tree_analyzer.py +177 -141
- claude_mpm/tools/code_tree_events.py +4 -2
- claude_mpm/utils/agent_dependency_loader.py +40 -20
- claude_mpm/utils/display_helper.py +260 -0
- claude_mpm/utils/git_analyzer.py +407 -0
- claude_mpm/utils/robust_installer.py +73 -19
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/METADATA +129 -12
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/RECORD +295 -193
- claude_mpm/dashboard/static/css/code-tree.css +0 -1639
- claude_mpm/dashboard/static/index-hub-backup.html +0 -713
- claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +0 -353
- claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +0 -235
- claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +0 -409
- claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +0 -435
- claude_mpm/dashboard/static/js/components/code-tree.js +0 -5869
- claude_mpm/dashboard/static/js/components/code-viewer.js +0 -1386
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +0 -425
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1041
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
- claude_mpm/services/project/analyzer_refactored.py +0 -450
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/WHEEL +0 -0
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Local Deploy command implementation for claude-mpm.
|
|
3
|
+
|
|
4
|
+
WHY: This module provides CLI commands for managing local development deployments
|
|
5
|
+
using the UnifiedLocalOpsManager. Supports starting, stopping, monitoring, and
|
|
6
|
+
managing local processes with full health monitoring and auto-restart capabilities.
|
|
7
|
+
|
|
8
|
+
DESIGN DECISIONS:
|
|
9
|
+
- Use UnifiedLocalOpsManager as single entry point
|
|
10
|
+
- Rich terminal output for better user experience
|
|
11
|
+
- Subcommands: start, stop, restart, status, health, list, monitor, history
|
|
12
|
+
- Support both interactive and script-friendly output modes
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import time
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
from rich.console import Console
|
|
21
|
+
from rich.live import Live
|
|
22
|
+
from rich.panel import Panel
|
|
23
|
+
from rich.table import Table
|
|
24
|
+
from rich.text import Text
|
|
25
|
+
|
|
26
|
+
from claude_mpm.core.enums import ServiceState
|
|
27
|
+
|
|
28
|
+
from ...services.local_ops import (
|
|
29
|
+
StartConfig,
|
|
30
|
+
UnifiedLocalOpsManager,
|
|
31
|
+
)
|
|
32
|
+
from ..shared import BaseCommand, CommandResult
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class LocalDeployCommand(BaseCommand):
|
|
36
|
+
"""Local Deploy command for managing local development deployments."""
|
|
37
|
+
|
|
38
|
+
def __init__(self):
|
|
39
|
+
super().__init__("local-deploy")
|
|
40
|
+
self.console = Console()
|
|
41
|
+
self.manager: Optional[UnifiedLocalOpsManager] = None
|
|
42
|
+
|
|
43
|
+
def validate_args(self, args) -> Optional[str]:
|
|
44
|
+
"""Validate command arguments."""
|
|
45
|
+
if not hasattr(args, "local_deploy_command") or not args.local_deploy_command:
|
|
46
|
+
return "No subcommand specified. Use: start, stop, restart, status, list, monitor, history"
|
|
47
|
+
|
|
48
|
+
valid_commands = [
|
|
49
|
+
"start",
|
|
50
|
+
"stop",
|
|
51
|
+
"restart",
|
|
52
|
+
"status",
|
|
53
|
+
"health",
|
|
54
|
+
"list",
|
|
55
|
+
"monitor",
|
|
56
|
+
"history",
|
|
57
|
+
"enable-auto-restart",
|
|
58
|
+
"disable-auto-restart",
|
|
59
|
+
]
|
|
60
|
+
if args.local_deploy_command not in valid_commands:
|
|
61
|
+
return f"Unknown subcommand: {args.local_deploy_command}. Valid commands: {', '.join(valid_commands)}"
|
|
62
|
+
|
|
63
|
+
# Validate command-specific arguments
|
|
64
|
+
if args.local_deploy_command == "start":
|
|
65
|
+
if not hasattr(args, "command") or not args.command:
|
|
66
|
+
return "Missing required argument: --command"
|
|
67
|
+
|
|
68
|
+
elif args.local_deploy_command in [
|
|
69
|
+
"stop",
|
|
70
|
+
"restart",
|
|
71
|
+
"status",
|
|
72
|
+
"health",
|
|
73
|
+
"history",
|
|
74
|
+
"enable-auto-restart",
|
|
75
|
+
"disable-auto-restart",
|
|
76
|
+
]:
|
|
77
|
+
if not hasattr(args, "deployment_id") or not args.deployment_id:
|
|
78
|
+
return "Missing required argument: --deployment-id"
|
|
79
|
+
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
def run(self, args) -> CommandResult:
|
|
83
|
+
"""Execute the local-deploy command."""
|
|
84
|
+
try:
|
|
85
|
+
self.logger.info(f"Local deploy command: {args.local_deploy_command}")
|
|
86
|
+
|
|
87
|
+
# Initialize manager
|
|
88
|
+
project_root = getattr(args, "project_dir", None) or self.working_dir
|
|
89
|
+
self.manager = UnifiedLocalOpsManager(project_root=Path(project_root))
|
|
90
|
+
|
|
91
|
+
if not self.manager.initialize():
|
|
92
|
+
return CommandResult.error_result(
|
|
93
|
+
"Failed to initialize local ops manager"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Route to specific command
|
|
97
|
+
command = args.local_deploy_command
|
|
98
|
+
if command == "start":
|
|
99
|
+
return self._start_command(args)
|
|
100
|
+
if command == "stop":
|
|
101
|
+
return self._stop_command(args)
|
|
102
|
+
if command == "restart":
|
|
103
|
+
return self._restart_command(args)
|
|
104
|
+
if command == "status":
|
|
105
|
+
return self._status_command(args)
|
|
106
|
+
if command == "health":
|
|
107
|
+
return self._health_command(args)
|
|
108
|
+
if command == "list":
|
|
109
|
+
return self._list_command(args)
|
|
110
|
+
if command == "monitor":
|
|
111
|
+
return self._monitor_command(args)
|
|
112
|
+
if command == "history":
|
|
113
|
+
return self._history_command(args)
|
|
114
|
+
if command == "enable-auto-restart":
|
|
115
|
+
return self._enable_auto_restart_command(args)
|
|
116
|
+
if command == "disable-auto-restart":
|
|
117
|
+
return self._disable_auto_restart_command(args)
|
|
118
|
+
return CommandResult.error_result(f"Unknown command: {command}")
|
|
119
|
+
|
|
120
|
+
except Exception as e:
|
|
121
|
+
self.logger.error(
|
|
122
|
+
f"Error executing local-deploy command: {e}", exc_info=True
|
|
123
|
+
)
|
|
124
|
+
return CommandResult.error_result(f"Error: {e}")
|
|
125
|
+
finally:
|
|
126
|
+
if self.manager:
|
|
127
|
+
self.manager.shutdown()
|
|
128
|
+
|
|
129
|
+
def _start_command(self, args) -> CommandResult:
|
|
130
|
+
"""Start a new deployment."""
|
|
131
|
+
try:
|
|
132
|
+
# Parse command
|
|
133
|
+
command = (
|
|
134
|
+
args.command.split() if isinstance(args.command, str) else args.command
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Create start configuration
|
|
138
|
+
config = StartConfig(
|
|
139
|
+
command=command,
|
|
140
|
+
working_directory=str(args.working_directory or self.working_dir),
|
|
141
|
+
port=getattr(args, "port", None),
|
|
142
|
+
auto_find_port=getattr(args, "auto_find_port", True),
|
|
143
|
+
environment=getattr(args, "env", {}) or {},
|
|
144
|
+
metadata={"log_file": getattr(args, "log_file", None)},
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Start deployment
|
|
148
|
+
auto_restart = getattr(args, "auto_restart", False)
|
|
149
|
+
deployment = self.manager.start_deployment(
|
|
150
|
+
config, auto_restart=auto_restart
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Output result
|
|
154
|
+
self.console.print(
|
|
155
|
+
Panel(
|
|
156
|
+
f"[green]✓[/green] Deployment started successfully\n\n"
|
|
157
|
+
f"[bold]Deployment ID:[/bold] {deployment.deployment_id}\n"
|
|
158
|
+
f"[bold]Process ID:[/bold] {deployment.process_id}\n"
|
|
159
|
+
f"[bold]Port:[/bold] {deployment.port or 'N/A'}\n"
|
|
160
|
+
f"[bold]Auto-restart:[/bold] {'Enabled' if auto_restart else 'Disabled'}\n"
|
|
161
|
+
f"[bold]Command:[/bold] {' '.join(deployment.command)}",
|
|
162
|
+
title="Deployment Started",
|
|
163
|
+
border_style="green",
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return CommandResult.success_result(
|
|
168
|
+
f"Started deployment {deployment.deployment_id}",
|
|
169
|
+
data={
|
|
170
|
+
"deployment_id": deployment.deployment_id,
|
|
171
|
+
"process_id": deployment.process_id,
|
|
172
|
+
"port": deployment.port,
|
|
173
|
+
},
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
except Exception as e:
|
|
177
|
+
self.logger.error(f"Failed to start deployment: {e}", exc_info=True)
|
|
178
|
+
self.console.print(f"[red]✗ Failed to start deployment: {e}[/red]")
|
|
179
|
+
return CommandResult.error_result(str(e))
|
|
180
|
+
|
|
181
|
+
def _stop_command(self, args) -> CommandResult:
|
|
182
|
+
"""Stop a deployment."""
|
|
183
|
+
try:
|
|
184
|
+
deployment_id = args.deployment_id
|
|
185
|
+
force = getattr(args, "force", False)
|
|
186
|
+
timeout = getattr(args, "timeout", 10)
|
|
187
|
+
|
|
188
|
+
success = self.manager.stop_deployment(
|
|
189
|
+
deployment_id, timeout=timeout, force=force
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if success:
|
|
193
|
+
self.console.print(
|
|
194
|
+
f"[green]✓ Deployment {deployment_id} stopped successfully[/green]"
|
|
195
|
+
)
|
|
196
|
+
return CommandResult.success_result(
|
|
197
|
+
f"Stopped deployment {deployment_id}"
|
|
198
|
+
)
|
|
199
|
+
self.console.print(
|
|
200
|
+
f"[red]✗ Failed to stop deployment {deployment_id}[/red]"
|
|
201
|
+
)
|
|
202
|
+
return CommandResult.error_result("Failed to stop deployment")
|
|
203
|
+
|
|
204
|
+
except Exception as e:
|
|
205
|
+
self.logger.error(f"Failed to stop deployment: {e}", exc_info=True)
|
|
206
|
+
self.console.print(f"[red]✗ Error: {e}[/red]")
|
|
207
|
+
return CommandResult.error_result(str(e))
|
|
208
|
+
|
|
209
|
+
def _restart_command(self, args) -> CommandResult:
|
|
210
|
+
"""Restart a deployment."""
|
|
211
|
+
try:
|
|
212
|
+
deployment_id = args.deployment_id
|
|
213
|
+
timeout = getattr(args, "timeout", 10)
|
|
214
|
+
|
|
215
|
+
deployment = self.manager.restart_deployment(deployment_id, timeout=timeout)
|
|
216
|
+
|
|
217
|
+
self.console.print(
|
|
218
|
+
Panel(
|
|
219
|
+
f"[green]✓[/green] Deployment restarted successfully\n\n"
|
|
220
|
+
f"[bold]Deployment ID:[/bold] {deployment.deployment_id}\n"
|
|
221
|
+
f"[bold]New Process ID:[/bold] {deployment.process_id}\n"
|
|
222
|
+
f"[bold]Port:[/bold] {deployment.port or 'N/A'}",
|
|
223
|
+
title="Deployment Restarted",
|
|
224
|
+
border_style="green",
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return CommandResult.success_result(f"Restarted deployment {deployment_id}")
|
|
229
|
+
|
|
230
|
+
except Exception as e:
|
|
231
|
+
self.logger.error(f"Failed to restart deployment: {e}", exc_info=True)
|
|
232
|
+
self.console.print(f"[red]✗ Error: {e}[/red]")
|
|
233
|
+
return CommandResult.error_result(str(e))
|
|
234
|
+
|
|
235
|
+
def _status_command(self, args) -> CommandResult:
|
|
236
|
+
"""Show deployment status."""
|
|
237
|
+
try:
|
|
238
|
+
deployment_id = args.deployment_id
|
|
239
|
+
json_output = getattr(args, "json", False)
|
|
240
|
+
|
|
241
|
+
status = self.manager.get_full_status(deployment_id)
|
|
242
|
+
|
|
243
|
+
if json_output:
|
|
244
|
+
print(json.dumps(status, indent=2, default=str))
|
|
245
|
+
return CommandResult.success_result("Status retrieved")
|
|
246
|
+
|
|
247
|
+
# Rich formatted output
|
|
248
|
+
self._render_status_panel(status)
|
|
249
|
+
|
|
250
|
+
return CommandResult.success_result("Status retrieved", data=status)
|
|
251
|
+
|
|
252
|
+
except Exception as e:
|
|
253
|
+
self.logger.error(f"Failed to get status: {e}", exc_info=True)
|
|
254
|
+
self.console.print(f"[red]✗ Error: {e}[/red]")
|
|
255
|
+
return CommandResult.error_result(str(e))
|
|
256
|
+
|
|
257
|
+
def _health_command(self, args) -> CommandResult:
|
|
258
|
+
"""Show health status."""
|
|
259
|
+
try:
|
|
260
|
+
deployment_id = args.deployment_id
|
|
261
|
+
health = self.manager.get_health_status(deployment_id)
|
|
262
|
+
|
|
263
|
+
if not health:
|
|
264
|
+
self.console.print(
|
|
265
|
+
f"[yellow]No health data available for {deployment_id}[/yellow]"
|
|
266
|
+
)
|
|
267
|
+
return CommandResult.error_result("No health data available")
|
|
268
|
+
|
|
269
|
+
# Render health status
|
|
270
|
+
status_color = {
|
|
271
|
+
"healthy": "green",
|
|
272
|
+
"degraded": "yellow",
|
|
273
|
+
"unhealthy": "red",
|
|
274
|
+
"unknown": "dim",
|
|
275
|
+
}.get(health.overall_status.value, "dim")
|
|
276
|
+
|
|
277
|
+
self.console.print(
|
|
278
|
+
Panel(
|
|
279
|
+
f"[{status_color}]Status:[/{status_color}] {health.overall_status.value.upper()}\n\n"
|
|
280
|
+
f"[bold]HTTP Check:[/bold] {'✓' if health.http_healthy else '✗'}\n"
|
|
281
|
+
f"[bold]Process Check:[/bold] {'✓' if health.process_healthy else '✗'}\n"
|
|
282
|
+
f"[bold]Resource Check:[/bold] {'✓' if health.resource_healthy else '✗'}\n"
|
|
283
|
+
f"[bold]Last Check:[/bold] {health.last_check or 'Never'}\n"
|
|
284
|
+
f"{f'[bold]Failure Reason:[/bold] {health.failure_reason}' if health.failure_reason else ''}",
|
|
285
|
+
title=f"Health Status: {deployment_id}",
|
|
286
|
+
border_style=status_color,
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
return CommandResult.success_result("Health status retrieved")
|
|
291
|
+
|
|
292
|
+
except Exception as e:
|
|
293
|
+
self.logger.error(f"Failed to get health status: {e}", exc_info=True)
|
|
294
|
+
self.console.print(f"[red]✗ Error: {e}[/red]")
|
|
295
|
+
return CommandResult.error_result(str(e))
|
|
296
|
+
|
|
297
|
+
def _list_command(self, args) -> CommandResult:
|
|
298
|
+
"""List all deployments."""
|
|
299
|
+
try:
|
|
300
|
+
status_filter_str = getattr(args, "status", None)
|
|
301
|
+
status_filter = (
|
|
302
|
+
ServiceState(status_filter_str) if status_filter_str else None
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
deployments = self.manager.list_deployments(status_filter=status_filter)
|
|
306
|
+
|
|
307
|
+
if not deployments:
|
|
308
|
+
self.console.print("[yellow]No deployments found[/yellow]")
|
|
309
|
+
return CommandResult.success_result("No deployments found")
|
|
310
|
+
|
|
311
|
+
# Create table
|
|
312
|
+
table = Table(title="Local Deployments", show_header=True)
|
|
313
|
+
table.add_column("Deployment ID", style="cyan")
|
|
314
|
+
table.add_column("PID", style="magenta")
|
|
315
|
+
table.add_column("Port", style="green")
|
|
316
|
+
table.add_column("Status", style="yellow")
|
|
317
|
+
table.add_column("Started At", style="dim")
|
|
318
|
+
|
|
319
|
+
for deployment in deployments:
|
|
320
|
+
table.add_row(
|
|
321
|
+
deployment.deployment_id,
|
|
322
|
+
str(deployment.process_id),
|
|
323
|
+
str(deployment.port) if deployment.port else "N/A",
|
|
324
|
+
deployment.status.value,
|
|
325
|
+
deployment.started_at.strftime("%Y-%m-%d %H:%M:%S"),
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
self.console.print(table)
|
|
329
|
+
|
|
330
|
+
return CommandResult.success_result(
|
|
331
|
+
f"Found {len(deployments)} deployment(s)",
|
|
332
|
+
data={"count": len(deployments)},
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
except Exception as e:
|
|
336
|
+
self.logger.error(f"Failed to list deployments: {e}", exc_info=True)
|
|
337
|
+
self.console.print(f"[red]✗ Error: {e}[/red]")
|
|
338
|
+
return CommandResult.error_result(str(e))
|
|
339
|
+
|
|
340
|
+
def _monitor_command(self, args) -> CommandResult:
|
|
341
|
+
"""Live monitoring dashboard."""
|
|
342
|
+
try:
|
|
343
|
+
deployment_id = args.deployment_id
|
|
344
|
+
refresh_interval = getattr(args, "refresh", 2)
|
|
345
|
+
|
|
346
|
+
self.console.print(
|
|
347
|
+
f"[cyan]Monitoring {deployment_id}... (Press Ctrl+C to stop)[/cyan]\n"
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
with Live(
|
|
351
|
+
console=self.console, refresh_per_second=1 / refresh_interval
|
|
352
|
+
) as live:
|
|
353
|
+
while True:
|
|
354
|
+
try:
|
|
355
|
+
status = self.manager.get_full_status(deployment_id)
|
|
356
|
+
live.update(self._render_live_status(status))
|
|
357
|
+
time.sleep(refresh_interval)
|
|
358
|
+
except KeyboardInterrupt:
|
|
359
|
+
break
|
|
360
|
+
|
|
361
|
+
return CommandResult.success_result("Monitoring stopped")
|
|
362
|
+
|
|
363
|
+
except Exception as e:
|
|
364
|
+
self.logger.error(f"Failed to monitor deployment: {e}", exc_info=True)
|
|
365
|
+
self.console.print(f"[red]✗ Error: {e}[/red]")
|
|
366
|
+
return CommandResult.error_result(str(e))
|
|
367
|
+
|
|
368
|
+
def _history_command(self, args) -> CommandResult:
|
|
369
|
+
"""Show restart history."""
|
|
370
|
+
try:
|
|
371
|
+
deployment_id = args.deployment_id
|
|
372
|
+
history = self.manager.get_restart_history(deployment_id)
|
|
373
|
+
|
|
374
|
+
if not history:
|
|
375
|
+
self.console.print(
|
|
376
|
+
f"[yellow]No restart history for {deployment_id}[/yellow]"
|
|
377
|
+
)
|
|
378
|
+
return CommandResult.success_result("No restart history")
|
|
379
|
+
|
|
380
|
+
self.console.print(
|
|
381
|
+
Panel(
|
|
382
|
+
f"[bold]Total Restarts:[/bold] {history.total_restarts}\n"
|
|
383
|
+
f"[bold]Successful:[/bold] {history.successful_restarts}\n"
|
|
384
|
+
f"[bold]Failed:[/bold] {history.failed_restarts}\n"
|
|
385
|
+
f"[bold]Circuit Breaker:[/bold] {history.circuit_breaker_state.value}\n"
|
|
386
|
+
f"[bold]Auto-restart:[/bold] {'Enabled' if history.auto_restart_enabled else 'Disabled'}",
|
|
387
|
+
title=f"Restart History: {deployment_id}",
|
|
388
|
+
border_style="cyan",
|
|
389
|
+
)
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
# Show recent attempts
|
|
393
|
+
if history.recent_attempts:
|
|
394
|
+
table = Table(title="Recent Restart Attempts", show_header=True)
|
|
395
|
+
table.add_column("Timestamp", style="dim")
|
|
396
|
+
table.add_column("Success", style="green")
|
|
397
|
+
table.add_column("Reason", style="yellow")
|
|
398
|
+
|
|
399
|
+
for attempt in history.recent_attempts[-10:]: # Last 10
|
|
400
|
+
table.add_row(
|
|
401
|
+
attempt.timestamp.strftime("%Y-%m-%d %H:%M:%S"),
|
|
402
|
+
"✓" if attempt.success else "✗",
|
|
403
|
+
attempt.reason or "Unknown",
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
self.console.print("\n")
|
|
407
|
+
self.console.print(table)
|
|
408
|
+
|
|
409
|
+
return CommandResult.success_result("Restart history retrieved")
|
|
410
|
+
|
|
411
|
+
except Exception as e:
|
|
412
|
+
self.logger.error(f"Failed to get restart history: {e}", exc_info=True)
|
|
413
|
+
self.console.print(f"[red]✗ Error: {e}[/red]")
|
|
414
|
+
return CommandResult.error_result(str(e))
|
|
415
|
+
|
|
416
|
+
def _enable_auto_restart_command(self, args) -> CommandResult:
|
|
417
|
+
"""Enable auto-restart for a deployment."""
|
|
418
|
+
try:
|
|
419
|
+
deployment_id = args.deployment_id
|
|
420
|
+
success = self.manager.enable_auto_restart(deployment_id)
|
|
421
|
+
|
|
422
|
+
if success:
|
|
423
|
+
self.console.print(
|
|
424
|
+
f"[green]✓ Auto-restart enabled for {deployment_id}[/green]"
|
|
425
|
+
)
|
|
426
|
+
return CommandResult.success_result(
|
|
427
|
+
f"Auto-restart enabled for {deployment_id}"
|
|
428
|
+
)
|
|
429
|
+
self.console.print(
|
|
430
|
+
f"[red]✗ Failed to enable auto-restart for {deployment_id}[/red]"
|
|
431
|
+
)
|
|
432
|
+
return CommandResult.error_result("Failed to enable auto-restart")
|
|
433
|
+
|
|
434
|
+
except Exception as e:
|
|
435
|
+
self.logger.error(f"Failed to enable auto-restart: {e}", exc_info=True)
|
|
436
|
+
self.console.print(f"[red]✗ Error: {e}[/red]")
|
|
437
|
+
return CommandResult.error_result(str(e))
|
|
438
|
+
|
|
439
|
+
def _disable_auto_restart_command(self, args) -> CommandResult:
|
|
440
|
+
"""Disable auto-restart for a deployment."""
|
|
441
|
+
try:
|
|
442
|
+
deployment_id = args.deployment_id
|
|
443
|
+
success = self.manager.disable_auto_restart(deployment_id)
|
|
444
|
+
|
|
445
|
+
if success:
|
|
446
|
+
self.console.print(
|
|
447
|
+
f"[green]✓ Auto-restart disabled for {deployment_id}[/green]"
|
|
448
|
+
)
|
|
449
|
+
return CommandResult.success_result(
|
|
450
|
+
f"Auto-restart disabled for {deployment_id}"
|
|
451
|
+
)
|
|
452
|
+
self.console.print(
|
|
453
|
+
f"[red]✗ Failed to disable auto-restart for {deployment_id}[/red]"
|
|
454
|
+
)
|
|
455
|
+
return CommandResult.error_result("Failed to disable auto-restart")
|
|
456
|
+
|
|
457
|
+
except Exception as e:
|
|
458
|
+
self.logger.error(f"Failed to disable auto-restart: {e}", exc_info=True)
|
|
459
|
+
self.console.print(f"[red]✗ Error: {e}[/red]")
|
|
460
|
+
return CommandResult.error_result(str(e))
|
|
461
|
+
|
|
462
|
+
def _render_status_panel(self, status: dict) -> None:
|
|
463
|
+
"""Render full status as a rich panel."""
|
|
464
|
+
process = status.get("process", {})
|
|
465
|
+
health = status.get("health", {})
|
|
466
|
+
restart = status.get("restart_history", {})
|
|
467
|
+
|
|
468
|
+
content = "[bold cyan]Process Information[/bold cyan]\n"
|
|
469
|
+
content += f" Status: {process.get('status', 'unknown')}\n"
|
|
470
|
+
content += f" PID: {process.get('pid', 'N/A')}\n"
|
|
471
|
+
content += f" Port: {process.get('port', 'N/A')}\n"
|
|
472
|
+
content += f" Uptime: {process.get('uptime_seconds', 0):.1f}s\n"
|
|
473
|
+
content += f" Memory: {process.get('memory_mb', 0):.1f} MB\n"
|
|
474
|
+
content += f" CPU: {process.get('cpu_percent', 0):.1f}%\n\n"
|
|
475
|
+
|
|
476
|
+
if health:
|
|
477
|
+
content += "[bold green]Health Status[/bold green]\n"
|
|
478
|
+
content += f" Overall: {health.get('status', 'unknown')}\n"
|
|
479
|
+
content += f" HTTP: {'✓' if health.get('http_healthy') else '✗'}\n"
|
|
480
|
+
content += f" Process: {'✓' if health.get('process_healthy') else '✗'}\n"
|
|
481
|
+
content += (
|
|
482
|
+
f" Resources: {'✓' if health.get('resource_healthy') else '✗'}\n\n"
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
if restart:
|
|
486
|
+
content += "[bold yellow]Restart Statistics[/bold yellow]\n"
|
|
487
|
+
content += f" Total Restarts: {restart.get('total_restarts', 0)}\n"
|
|
488
|
+
content += f" Successful: {restart.get('successful_restarts', 0)}\n"
|
|
489
|
+
content += f" Failed: {restart.get('failed_restarts', 0)}\n"
|
|
490
|
+
content += f" Auto-restart: {'Enabled' if restart.get('auto_restart_enabled') else 'Disabled'}"
|
|
491
|
+
|
|
492
|
+
self.console.print(
|
|
493
|
+
Panel(
|
|
494
|
+
content,
|
|
495
|
+
title=f"Status: {status.get('deployment_id', 'Unknown')}",
|
|
496
|
+
border_style="cyan",
|
|
497
|
+
)
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
def _render_live_status(self, status: dict) -> Panel:
|
|
501
|
+
"""Render status for live monitoring."""
|
|
502
|
+
process = status.get("process", {})
|
|
503
|
+
health = status.get("health", {})
|
|
504
|
+
|
|
505
|
+
content = Text()
|
|
506
|
+
content.append("Process Status\n", style="bold cyan")
|
|
507
|
+
content.append(f" PID: {process.get('pid', 'N/A')}\n")
|
|
508
|
+
content.append(f" Status: {process.get('status', 'unknown')}\n")
|
|
509
|
+
content.append(f" Uptime: {process.get('uptime_seconds', 0):.1f}s\n")
|
|
510
|
+
content.append(f" Memory: {process.get('memory_mb', 0):.1f} MB\n")
|
|
511
|
+
content.append(f" CPU: {process.get('cpu_percent', 0):.1f}%\n\n")
|
|
512
|
+
|
|
513
|
+
if health:
|
|
514
|
+
health_status = health.get("status", "unknown")
|
|
515
|
+
health_color = {
|
|
516
|
+
"healthy": "green",
|
|
517
|
+
"degraded": "yellow",
|
|
518
|
+
"unhealthy": "red",
|
|
519
|
+
}.get(health_status, "white")
|
|
520
|
+
|
|
521
|
+
content.append("Health Status\n", style="bold green")
|
|
522
|
+
content.append(" Overall: ", style="white")
|
|
523
|
+
content.append(f"{health_status.upper()}\n", style=health_color)
|
|
524
|
+
content.append(
|
|
525
|
+
f" Checks: HTTP={'✓' if health.get('http_healthy') else '✗'} "
|
|
526
|
+
f"Process={'✓' if health.get('process_healthy') else '✗'} "
|
|
527
|
+
f"Resources={'✓' if health.get('resource_healthy') else '✗'}\n"
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
return Panel(
|
|
531
|
+
content,
|
|
532
|
+
title=f"Monitoring: {status.get('deployment_id', 'Unknown')}",
|
|
533
|
+
border_style="cyan",
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
__all__ = ["LocalDeployCommand"]
|