claude-mpm 4.13.2__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.
Files changed (250) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_ENGINEER.md +286 -0
  3. claude_mpm/agents/BASE_PM.md +48 -17
  4. claude_mpm/agents/OUTPUT_STYLE.md +329 -11
  5. claude_mpm/agents/PM_INSTRUCTIONS.md +227 -8
  6. claude_mpm/agents/agent_loader.py +17 -5
  7. claude_mpm/agents/frontmatter_validator.py +284 -253
  8. claude_mpm/agents/templates/agentic-coder-optimizer.json +9 -2
  9. claude_mpm/agents/templates/api_qa.json +7 -1
  10. claude_mpm/agents/templates/clerk-ops.json +8 -1
  11. claude_mpm/agents/templates/code_analyzer.json +4 -1
  12. claude_mpm/agents/templates/dart_engineer.json +11 -1
  13. claude_mpm/agents/templates/data_engineer.json +11 -1
  14. claude_mpm/agents/templates/documentation.json +6 -1
  15. claude_mpm/agents/templates/engineer.json +18 -1
  16. claude_mpm/agents/templates/gcp_ops_agent.json +8 -1
  17. claude_mpm/agents/templates/golang_engineer.json +11 -1
  18. claude_mpm/agents/templates/java_engineer.json +12 -2
  19. claude_mpm/agents/templates/local_ops_agent.json +1217 -6
  20. claude_mpm/agents/templates/nextjs_engineer.json +11 -1
  21. claude_mpm/agents/templates/ops.json +8 -1
  22. claude_mpm/agents/templates/php-engineer.json +11 -1
  23. claude_mpm/agents/templates/project_organizer.json +10 -3
  24. claude_mpm/agents/templates/prompt-engineer.json +5 -1
  25. claude_mpm/agents/templates/python_engineer.json +11 -1
  26. claude_mpm/agents/templates/qa.json +7 -1
  27. claude_mpm/agents/templates/react_engineer.json +11 -1
  28. claude_mpm/agents/templates/refactoring_engineer.json +8 -1
  29. claude_mpm/agents/templates/research.json +4 -1
  30. claude_mpm/agents/templates/ruby-engineer.json +11 -1
  31. claude_mpm/agents/templates/rust_engineer.json +11 -1
  32. claude_mpm/agents/templates/security.json +6 -1
  33. claude_mpm/agents/templates/svelte-engineer.json +225 -0
  34. claude_mpm/agents/templates/ticketing.json +6 -1
  35. claude_mpm/agents/templates/typescript_engineer.json +11 -1
  36. claude_mpm/agents/templates/vercel_ops_agent.json +8 -1
  37. claude_mpm/agents/templates/version_control.json +8 -1
  38. claude_mpm/agents/templates/web_qa.json +7 -1
  39. claude_mpm/agents/templates/web_ui.json +11 -1
  40. claude_mpm/cli/__init__.py +34 -706
  41. claude_mpm/cli/commands/agent_manager.py +25 -12
  42. claude_mpm/cli/commands/agent_state_manager.py +186 -0
  43. claude_mpm/cli/commands/agents.py +204 -148
  44. claude_mpm/cli/commands/aggregate.py +7 -3
  45. claude_mpm/cli/commands/analyze.py +9 -4
  46. claude_mpm/cli/commands/analyze_code.py +7 -2
  47. claude_mpm/cli/commands/auto_configure.py +7 -9
  48. claude_mpm/cli/commands/config.py +47 -13
  49. claude_mpm/cli/commands/configure.py +294 -1788
  50. claude_mpm/cli/commands/configure_agent_display.py +261 -0
  51. claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
  52. claude_mpm/cli/commands/configure_hook_manager.py +225 -0
  53. claude_mpm/cli/commands/configure_models.py +18 -0
  54. claude_mpm/cli/commands/configure_navigation.py +167 -0
  55. claude_mpm/cli/commands/configure_paths.py +104 -0
  56. claude_mpm/cli/commands/configure_persistence.py +254 -0
  57. claude_mpm/cli/commands/configure_startup_manager.py +646 -0
  58. claude_mpm/cli/commands/configure_template_editor.py +497 -0
  59. claude_mpm/cli/commands/configure_validators.py +73 -0
  60. claude_mpm/cli/commands/local_deploy.py +537 -0
  61. claude_mpm/cli/commands/memory.py +54 -20
  62. claude_mpm/cli/commands/mpm_init.py +39 -25
  63. claude_mpm/cli/commands/mpm_init_handler.py +8 -3
  64. claude_mpm/cli/executor.py +202 -0
  65. claude_mpm/cli/helpers.py +105 -0
  66. claude_mpm/cli/interactive/__init__.py +3 -0
  67. claude_mpm/cli/interactive/skills_wizard.py +491 -0
  68. claude_mpm/cli/parsers/__init__.py +7 -1
  69. claude_mpm/cli/parsers/base_parser.py +98 -3
  70. claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
  71. claude_mpm/cli/shared/output_formatters.py +28 -19
  72. claude_mpm/cli/startup.py +481 -0
  73. claude_mpm/cli/utils.py +52 -1
  74. claude_mpm/commands/mpm-help.md +3 -0
  75. claude_mpm/commands/mpm-version.md +113 -0
  76. claude_mpm/commands/mpm.md +1 -0
  77. claude_mpm/config/agent_config.py +2 -2
  78. claude_mpm/config/model_config.py +428 -0
  79. claude_mpm/core/base_service.py +13 -12
  80. claude_mpm/core/enums.py +452 -0
  81. claude_mpm/core/factories.py +1 -1
  82. claude_mpm/core/instruction_reinforcement_hook.py +2 -1
  83. claude_mpm/core/interactive_session.py +9 -3
  84. claude_mpm/core/logging_config.py +6 -2
  85. claude_mpm/core/oneshot_session.py +8 -4
  86. claude_mpm/core/optimized_agent_loader.py +3 -3
  87. claude_mpm/core/output_style_manager.py +12 -192
  88. claude_mpm/core/service_registry.py +5 -1
  89. claude_mpm/core/types.py +2 -9
  90. claude_mpm/core/typing_utils.py +7 -6
  91. claude_mpm/dashboard/static/js/dashboard.js +0 -14
  92. claude_mpm/dashboard/templates/index.html +3 -41
  93. claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
  94. claude_mpm/hooks/instruction_reinforcement.py +7 -2
  95. claude_mpm/models/resume_log.py +340 -0
  96. claude_mpm/services/agents/auto_config_manager.py +10 -11
  97. claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
  98. claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
  99. claude_mpm/services/agents/deployment/agent_validator.py +17 -1
  100. claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
  101. claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
  102. claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
  103. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +7 -6
  104. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
  105. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
  106. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +5 -3
  107. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
  108. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +9 -6
  109. claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
  110. claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
  111. claude_mpm/services/agents/local_template_manager.py +1 -1
  112. claude_mpm/services/agents/memory/agent_memory_manager.py +5 -2
  113. claude_mpm/services/agents/registry/modification_tracker.py +5 -2
  114. claude_mpm/services/command_handler_service.py +11 -5
  115. claude_mpm/services/core/interfaces/__init__.py +74 -2
  116. claude_mpm/services/core/interfaces/health.py +172 -0
  117. claude_mpm/services/core/interfaces/model.py +281 -0
  118. claude_mpm/services/core/interfaces/process.py +372 -0
  119. claude_mpm/services/core/interfaces/restart.py +307 -0
  120. claude_mpm/services/core/interfaces/stability.py +260 -0
  121. claude_mpm/services/core/models/__init__.py +33 -0
  122. claude_mpm/services/core/models/agent_config.py +12 -28
  123. claude_mpm/services/core/models/health.py +162 -0
  124. claude_mpm/services/core/models/process.py +235 -0
  125. claude_mpm/services/core/models/restart.py +302 -0
  126. claude_mpm/services/core/models/stability.py +264 -0
  127. claude_mpm/services/core/path_resolver.py +23 -7
  128. claude_mpm/services/diagnostics/__init__.py +2 -2
  129. claude_mpm/services/diagnostics/checks/agent_check.py +25 -24
  130. claude_mpm/services/diagnostics/checks/claude_code_check.py +24 -23
  131. claude_mpm/services/diagnostics/checks/common_issues_check.py +25 -24
  132. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -23
  133. claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
  134. claude_mpm/services/diagnostics/checks/installation_check.py +30 -29
  135. claude_mpm/services/diagnostics/checks/instructions_check.py +20 -19
  136. claude_mpm/services/diagnostics/checks/mcp_check.py +50 -36
  137. claude_mpm/services/diagnostics/checks/mcp_services_check.py +36 -31
  138. claude_mpm/services/diagnostics/checks/monitor_check.py +23 -22
  139. claude_mpm/services/diagnostics/checks/startup_log_check.py +9 -8
  140. claude_mpm/services/diagnostics/diagnostic_runner.py +6 -5
  141. claude_mpm/services/diagnostics/doctor_reporter.py +28 -25
  142. claude_mpm/services/diagnostics/models.py +19 -24
  143. claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
  144. claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
  145. claude_mpm/services/infrastructure/monitoring/base.py +5 -13
  146. claude_mpm/services/infrastructure/monitoring/network.py +7 -6
  147. claude_mpm/services/infrastructure/monitoring/process.py +13 -12
  148. claude_mpm/services/infrastructure/monitoring/resources.py +7 -6
  149. claude_mpm/services/infrastructure/monitoring/service.py +16 -15
  150. claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
  151. claude_mpm/services/local_ops/__init__.py +163 -0
  152. claude_mpm/services/local_ops/crash_detector.py +257 -0
  153. claude_mpm/services/local_ops/health_checks/__init__.py +28 -0
  154. claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
  155. claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
  156. claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
  157. claude_mpm/services/local_ops/health_manager.py +430 -0
  158. claude_mpm/services/local_ops/log_monitor.py +396 -0
  159. claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
  160. claude_mpm/services/local_ops/process_manager.py +595 -0
  161. claude_mpm/services/local_ops/resource_monitor.py +331 -0
  162. claude_mpm/services/local_ops/restart_manager.py +401 -0
  163. claude_mpm/services/local_ops/restart_policy.py +387 -0
  164. claude_mpm/services/local_ops/state_manager.py +372 -0
  165. claude_mpm/services/local_ops/unified_manager.py +600 -0
  166. claude_mpm/services/mcp_config_manager.py +9 -4
  167. claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
  168. claude_mpm/services/mcp_gateway/core/base.py +18 -31
  169. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +71 -24
  170. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +30 -28
  171. claude_mpm/services/memory_hook_service.py +4 -1
  172. claude_mpm/services/model/__init__.py +147 -0
  173. claude_mpm/services/model/base_provider.py +365 -0
  174. claude_mpm/services/model/claude_provider.py +412 -0
  175. claude_mpm/services/model/model_router.py +453 -0
  176. claude_mpm/services/model/ollama_provider.py +415 -0
  177. claude_mpm/services/monitor/daemon_manager.py +3 -2
  178. claude_mpm/services/monitor/handlers/dashboard.py +2 -1
  179. claude_mpm/services/monitor/handlers/hooks.py +2 -1
  180. claude_mpm/services/monitor/management/lifecycle.py +3 -2
  181. claude_mpm/services/monitor/server.py +2 -1
  182. claude_mpm/services/session_management_service.py +3 -2
  183. claude_mpm/services/session_manager.py +205 -1
  184. claude_mpm/services/shared/async_service_base.py +16 -27
  185. claude_mpm/services/shared/lifecycle_service_base.py +1 -14
  186. claude_mpm/services/socketio/handlers/__init__.py +5 -2
  187. claude_mpm/services/socketio/handlers/hook.py +13 -2
  188. claude_mpm/services/socketio/handlers/registry.py +4 -2
  189. claude_mpm/services/socketio/server/main.py +10 -8
  190. claude_mpm/services/subprocess_launcher_service.py +14 -5
  191. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +8 -7
  192. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +6 -5
  193. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +8 -7
  194. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +7 -6
  195. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +5 -4
  196. claude_mpm/services/unified/config_strategies/validation_strategy.py +13 -9
  197. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +10 -3
  198. claude_mpm/services/unified/deployment_strategies/local.py +6 -5
  199. claude_mpm/services/unified/deployment_strategies/utils.py +6 -5
  200. claude_mpm/services/unified/deployment_strategies/vercel.py +7 -6
  201. claude_mpm/services/unified/interfaces.py +3 -1
  202. claude_mpm/services/unified/unified_analyzer.py +14 -10
  203. claude_mpm/services/unified/unified_config.py +2 -1
  204. claude_mpm/services/unified/unified_deployment.py +9 -4
  205. claude_mpm/services/version_service.py +104 -1
  206. claude_mpm/skills/__init__.py +21 -0
  207. claude_mpm/skills/bundled/__init__.py +6 -0
  208. claude_mpm/skills/bundled/api-documentation.md +393 -0
  209. claude_mpm/skills/bundled/async-testing.md +571 -0
  210. claude_mpm/skills/bundled/code-review.md +143 -0
  211. claude_mpm/skills/bundled/database-migration.md +199 -0
  212. claude_mpm/skills/bundled/docker-containerization.md +194 -0
  213. claude_mpm/skills/bundled/express-local-dev.md +1429 -0
  214. claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
  215. claude_mpm/skills/bundled/git-workflow.md +414 -0
  216. claude_mpm/skills/bundled/imagemagick.md +204 -0
  217. claude_mpm/skills/bundled/json-data-handling.md +223 -0
  218. claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
  219. claude_mpm/skills/bundled/pdf.md +141 -0
  220. claude_mpm/skills/bundled/performance-profiling.md +567 -0
  221. claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
  222. claude_mpm/skills/bundled/security-scanning.md +327 -0
  223. claude_mpm/skills/bundled/systematic-debugging.md +473 -0
  224. claude_mpm/skills/bundled/test-driven-development.md +378 -0
  225. claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
  226. claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
  227. claude_mpm/skills/bundled/xlsx.md +157 -0
  228. claude_mpm/skills/registry.py +286 -0
  229. claude_mpm/skills/skill_manager.py +310 -0
  230. claude_mpm/tools/code_tree_analyzer.py +177 -141
  231. claude_mpm/tools/code_tree_events.py +4 -2
  232. claude_mpm/utils/agent_dependency_loader.py +2 -2
  233. {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/METADATA +117 -8
  234. {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/RECORD +238 -174
  235. claude_mpm/dashboard/static/css/code-tree.css +0 -1639
  236. claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +0 -353
  237. claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +0 -235
  238. claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +0 -409
  239. claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +0 -435
  240. claude_mpm/dashboard/static/js/components/code-tree.js +0 -5869
  241. claude_mpm/dashboard/static/js/components/code-viewer.js +0 -1386
  242. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +0 -425
  243. claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1041
  244. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
  245. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
  246. claude_mpm/services/project/analyzer_refactored.py +0 -450
  247. {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/WHEEL +0 -0
  248. {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/entry_points.txt +0 -0
  249. {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/licenses/LICENSE +0 -0
  250. {claude_mpm-4.13.2.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"]