claude-mpm 4.13.2__py3-none-any.whl → 4.14.0__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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (44) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/cli/__init__.py +10 -0
  3. claude_mpm/cli/commands/local_deploy.py +536 -0
  4. claude_mpm/cli/parsers/base_parser.py +7 -0
  5. claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
  6. claude_mpm/config/model_config.py +428 -0
  7. claude_mpm/core/interactive_session.py +3 -0
  8. claude_mpm/services/core/interfaces/__init__.py +74 -2
  9. claude_mpm/services/core/interfaces/health.py +172 -0
  10. claude_mpm/services/core/interfaces/model.py +281 -0
  11. claude_mpm/services/core/interfaces/process.py +372 -0
  12. claude_mpm/services/core/interfaces/restart.py +307 -0
  13. claude_mpm/services/core/interfaces/stability.py +260 -0
  14. claude_mpm/services/core/models/__init__.py +35 -0
  15. claude_mpm/services/core/models/health.py +189 -0
  16. claude_mpm/services/core/models/process.py +258 -0
  17. claude_mpm/services/core/models/restart.py +302 -0
  18. claude_mpm/services/core/models/stability.py +264 -0
  19. claude_mpm/services/local_ops/__init__.py +163 -0
  20. claude_mpm/services/local_ops/crash_detector.py +257 -0
  21. claude_mpm/services/local_ops/health_checks/__init__.py +28 -0
  22. claude_mpm/services/local_ops/health_checks/http_check.py +223 -0
  23. claude_mpm/services/local_ops/health_checks/process_check.py +235 -0
  24. claude_mpm/services/local_ops/health_checks/resource_check.py +254 -0
  25. claude_mpm/services/local_ops/health_manager.py +430 -0
  26. claude_mpm/services/local_ops/log_monitor.py +396 -0
  27. claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
  28. claude_mpm/services/local_ops/process_manager.py +595 -0
  29. claude_mpm/services/local_ops/resource_monitor.py +331 -0
  30. claude_mpm/services/local_ops/restart_manager.py +401 -0
  31. claude_mpm/services/local_ops/restart_policy.py +387 -0
  32. claude_mpm/services/local_ops/state_manager.py +371 -0
  33. claude_mpm/services/local_ops/unified_manager.py +600 -0
  34. claude_mpm/services/model/__init__.py +147 -0
  35. claude_mpm/services/model/base_provider.py +365 -0
  36. claude_mpm/services/model/claude_provider.py +412 -0
  37. claude_mpm/services/model/model_router.py +453 -0
  38. claude_mpm/services/model/ollama_provider.py +415 -0
  39. {claude_mpm-4.13.2.dist-info → claude_mpm-4.14.0.dist-info}/METADATA +1 -1
  40. {claude_mpm-4.13.2.dist-info → claude_mpm-4.14.0.dist-info}/RECORD +44 -12
  41. {claude_mpm-4.13.2.dist-info → claude_mpm-4.14.0.dist-info}/WHEEL +0 -0
  42. {claude_mpm-4.13.2.dist-info → claude_mpm-4.14.0.dist-info}/entry_points.txt +0 -0
  43. {claude_mpm-4.13.2.dist-info → claude_mpm-4.14.0.dist-info}/licenses/LICENSE +0 -0
  44. {claude_mpm-4.13.2.dist-info → claude_mpm-4.14.0.dist-info}/top_level.txt +0 -0
claude_mpm/VERSION CHANGED
@@ -1 +1 @@
1
- 4.13.2
1
+ 4.14.0
@@ -730,6 +730,16 @@ def _execute_command(command: str, args) -> int:
730
730
  # Convert CommandResult to exit code
731
731
  return result.exit_code if result else 0
732
732
 
733
+ # Handle local-deploy command with lazy import
734
+ if command == "local-deploy":
735
+ # Lazy import to avoid loading unless needed
736
+ from .commands.local_deploy import LocalDeployCommand
737
+
738
+ cmd = LocalDeployCommand()
739
+ result = cmd.run(args)
740
+ # Convert CommandResult to exit code
741
+ return result.exit_code if result else 0
742
+
733
743
  # Map stable commands to their implementations
734
744
  command_map = {
735
745
  CLICommands.RUN.value: run_session,
@@ -0,0 +1,536 @@
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 ...services.local_ops import (
27
+ ProcessStatus,
28
+ StartConfig,
29
+ UnifiedLocalOpsManager,
30
+ )
31
+ from ..shared import BaseCommand, CommandResult
32
+
33
+
34
+ class LocalDeployCommand(BaseCommand):
35
+ """Local Deploy command for managing local development deployments."""
36
+
37
+ def __init__(self):
38
+ super().__init__("local-deploy")
39
+ self.console = Console()
40
+ self.manager: Optional[UnifiedLocalOpsManager] = None
41
+
42
+ def validate_args(self, args) -> Optional[str]:
43
+ """Validate command arguments."""
44
+ if not hasattr(args, "local_deploy_command") or not args.local_deploy_command:
45
+ return "No subcommand specified. Use: start, stop, restart, status, list, monitor, history"
46
+
47
+ valid_commands = [
48
+ "start",
49
+ "stop",
50
+ "restart",
51
+ "status",
52
+ "health",
53
+ "list",
54
+ "monitor",
55
+ "history",
56
+ "enable-auto-restart",
57
+ "disable-auto-restart",
58
+ ]
59
+ if args.local_deploy_command not in valid_commands:
60
+ return f"Unknown subcommand: {args.local_deploy_command}. Valid commands: {', '.join(valid_commands)}"
61
+
62
+ # Validate command-specific arguments
63
+ if args.local_deploy_command == "start":
64
+ if not hasattr(args, "command") or not args.command:
65
+ return "Missing required argument: --command"
66
+
67
+ elif args.local_deploy_command in [
68
+ "stop",
69
+ "restart",
70
+ "status",
71
+ "health",
72
+ "history",
73
+ "enable-auto-restart",
74
+ "disable-auto-restart",
75
+ ]:
76
+ if not hasattr(args, "deployment_id") or not args.deployment_id:
77
+ return "Missing required argument: --deployment-id"
78
+
79
+ return None
80
+
81
+ def run(self, args) -> CommandResult:
82
+ """Execute the local-deploy command."""
83
+ try:
84
+ self.logger.info(f"Local deploy command: {args.local_deploy_command}")
85
+
86
+ # Initialize manager
87
+ project_root = getattr(args, "project_dir", None) or self.working_dir
88
+ self.manager = UnifiedLocalOpsManager(project_root=Path(project_root))
89
+
90
+ if not self.manager.initialize():
91
+ return CommandResult.error_result(
92
+ "Failed to initialize local ops manager"
93
+ )
94
+
95
+ # Route to specific command
96
+ command = args.local_deploy_command
97
+ if command == "start":
98
+ return self._start_command(args)
99
+ if command == "stop":
100
+ return self._stop_command(args)
101
+ if command == "restart":
102
+ return self._restart_command(args)
103
+ if command == "status":
104
+ return self._status_command(args)
105
+ if command == "health":
106
+ return self._health_command(args)
107
+ if command == "list":
108
+ return self._list_command(args)
109
+ if command == "monitor":
110
+ return self._monitor_command(args)
111
+ if command == "history":
112
+ return self._history_command(args)
113
+ if command == "enable-auto-restart":
114
+ return self._enable_auto_restart_command(args)
115
+ if command == "disable-auto-restart":
116
+ return self._disable_auto_restart_command(args)
117
+ return CommandResult.error_result(f"Unknown command: {command}")
118
+
119
+ except Exception as e:
120
+ self.logger.error(
121
+ f"Error executing local-deploy command: {e}", exc_info=True
122
+ )
123
+ return CommandResult.error_result(f"Error: {e}")
124
+ finally:
125
+ if self.manager:
126
+ self.manager.shutdown()
127
+
128
+ def _start_command(self, args) -> CommandResult:
129
+ """Start a new deployment."""
130
+ try:
131
+ # Parse command
132
+ command = (
133
+ args.command.split() if isinstance(args.command, str) else args.command
134
+ )
135
+
136
+ # Create start configuration
137
+ config = StartConfig(
138
+ command=command,
139
+ working_directory=str(args.working_directory or self.working_dir),
140
+ port=getattr(args, "port", None),
141
+ auto_find_port=getattr(args, "auto_find_port", True),
142
+ environment=getattr(args, "env", {}) or {},
143
+ metadata={"log_file": getattr(args, "log_file", None)},
144
+ )
145
+
146
+ # Start deployment
147
+ auto_restart = getattr(args, "auto_restart", False)
148
+ deployment = self.manager.start_deployment(
149
+ config, auto_restart=auto_restart
150
+ )
151
+
152
+ # Output result
153
+ self.console.print(
154
+ Panel(
155
+ f"[green]✓[/green] Deployment started successfully\n\n"
156
+ f"[bold]Deployment ID:[/bold] {deployment.deployment_id}\n"
157
+ f"[bold]Process ID:[/bold] {deployment.process_id}\n"
158
+ f"[bold]Port:[/bold] {deployment.port or 'N/A'}\n"
159
+ f"[bold]Auto-restart:[/bold] {'Enabled' if auto_restart else 'Disabled'}\n"
160
+ f"[bold]Command:[/bold] {' '.join(deployment.command)}",
161
+ title="Deployment Started",
162
+ border_style="green",
163
+ )
164
+ )
165
+
166
+ return CommandResult.success_result(
167
+ f"Started deployment {deployment.deployment_id}",
168
+ data={
169
+ "deployment_id": deployment.deployment_id,
170
+ "process_id": deployment.process_id,
171
+ "port": deployment.port,
172
+ },
173
+ )
174
+
175
+ except Exception as e:
176
+ self.logger.error(f"Failed to start deployment: {e}", exc_info=True)
177
+ self.console.print(f"[red]✗ Failed to start deployment: {e}[/red]")
178
+ return CommandResult.error_result(str(e))
179
+
180
+ def _stop_command(self, args) -> CommandResult:
181
+ """Stop a deployment."""
182
+ try:
183
+ deployment_id = args.deployment_id
184
+ force = getattr(args, "force", False)
185
+ timeout = getattr(args, "timeout", 10)
186
+
187
+ success = self.manager.stop_deployment(
188
+ deployment_id, timeout=timeout, force=force
189
+ )
190
+
191
+ if success:
192
+ self.console.print(
193
+ f"[green]✓ Deployment {deployment_id} stopped successfully[/green]"
194
+ )
195
+ return CommandResult.success_result(
196
+ f"Stopped deployment {deployment_id}"
197
+ )
198
+ self.console.print(
199
+ f"[red]✗ Failed to stop deployment {deployment_id}[/red]"
200
+ )
201
+ return CommandResult.error_result("Failed to stop deployment")
202
+
203
+ except Exception as e:
204
+ self.logger.error(f"Failed to stop deployment: {e}", exc_info=True)
205
+ self.console.print(f"[red]✗ Error: {e}[/red]")
206
+ return CommandResult.error_result(str(e))
207
+
208
+ def _restart_command(self, args) -> CommandResult:
209
+ """Restart a deployment."""
210
+ try:
211
+ deployment_id = args.deployment_id
212
+ timeout = getattr(args, "timeout", 10)
213
+
214
+ deployment = self.manager.restart_deployment(deployment_id, timeout=timeout)
215
+
216
+ self.console.print(
217
+ Panel(
218
+ f"[green]✓[/green] Deployment restarted successfully\n\n"
219
+ f"[bold]Deployment ID:[/bold] {deployment.deployment_id}\n"
220
+ f"[bold]New Process ID:[/bold] {deployment.process_id}\n"
221
+ f"[bold]Port:[/bold] {deployment.port or 'N/A'}",
222
+ title="Deployment Restarted",
223
+ border_style="green",
224
+ )
225
+ )
226
+
227
+ return CommandResult.success_result(f"Restarted deployment {deployment_id}")
228
+
229
+ except Exception as e:
230
+ self.logger.error(f"Failed to restart deployment: {e}", exc_info=True)
231
+ self.console.print(f"[red]✗ Error: {e}[/red]")
232
+ return CommandResult.error_result(str(e))
233
+
234
+ def _status_command(self, args) -> CommandResult:
235
+ """Show deployment status."""
236
+ try:
237
+ deployment_id = args.deployment_id
238
+ json_output = getattr(args, "json", False)
239
+
240
+ status = self.manager.get_full_status(deployment_id)
241
+
242
+ if json_output:
243
+ print(json.dumps(status, indent=2, default=str))
244
+ return CommandResult.success_result("Status retrieved")
245
+
246
+ # Rich formatted output
247
+ self._render_status_panel(status)
248
+
249
+ return CommandResult.success_result("Status retrieved", data=status)
250
+
251
+ except Exception as e:
252
+ self.logger.error(f"Failed to get status: {e}", exc_info=True)
253
+ self.console.print(f"[red]✗ Error: {e}[/red]")
254
+ return CommandResult.error_result(str(e))
255
+
256
+ def _health_command(self, args) -> CommandResult:
257
+ """Show health status."""
258
+ try:
259
+ deployment_id = args.deployment_id
260
+ health = self.manager.get_health_status(deployment_id)
261
+
262
+ if not health:
263
+ self.console.print(
264
+ f"[yellow]No health data available for {deployment_id}[/yellow]"
265
+ )
266
+ return CommandResult.error_result("No health data available")
267
+
268
+ # Render health status
269
+ status_color = {
270
+ "healthy": "green",
271
+ "degraded": "yellow",
272
+ "unhealthy": "red",
273
+ "unknown": "dim",
274
+ }.get(health.overall_status.value, "dim")
275
+
276
+ self.console.print(
277
+ Panel(
278
+ f"[{status_color}]Status:[/{status_color}] {health.overall_status.value.upper()}\n\n"
279
+ f"[bold]HTTP Check:[/bold] {'✓' if health.http_healthy else '✗'}\n"
280
+ f"[bold]Process Check:[/bold] {'✓' if health.process_healthy else '✗'}\n"
281
+ f"[bold]Resource Check:[/bold] {'✓' if health.resource_healthy else '✗'}\n"
282
+ f"[bold]Last Check:[/bold] {health.last_check or 'Never'}\n"
283
+ f"{f'[bold]Failure Reason:[/bold] {health.failure_reason}' if health.failure_reason else ''}",
284
+ title=f"Health Status: {deployment_id}",
285
+ border_style=status_color,
286
+ )
287
+ )
288
+
289
+ return CommandResult.success_result("Health status retrieved")
290
+
291
+ except Exception as e:
292
+ self.logger.error(f"Failed to get health status: {e}", exc_info=True)
293
+ self.console.print(f"[red]✗ Error: {e}[/red]")
294
+ return CommandResult.error_result(str(e))
295
+
296
+ def _list_command(self, args) -> CommandResult:
297
+ """List all deployments."""
298
+ try:
299
+ status_filter_str = getattr(args, "status", None)
300
+ status_filter = (
301
+ ProcessStatus(status_filter_str) if status_filter_str else None
302
+ )
303
+
304
+ deployments = self.manager.list_deployments(status_filter=status_filter)
305
+
306
+ if not deployments:
307
+ self.console.print("[yellow]No deployments found[/yellow]")
308
+ return CommandResult.success_result("No deployments found")
309
+
310
+ # Create table
311
+ table = Table(title="Local Deployments", show_header=True)
312
+ table.add_column("Deployment ID", style="cyan")
313
+ table.add_column("PID", style="magenta")
314
+ table.add_column("Port", style="green")
315
+ table.add_column("Status", style="yellow")
316
+ table.add_column("Started At", style="dim")
317
+
318
+ for deployment in deployments:
319
+ table.add_row(
320
+ deployment.deployment_id,
321
+ str(deployment.process_id),
322
+ str(deployment.port) if deployment.port else "N/A",
323
+ deployment.status.value,
324
+ deployment.started_at.strftime("%Y-%m-%d %H:%M:%S"),
325
+ )
326
+
327
+ self.console.print(table)
328
+
329
+ return CommandResult.success_result(
330
+ f"Found {len(deployments)} deployment(s)",
331
+ data={"count": len(deployments)},
332
+ )
333
+
334
+ except Exception as e:
335
+ self.logger.error(f"Failed to list deployments: {e}", exc_info=True)
336
+ self.console.print(f"[red]✗ Error: {e}[/red]")
337
+ return CommandResult.error_result(str(e))
338
+
339
+ def _monitor_command(self, args) -> CommandResult:
340
+ """Live monitoring dashboard."""
341
+ try:
342
+ deployment_id = args.deployment_id
343
+ refresh_interval = getattr(args, "refresh", 2)
344
+
345
+ self.console.print(
346
+ f"[cyan]Monitoring {deployment_id}... (Press Ctrl+C to stop)[/cyan]\n"
347
+ )
348
+
349
+ with Live(
350
+ console=self.console, refresh_per_second=1 / refresh_interval
351
+ ) as live:
352
+ while True:
353
+ try:
354
+ status = self.manager.get_full_status(deployment_id)
355
+ live.update(self._render_live_status(status))
356
+ time.sleep(refresh_interval)
357
+ except KeyboardInterrupt:
358
+ break
359
+
360
+ return CommandResult.success_result("Monitoring stopped")
361
+
362
+ except Exception as e:
363
+ self.logger.error(f"Failed to monitor deployment: {e}", exc_info=True)
364
+ self.console.print(f"[red]✗ Error: {e}[/red]")
365
+ return CommandResult.error_result(str(e))
366
+
367
+ def _history_command(self, args) -> CommandResult:
368
+ """Show restart history."""
369
+ try:
370
+ deployment_id = args.deployment_id
371
+ history = self.manager.get_restart_history(deployment_id)
372
+
373
+ if not history:
374
+ self.console.print(
375
+ f"[yellow]No restart history for {deployment_id}[/yellow]"
376
+ )
377
+ return CommandResult.success_result("No restart history")
378
+
379
+ self.console.print(
380
+ Panel(
381
+ f"[bold]Total Restarts:[/bold] {history.total_restarts}\n"
382
+ f"[bold]Successful:[/bold] {history.successful_restarts}\n"
383
+ f"[bold]Failed:[/bold] {history.failed_restarts}\n"
384
+ f"[bold]Circuit Breaker:[/bold] {history.circuit_breaker_state.value}\n"
385
+ f"[bold]Auto-restart:[/bold] {'Enabled' if history.auto_restart_enabled else 'Disabled'}",
386
+ title=f"Restart History: {deployment_id}",
387
+ border_style="cyan",
388
+ )
389
+ )
390
+
391
+ # Show recent attempts
392
+ if history.recent_attempts:
393
+ table = Table(title="Recent Restart Attempts", show_header=True)
394
+ table.add_column("Timestamp", style="dim")
395
+ table.add_column("Success", style="green")
396
+ table.add_column("Reason", style="yellow")
397
+
398
+ for attempt in history.recent_attempts[-10:]: # Last 10
399
+ table.add_row(
400
+ attempt.timestamp.strftime("%Y-%m-%d %H:%M:%S"),
401
+ "✓" if attempt.success else "✗",
402
+ attempt.reason or "Unknown",
403
+ )
404
+
405
+ self.console.print("\n")
406
+ self.console.print(table)
407
+
408
+ return CommandResult.success_result("Restart history retrieved")
409
+
410
+ except Exception as e:
411
+ self.logger.error(f"Failed to get restart history: {e}", exc_info=True)
412
+ self.console.print(f"[red]✗ Error: {e}[/red]")
413
+ return CommandResult.error_result(str(e))
414
+
415
+ def _enable_auto_restart_command(self, args) -> CommandResult:
416
+ """Enable auto-restart for a deployment."""
417
+ try:
418
+ deployment_id = args.deployment_id
419
+ success = self.manager.enable_auto_restart(deployment_id)
420
+
421
+ if success:
422
+ self.console.print(
423
+ f"[green]✓ Auto-restart enabled for {deployment_id}[/green]"
424
+ )
425
+ return CommandResult.success_result(
426
+ f"Auto-restart enabled for {deployment_id}"
427
+ )
428
+ self.console.print(
429
+ f"[red]✗ Failed to enable auto-restart for {deployment_id}[/red]"
430
+ )
431
+ return CommandResult.error_result("Failed to enable auto-restart")
432
+
433
+ except Exception as e:
434
+ self.logger.error(f"Failed to enable auto-restart: {e}", exc_info=True)
435
+ self.console.print(f"[red]✗ Error: {e}[/red]")
436
+ return CommandResult.error_result(str(e))
437
+
438
+ def _disable_auto_restart_command(self, args) -> CommandResult:
439
+ """Disable auto-restart for a deployment."""
440
+ try:
441
+ deployment_id = args.deployment_id
442
+ success = self.manager.disable_auto_restart(deployment_id)
443
+
444
+ if success:
445
+ self.console.print(
446
+ f"[green]✓ Auto-restart disabled for {deployment_id}[/green]"
447
+ )
448
+ return CommandResult.success_result(
449
+ f"Auto-restart disabled for {deployment_id}"
450
+ )
451
+ self.console.print(
452
+ f"[red]✗ Failed to disable auto-restart for {deployment_id}[/red]"
453
+ )
454
+ return CommandResult.error_result("Failed to disable auto-restart")
455
+
456
+ except Exception as e:
457
+ self.logger.error(f"Failed to disable auto-restart: {e}", exc_info=True)
458
+ self.console.print(f"[red]✗ Error: {e}[/red]")
459
+ return CommandResult.error_result(str(e))
460
+
461
+ def _render_status_panel(self, status: dict) -> None:
462
+ """Render full status as a rich panel."""
463
+ process = status.get("process", {})
464
+ health = status.get("health", {})
465
+ restart = status.get("restart_history", {})
466
+
467
+ content = "[bold cyan]Process Information[/bold cyan]\n"
468
+ content += f" Status: {process.get('status', 'unknown')}\n"
469
+ content += f" PID: {process.get('pid', 'N/A')}\n"
470
+ content += f" Port: {process.get('port', 'N/A')}\n"
471
+ content += f" Uptime: {process.get('uptime_seconds', 0):.1f}s\n"
472
+ content += f" Memory: {process.get('memory_mb', 0):.1f} MB\n"
473
+ content += f" CPU: {process.get('cpu_percent', 0):.1f}%\n\n"
474
+
475
+ if health:
476
+ content += "[bold green]Health Status[/bold green]\n"
477
+ content += f" Overall: {health.get('status', 'unknown')}\n"
478
+ content += f" HTTP: {'✓' if health.get('http_healthy') else '✗'}\n"
479
+ content += f" Process: {'✓' if health.get('process_healthy') else '✗'}\n"
480
+ content += (
481
+ f" Resources: {'✓' if health.get('resource_healthy') else '✗'}\n\n"
482
+ )
483
+
484
+ if restart:
485
+ content += "[bold yellow]Restart Statistics[/bold yellow]\n"
486
+ content += f" Total Restarts: {restart.get('total_restarts', 0)}\n"
487
+ content += f" Successful: {restart.get('successful_restarts', 0)}\n"
488
+ content += f" Failed: {restart.get('failed_restarts', 0)}\n"
489
+ content += f" Auto-restart: {'Enabled' if restart.get('auto_restart_enabled') else 'Disabled'}"
490
+
491
+ self.console.print(
492
+ Panel(
493
+ content,
494
+ title=f"Status: {status.get('deployment_id', 'Unknown')}",
495
+ border_style="cyan",
496
+ )
497
+ )
498
+
499
+ def _render_live_status(self, status: dict) -> Panel:
500
+ """Render status for live monitoring."""
501
+ process = status.get("process", {})
502
+ health = status.get("health", {})
503
+
504
+ content = Text()
505
+ content.append("Process Status\n", style="bold cyan")
506
+ content.append(f" PID: {process.get('pid', 'N/A')}\n")
507
+ content.append(f" Status: {process.get('status', 'unknown')}\n")
508
+ content.append(f" Uptime: {process.get('uptime_seconds', 0):.1f}s\n")
509
+ content.append(f" Memory: {process.get('memory_mb', 0):.1f} MB\n")
510
+ content.append(f" CPU: {process.get('cpu_percent', 0):.1f}%\n\n")
511
+
512
+ if health:
513
+ health_status = health.get("status", "unknown")
514
+ health_color = {
515
+ "healthy": "green",
516
+ "degraded": "yellow",
517
+ "unhealthy": "red",
518
+ }.get(health_status, "white")
519
+
520
+ content.append("Health Status\n", style="bold green")
521
+ content.append(" Overall: ", style="white")
522
+ content.append(f"{health_status.upper()}\n", style=health_color)
523
+ content.append(
524
+ f" Checks: HTTP={'✓' if health.get('http_healthy') else '✗'} "
525
+ f"Process={'✓' if health.get('process_healthy') else '✗'} "
526
+ f"Resources={'✓' if health.get('resource_healthy') else '✗'}\n"
527
+ )
528
+
529
+ return Panel(
530
+ content,
531
+ title=f"Monitoring: {status.get('deployment_id', 'Unknown')}",
532
+ border_style="cyan",
533
+ )
534
+
535
+
536
+ __all__ = ["LocalDeployCommand"]
@@ -336,6 +336,13 @@ def create_parser(
336
336
  except ImportError:
337
337
  pass
338
338
 
339
+ try:
340
+ from .local_deploy_parser import add_local_deploy_arguments
341
+
342
+ add_local_deploy_arguments(subparsers)
343
+ except ImportError:
344
+ pass
345
+
339
346
  try:
340
347
  from .mcp_parser import add_mcp_subparser
341
348