claude-mpm 0.3.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 (159) hide show
  1. claude_mpm/__init__.py +17 -0
  2. claude_mpm/__main__.py +14 -0
  3. claude_mpm/_version.py +32 -0
  4. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
  5. claude_mpm/agents/INSTRUCTIONS.md +375 -0
  6. claude_mpm/agents/__init__.py +118 -0
  7. claude_mpm/agents/agent_loader.py +621 -0
  8. claude_mpm/agents/agent_loader_integration.py +229 -0
  9. claude_mpm/agents/agents_metadata.py +204 -0
  10. claude_mpm/agents/base_agent.json +27 -0
  11. claude_mpm/agents/base_agent_loader.py +519 -0
  12. claude_mpm/agents/schema/agent_schema.json +160 -0
  13. claude_mpm/agents/system_agent_config.py +587 -0
  14. claude_mpm/agents/templates/__init__.py +101 -0
  15. claude_mpm/agents/templates/data_engineer_agent.json +46 -0
  16. claude_mpm/agents/templates/documentation_agent.json +45 -0
  17. claude_mpm/agents/templates/engineer_agent.json +49 -0
  18. claude_mpm/agents/templates/ops_agent.json +46 -0
  19. claude_mpm/agents/templates/qa_agent.json +45 -0
  20. claude_mpm/agents/templates/research_agent.json +49 -0
  21. claude_mpm/agents/templates/security_agent.json +46 -0
  22. claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
  23. claude_mpm/agents/templates/version_control_agent.json +46 -0
  24. claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
  25. claude_mpm/cli.py +655 -0
  26. claude_mpm/cli_main.py +13 -0
  27. claude_mpm/cli_module/__init__.py +15 -0
  28. claude_mpm/cli_module/args.py +222 -0
  29. claude_mpm/cli_module/commands.py +203 -0
  30. claude_mpm/cli_module/migration_example.py +183 -0
  31. claude_mpm/cli_module/refactoring_guide.md +253 -0
  32. claude_mpm/cli_old/__init__.py +1 -0
  33. claude_mpm/cli_old/ticket_cli.py +102 -0
  34. claude_mpm/config/__init__.py +5 -0
  35. claude_mpm/config/hook_config.py +42 -0
  36. claude_mpm/constants.py +150 -0
  37. claude_mpm/core/__init__.py +45 -0
  38. claude_mpm/core/agent_name_normalizer.py +248 -0
  39. claude_mpm/core/agent_registry.py +627 -0
  40. claude_mpm/core/agent_registry.py.bak +312 -0
  41. claude_mpm/core/agent_session_manager.py +273 -0
  42. claude_mpm/core/base_service.py +747 -0
  43. claude_mpm/core/base_service.py.bak +406 -0
  44. claude_mpm/core/config.py +334 -0
  45. claude_mpm/core/config_aliases.py +292 -0
  46. claude_mpm/core/container.py +347 -0
  47. claude_mpm/core/factories.py +281 -0
  48. claude_mpm/core/framework_loader.py +472 -0
  49. claude_mpm/core/injectable_service.py +206 -0
  50. claude_mpm/core/interfaces.py +539 -0
  51. claude_mpm/core/logger.py +468 -0
  52. claude_mpm/core/minimal_framework_loader.py +107 -0
  53. claude_mpm/core/mixins.py +150 -0
  54. claude_mpm/core/service_registry.py +299 -0
  55. claude_mpm/core/session_manager.py +190 -0
  56. claude_mpm/core/simple_runner.py +511 -0
  57. claude_mpm/core/tool_access_control.py +173 -0
  58. claude_mpm/hooks/README.md +243 -0
  59. claude_mpm/hooks/__init__.py +5 -0
  60. claude_mpm/hooks/base_hook.py +154 -0
  61. claude_mpm/hooks/builtin/__init__.py +1 -0
  62. claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
  63. claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
  64. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
  65. claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
  66. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
  67. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
  68. claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
  69. claude_mpm/hooks/hook_client.py +264 -0
  70. claude_mpm/hooks/hook_runner.py +370 -0
  71. claude_mpm/hooks/json_rpc_executor.py +259 -0
  72. claude_mpm/hooks/json_rpc_hook_client.py +319 -0
  73. claude_mpm/hooks/tool_call_interceptor.py +204 -0
  74. claude_mpm/init.py +246 -0
  75. claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
  76. claude_mpm/orchestration/__init__.py +6 -0
  77. claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
  78. claude_mpm/orchestration/archive/factory.py +215 -0
  79. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
  80. claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
  81. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
  82. claude_mpm/orchestration/archive/orchestrator.py +501 -0
  83. claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
  84. claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
  85. claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
  86. claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
  87. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
  88. claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
  89. claude_mpm/scripts/__init__.py +1 -0
  90. claude_mpm/scripts/ticket.py +269 -0
  91. claude_mpm/services/__init__.py +10 -0
  92. claude_mpm/services/agent_deployment.py +955 -0
  93. claude_mpm/services/agent_lifecycle_manager.py +948 -0
  94. claude_mpm/services/agent_management_service.py +596 -0
  95. claude_mpm/services/agent_modification_tracker.py +841 -0
  96. claude_mpm/services/agent_profile_loader.py +606 -0
  97. claude_mpm/services/agent_registry.py +677 -0
  98. claude_mpm/services/base_agent_manager.py +380 -0
  99. claude_mpm/services/framework_agent_loader.py +337 -0
  100. claude_mpm/services/framework_claude_md_generator/README.md +92 -0
  101. claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
  102. claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
  103. claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
  104. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
  105. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
  106. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
  107. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
  108. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
  109. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
  110. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
  111. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
  112. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
  113. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
  114. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
  115. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
  116. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
  117. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
  118. claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
  119. claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
  120. claude_mpm/services/framework_claude_md_generator.py +621 -0
  121. claude_mpm/services/hook_service.py +388 -0
  122. claude_mpm/services/hook_service_manager.py +223 -0
  123. claude_mpm/services/json_rpc_hook_manager.py +92 -0
  124. claude_mpm/services/parent_directory_manager/README.md +83 -0
  125. claude_mpm/services/parent_directory_manager/__init__.py +577 -0
  126. claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
  127. claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
  128. claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
  129. claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
  130. claude_mpm/services/parent_directory_manager/operations.py +186 -0
  131. claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
  132. claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
  133. claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
  134. claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
  135. claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
  136. claude_mpm/services/shared_prompt_cache.py +819 -0
  137. claude_mpm/services/ticket_manager.py +213 -0
  138. claude_mpm/services/ticket_manager_di.py +318 -0
  139. claude_mpm/services/ticketing_service_original.py +508 -0
  140. claude_mpm/services/version_control/VERSION +1 -0
  141. claude_mpm/services/version_control/__init__.py +70 -0
  142. claude_mpm/services/version_control/branch_strategy.py +670 -0
  143. claude_mpm/services/version_control/conflict_resolution.py +744 -0
  144. claude_mpm/services/version_control/git_operations.py +784 -0
  145. claude_mpm/services/version_control/semantic_versioning.py +703 -0
  146. claude_mpm/ui/__init__.py +1 -0
  147. claude_mpm/ui/rich_terminal_ui.py +295 -0
  148. claude_mpm/ui/terminal_ui.py +328 -0
  149. claude_mpm/utils/__init__.py +16 -0
  150. claude_mpm/utils/config_manager.py +468 -0
  151. claude_mpm/utils/import_migration_example.py +80 -0
  152. claude_mpm/utils/imports.py +182 -0
  153. claude_mpm/utils/path_operations.py +357 -0
  154. claude_mpm/utils/paths.py +289 -0
  155. claude_mpm-0.3.0.dist-info/METADATA +290 -0
  156. claude_mpm-0.3.0.dist-info/RECORD +159 -0
  157. claude_mpm-0.3.0.dist-info/WHEEL +5 -0
  158. claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
  159. claude_mpm-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,406 @@
1
+ """
2
+ Base service class for Claude PM Framework services.
3
+
4
+ Provides common functionality including:
5
+ - Lifecycle management (start, stop, health checks)
6
+ - Configuration management
7
+ - Logging
8
+ - Metrics collection
9
+ - Error handling and retry logic
10
+ - Service discovery and registration
11
+ """
12
+
13
+ import asyncio
14
+ import logging
15
+ import signal
16
+ import sys
17
+ import os
18
+ from abc import ABC, abstractmethod
19
+ from dataclasses import dataclass, field
20
+ from datetime import datetime
21
+ from typing import Any, Dict, List, Optional, Union
22
+ from pathlib import Path
23
+ import json
24
+
25
+ from .config import Config
26
+ from .logger import setup_logging
27
+ from .mixins import LoggerMixin
28
+
29
+
30
+ @dataclass
31
+ class ServiceHealth:
32
+ """Service health status information."""
33
+
34
+ status: str # healthy, degraded, unhealthy, unknown
35
+ message: str
36
+ timestamp: str
37
+ metrics: Dict[str, Any] = field(default_factory=dict)
38
+ checks: Dict[str, bool] = field(default_factory=dict)
39
+
40
+
41
+ @dataclass
42
+ class ServiceMetrics:
43
+ """Service metrics collection."""
44
+
45
+ requests_total: int = 0
46
+ requests_failed: int = 0
47
+ response_time_avg: float = 0.0
48
+ uptime_seconds: int = 0
49
+ memory_usage_mb: float = 0.0
50
+ custom_metrics: Dict[str, Any] = field(default_factory=dict)
51
+
52
+
53
+ class BaseService(LoggerMixin, ABC):
54
+ """
55
+ Abstract base class for all Claude PM services.
56
+
57
+ Provides common infrastructure for service lifecycle management,
58
+ health monitoring, configuration, logging, and error handling.
59
+ """
60
+
61
+ def __init__(
62
+ self, name: str, config: Optional[Dict[str, Any]] = None, config_path: Optional[Path] = None
63
+ ):
64
+ """
65
+ Initialize the base service.
66
+
67
+ Args:
68
+ name: Service name for identification
69
+ config: Optional configuration dictionary
70
+ config_path: Optional path to configuration file
71
+ """
72
+ self.name = name
73
+ self.config = Config(config or {}, config_path)
74
+
75
+ # Set custom logger name based on service name
76
+ self._logger_name = f"{self.__class__.__module__}.{self.__class__.__name__}.{name}"
77
+
78
+ # Service state
79
+ self._running = False
80
+ self._start_time: Optional[datetime] = None
81
+ self._stop_event = asyncio.Event()
82
+
83
+ # Health and metrics
84
+ self._health = ServiceHealth(
85
+ status="unknown", message="Service not started", timestamp=datetime.now().isoformat()
86
+ )
87
+ self._metrics = ServiceMetrics()
88
+
89
+ # Background tasks
90
+ self._background_tasks: List[asyncio.Task] = []
91
+
92
+ # Check for quiet mode environment variable
93
+ default_log_level = "INFO"
94
+ if os.getenv('CLAUDE_PM_QUIET_MODE') == 'true':
95
+ default_log_level = "WARNING"
96
+ # Set logger level if needed
97
+ self.logger.setLevel(logging.WARNING)
98
+
99
+ # Only log if not in quiet mode
100
+ if not os.environ.get('CLAUDE_PM_QUIET_MODE', '').lower() == 'true':
101
+ self.logger.info(f"Initialized {self.name} service")
102
+
103
+ @property
104
+ def running(self) -> bool:
105
+ """Check if service is currently running."""
106
+ return self._running
107
+
108
+ @property
109
+ def uptime(self) -> Optional[float]:
110
+ """Get service uptime in seconds."""
111
+ if self._start_time and self._running:
112
+ return (datetime.now() - self._start_time).total_seconds()
113
+ return None
114
+
115
+ @property
116
+ def health(self) -> ServiceHealth:
117
+ """Get current service health status."""
118
+ return self._health
119
+
120
+ @property
121
+ def metrics(self) -> ServiceMetrics:
122
+ """Get current service metrics."""
123
+ if self.uptime:
124
+ self._metrics.uptime_seconds = int(self.uptime)
125
+ return self._metrics
126
+
127
+ async def start(self) -> None:
128
+ """Start the service."""
129
+ if self._running:
130
+ self.logger.warning(f"Service {self.name} is already running")
131
+ return
132
+
133
+ self.logger.info(f"Starting {self.name} service...")
134
+
135
+ try:
136
+ # Setup signal handlers
137
+ self._setup_signal_handlers()
138
+
139
+ # Initialize service
140
+ await self._initialize()
141
+
142
+ # Start background tasks
143
+ await self._start_background_tasks()
144
+
145
+ # Mark as running
146
+ self._running = True
147
+ self._start_time = datetime.now()
148
+
149
+ # Update health status
150
+ self._health = ServiceHealth(
151
+ status="healthy",
152
+ message="Service started successfully",
153
+ timestamp=datetime.now().isoformat(),
154
+ checks={"startup": True},
155
+ )
156
+
157
+ self.logger.info(f"Service {self.name} started successfully")
158
+
159
+ except Exception as e:
160
+ self.logger.error(f"Failed to start service {self.name}: {e}")
161
+ self._health = ServiceHealth(
162
+ status="unhealthy",
163
+ message=f"Startup failed: {str(e)}",
164
+ timestamp=datetime.now().isoformat(),
165
+ checks={"startup": False},
166
+ )
167
+ raise
168
+
169
+ async def stop(self) -> None:
170
+ """Stop the service gracefully."""
171
+ if not self._running:
172
+ self.logger.warning(f"Service {self.name} is not running")
173
+ return
174
+
175
+ self.logger.info(f"Stopping {self.name} service...")
176
+
177
+ try:
178
+ # Signal stop to background tasks
179
+ self._stop_event.set()
180
+
181
+ # Cancel background tasks
182
+ for task in self._background_tasks:
183
+ if not task.done():
184
+ task.cancel()
185
+
186
+ # Wait for tasks to complete
187
+ if self._background_tasks:
188
+ await asyncio.gather(*self._background_tasks, return_exceptions=True)
189
+
190
+ # Cleanup service
191
+ await self._cleanup()
192
+
193
+ # Mark as stopped
194
+ self._running = False
195
+
196
+ # Update health status
197
+ self._health = ServiceHealth(
198
+ status="unknown",
199
+ message="Service stopped",
200
+ timestamp=datetime.now().isoformat(),
201
+ checks={"running": False},
202
+ )
203
+
204
+ self.logger.info(f"Service {self.name} stopped successfully")
205
+
206
+ except Exception as e:
207
+ self.logger.error(f"Error stopping service {self.name}: {e}")
208
+ raise
209
+
210
+ async def restart(self) -> None:
211
+ """Restart the service."""
212
+ self.logger.info(f"Restarting {self.name} service...")
213
+ await self.stop()
214
+ await self.start()
215
+
216
+ async def health_check(self) -> ServiceHealth:
217
+ """
218
+ Perform comprehensive health check.
219
+
220
+ Returns:
221
+ ServiceHealth object with current status
222
+ """
223
+ try:
224
+ checks = {}
225
+
226
+ # Basic running check
227
+ checks["running"] = self._running
228
+
229
+ # Custom health checks
230
+ custom_checks = await self._health_check()
231
+ checks.update(custom_checks)
232
+
233
+ # Determine overall status
234
+ if not checks["running"]:
235
+ status = "unhealthy"
236
+ message = "Service is not running"
237
+ elif all(checks.values()):
238
+ status = "healthy"
239
+ message = "All health checks passed"
240
+ elif any(checks.values()):
241
+ status = "degraded"
242
+ message = "Some health checks failed"
243
+ else:
244
+ status = "unhealthy"
245
+ message = "Multiple health checks failed"
246
+
247
+ # Update health status
248
+ self._health = ServiceHealth(
249
+ status=status,
250
+ message=message,
251
+ timestamp=datetime.now().isoformat(),
252
+ checks=checks,
253
+ metrics={
254
+ "uptime": self.uptime,
255
+ "requests_total": self._metrics.requests_total,
256
+ "requests_failed": self._metrics.requests_failed,
257
+ },
258
+ )
259
+
260
+ return self._health
261
+
262
+ except Exception as e:
263
+ self.logger.error(f"Health check failed for {self.name}: {e}")
264
+ self._health = ServiceHealth(
265
+ status="unhealthy",
266
+ message=f"Health check error: {str(e)}",
267
+ timestamp=datetime.now().isoformat(),
268
+ checks={"health_check_error": True},
269
+ )
270
+ return self._health
271
+
272
+ def update_metrics(self, **kwargs) -> None:
273
+ """Update service metrics."""
274
+ for key, value in kwargs.items():
275
+ if hasattr(self._metrics, key):
276
+ setattr(self._metrics, key, value)
277
+ else:
278
+ self._metrics.custom_metrics[key] = value
279
+
280
+ def get_config(self, key: str, default: Any = None) -> Any:
281
+ """Get configuration value."""
282
+ return self.config.get(key, default)
283
+
284
+ def _setup_signal_handlers(self) -> None:
285
+ """Setup signal handlers for graceful shutdown."""
286
+
287
+ def signal_handler(signum, frame):
288
+ self.logger.info(f"Received signal {signum}, initiating graceful shutdown...")
289
+ asyncio.create_task(self.stop())
290
+
291
+ signal.signal(signal.SIGINT, signal_handler)
292
+ signal.signal(signal.SIGTERM, signal_handler)
293
+
294
+ async def _start_background_tasks(self) -> None:
295
+ """Start background tasks."""
296
+ # Health check task
297
+ if self.get_config("enable_health_monitoring", True):
298
+ interval = self.get_config("health_check_interval", 30)
299
+ task = asyncio.create_task(self._health_monitor_task(interval))
300
+ self._background_tasks.append(task)
301
+
302
+ # Metrics collection task
303
+ if self.get_config("enable_metrics", True):
304
+ interval = self.get_config("metrics_interval", 60)
305
+ task = asyncio.create_task(self._metrics_task(interval))
306
+ self._background_tasks.append(task)
307
+
308
+ # Custom background tasks
309
+ custom_tasks = await self._start_custom_tasks()
310
+ if custom_tasks:
311
+ self._background_tasks.extend(custom_tasks)
312
+
313
+ async def _health_monitor_task(self, interval: int) -> None:
314
+ """Background task for periodic health monitoring."""
315
+ while not self._stop_event.is_set():
316
+ try:
317
+ await self.health_check()
318
+ await asyncio.sleep(interval)
319
+ except asyncio.CancelledError:
320
+ break
321
+ except Exception as e:
322
+ self.logger.error(f"Health monitor task error: {e}")
323
+ await asyncio.sleep(interval)
324
+
325
+ async def _metrics_task(self, interval: int) -> None:
326
+ """Background task for metrics collection."""
327
+ while not self._stop_event.is_set():
328
+ try:
329
+ await self._collect_metrics()
330
+ await asyncio.sleep(interval)
331
+ except asyncio.CancelledError:
332
+ break
333
+ except Exception as e:
334
+ self.logger.error(f"Metrics task error: {e}")
335
+ await asyncio.sleep(interval)
336
+
337
+ async def _collect_metrics(self) -> None:
338
+ """Collect service metrics."""
339
+ try:
340
+ # Update uptime
341
+ if self.uptime:
342
+ self._metrics.uptime_seconds = int(self.uptime)
343
+
344
+ # Memory usage (basic implementation)
345
+ import psutil
346
+
347
+ process = psutil.Process()
348
+ self._metrics.memory_usage_mb = process.memory_info().rss / 1024 / 1024
349
+
350
+ # Custom metrics collection
351
+ await self._collect_custom_metrics()
352
+
353
+ except Exception as e:
354
+ self.logger.warning(f"Failed to collect metrics: {e}")
355
+
356
+ # Abstract methods to be implemented by subclasses
357
+
358
+ @abstractmethod
359
+ async def _initialize(self) -> None:
360
+ """Initialize the service. Must be implemented by subclasses."""
361
+ pass
362
+
363
+ @abstractmethod
364
+ async def _cleanup(self) -> None:
365
+ """Cleanup service resources. Must be implemented by subclasses."""
366
+ pass
367
+
368
+ async def _health_check(self) -> Dict[str, bool]:
369
+ """
370
+ Perform custom health checks. Override in subclasses.
371
+
372
+ Returns:
373
+ Dictionary of check name -> success boolean
374
+ """
375
+ return {}
376
+
377
+ async def _start_custom_tasks(self) -> Optional[List[asyncio.Task]]:
378
+ """
379
+ Start custom background tasks. Override in subclasses.
380
+
381
+ Returns:
382
+ List of asyncio tasks or None
383
+ """
384
+ return None
385
+
386
+ async def _collect_custom_metrics(self) -> None:
387
+ """Collect custom metrics. Override in subclasses."""
388
+ pass
389
+
390
+ # Utility methods
391
+
392
+ async def run_forever(self) -> None:
393
+ """Run the service until stopped."""
394
+ await self.start()
395
+ try:
396
+ # Wait for stop signal
397
+ while self._running:
398
+ await asyncio.sleep(1)
399
+ except KeyboardInterrupt:
400
+ self.logger.info("Received keyboard interrupt")
401
+ finally:
402
+ await self.stop()
403
+
404
+ def __repr__(self) -> str:
405
+ """String representation of the service."""
406
+ return f"<{self.__class__.__name__}(name='{self.name}', running={self._running})>"