claude-mpm 4.1.4__py3-none-any.whl → 4.1.6__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 (81) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/templates/research.json +39 -13
  3. claude_mpm/cli/__init__.py +2 -0
  4. claude_mpm/cli/commands/__init__.py +2 -0
  5. claude_mpm/cli/commands/configure.py +1221 -0
  6. claude_mpm/cli/commands/configure_tui.py +1921 -0
  7. claude_mpm/cli/commands/tickets.py +365 -784
  8. claude_mpm/cli/parsers/base_parser.py +7 -0
  9. claude_mpm/cli/parsers/configure_parser.py +119 -0
  10. claude_mpm/cli/startup_logging.py +39 -12
  11. claude_mpm/constants.py +1 -0
  12. claude_mpm/core/output_style_manager.py +24 -0
  13. claude_mpm/core/socketio_pool.py +35 -3
  14. claude_mpm/core/unified_agent_registry.py +46 -15
  15. claude_mpm/dashboard/static/css/connection-status.css +370 -0
  16. claude_mpm/dashboard/static/js/components/connection-debug.js +654 -0
  17. claude_mpm/dashboard/static/js/connection-manager.js +536 -0
  18. claude_mpm/dashboard/templates/index.html +11 -0
  19. claude_mpm/hooks/claude_hooks/services/__init__.py +3 -1
  20. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +190 -0
  21. claude_mpm/services/agents/deployment/agent_discovery_service.py +12 -3
  22. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +172 -233
  23. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +575 -0
  24. claude_mpm/services/agents/deployment/agent_operation_service.py +573 -0
  25. claude_mpm/services/agents/deployment/agent_record_service.py +419 -0
  26. claude_mpm/services/agents/deployment/agent_state_service.py +381 -0
  27. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +4 -2
  28. claude_mpm/services/diagnostics/checks/__init__.py +2 -0
  29. claude_mpm/services/diagnostics/checks/instructions_check.py +418 -0
  30. claude_mpm/services/diagnostics/diagnostic_runner.py +15 -2
  31. claude_mpm/services/event_bus/direct_relay.py +173 -0
  32. claude_mpm/services/infrastructure/__init__.py +31 -5
  33. claude_mpm/services/infrastructure/monitoring/__init__.py +43 -0
  34. claude_mpm/services/infrastructure/monitoring/aggregator.py +437 -0
  35. claude_mpm/services/infrastructure/monitoring/base.py +130 -0
  36. claude_mpm/services/infrastructure/monitoring/legacy.py +203 -0
  37. claude_mpm/services/infrastructure/monitoring/network.py +218 -0
  38. claude_mpm/services/infrastructure/monitoring/process.py +342 -0
  39. claude_mpm/services/infrastructure/monitoring/resources.py +243 -0
  40. claude_mpm/services/infrastructure/monitoring/service.py +367 -0
  41. claude_mpm/services/infrastructure/monitoring.py +67 -1030
  42. claude_mpm/services/project/analyzer.py +13 -4
  43. claude_mpm/services/project/analyzer_refactored.py +450 -0
  44. claude_mpm/services/project/analyzer_v2.py +566 -0
  45. claude_mpm/services/project/architecture_analyzer.py +461 -0
  46. claude_mpm/services/project/dependency_analyzer.py +462 -0
  47. claude_mpm/services/project/language_analyzer.py +265 -0
  48. claude_mpm/services/project/metrics_collector.py +410 -0
  49. claude_mpm/services/socketio/handlers/connection_handler.py +345 -0
  50. claude_mpm/services/socketio/server/broadcaster.py +32 -1
  51. claude_mpm/services/socketio/server/connection_manager.py +516 -0
  52. claude_mpm/services/socketio/server/core.py +63 -0
  53. claude_mpm/services/socketio/server/eventbus_integration.py +20 -9
  54. claude_mpm/services/socketio/server/main.py +27 -1
  55. claude_mpm/services/ticket_manager.py +5 -1
  56. claude_mpm/services/ticket_services/__init__.py +26 -0
  57. claude_mpm/services/ticket_services/crud_service.py +328 -0
  58. claude_mpm/services/ticket_services/formatter_service.py +290 -0
  59. claude_mpm/services/ticket_services/search_service.py +324 -0
  60. claude_mpm/services/ticket_services/validation_service.py +303 -0
  61. claude_mpm/services/ticket_services/workflow_service.py +244 -0
  62. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/METADATA +3 -1
  63. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/RECORD +67 -46
  64. claude_mpm/agents/OUTPUT_STYLE.md +0 -73
  65. claude_mpm/agents/backups/INSTRUCTIONS.md +0 -352
  66. claude_mpm/agents/templates/OPTIMIZATION_REPORT.md +0 -156
  67. claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +0 -79
  68. claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +0 -68
  69. claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +0 -77
  70. claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +0 -78
  71. claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +0 -67
  72. claude_mpm/agents/templates/backup/research_agent_2025011_234551.json +0 -88
  73. claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +0 -72
  74. claude_mpm/agents/templates/backup/research_memory_efficient.json +0 -88
  75. claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +0 -78
  76. claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +0 -62
  77. claude_mpm/agents/templates/vercel_ops_instructions.md +0 -582
  78. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/WHEEL +0 -0
  79. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/entry_points.txt +0 -0
  80. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/licenses/LICENSE +0 -0
  81. {claude_mpm-4.1.4.dist-info → claude_mpm-4.1.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,303 @@
1
+ """
2
+ Input validation service for tickets.
3
+
4
+ WHY: Centralizes all validation logic to ensure data integrity and
5
+ provide consistent error messages across the ticket system.
6
+
7
+ DESIGN DECISIONS:
8
+ - Returns validation results with detailed error messages
9
+ - Validates ticket IDs, types, statuses, priorities
10
+ - Handles pagination parameter validation
11
+ - Provides sanitization for user inputs
12
+ """
13
+
14
+ from typing import Any, Dict, List, Optional, Tuple
15
+
16
+
17
+ class TicketValidationService:
18
+ """Service for validating ticket inputs."""
19
+
20
+ # Valid ticket types
21
+ VALID_TYPES = ["task", "issue", "epic", "bug", "feature", "story"]
22
+
23
+ # Valid ticket statuses
24
+ VALID_STATUSES = [
25
+ "open",
26
+ "in_progress",
27
+ "ready",
28
+ "tested",
29
+ "done",
30
+ "waiting",
31
+ "closed",
32
+ "blocked",
33
+ "all",
34
+ ]
35
+
36
+ # Valid priorities
37
+ VALID_PRIORITIES = ["low", "medium", "high", "critical"]
38
+
39
+ # Valid workflow states
40
+ VALID_WORKFLOW_STATES = [
41
+ "todo",
42
+ "in_progress",
43
+ "ready",
44
+ "tested",
45
+ "done",
46
+ "blocked",
47
+ ]
48
+
49
+ def validate_ticket_id(self, ticket_id: Any) -> Tuple[bool, Optional[str]]:
50
+ """
51
+ Validate a ticket ID.
52
+
53
+ Returns:
54
+ Tuple of (is_valid, error_message)
55
+ """
56
+ if not ticket_id:
57
+ return False, "No ticket ID provided"
58
+
59
+ if not isinstance(ticket_id, str):
60
+ return False, "Ticket ID must be a string"
61
+
62
+ if len(ticket_id) < 3:
63
+ return False, "Invalid ticket ID format"
64
+
65
+ return True, None
66
+
67
+ def validate_ticket_type(self, ticket_type: str) -> Tuple[bool, Optional[str]]:
68
+ """
69
+ Validate a ticket type.
70
+
71
+ Returns:
72
+ Tuple of (is_valid, error_message)
73
+ """
74
+ if not ticket_type:
75
+ return True, None # Type is optional, default will be used
76
+
77
+ if ticket_type not in self.VALID_TYPES:
78
+ return (
79
+ False,
80
+ f"Invalid ticket type: {ticket_type}. Valid types: {', '.join(self.VALID_TYPES)}",
81
+ )
82
+
83
+ return True, None
84
+
85
+ def validate_status(self, status: str) -> Tuple[bool, Optional[str]]:
86
+ """
87
+ Validate a ticket status.
88
+
89
+ Returns:
90
+ Tuple of (is_valid, error_message)
91
+ """
92
+ if not status:
93
+ return True, None # Status is optional
94
+
95
+ if status not in self.VALID_STATUSES:
96
+ return (
97
+ False,
98
+ f"Invalid status: {status}. Valid statuses: {', '.join(self.VALID_STATUSES)}",
99
+ )
100
+
101
+ return True, None
102
+
103
+ def validate_priority(self, priority: str) -> Tuple[bool, Optional[str]]:
104
+ """
105
+ Validate a ticket priority.
106
+
107
+ Returns:
108
+ Tuple of (is_valid, error_message)
109
+ """
110
+ if not priority:
111
+ return True, None # Priority is optional, default will be used
112
+
113
+ if priority not in self.VALID_PRIORITIES:
114
+ return (
115
+ False,
116
+ f"Invalid priority: {priority}. Valid priorities: {', '.join(self.VALID_PRIORITIES)}",
117
+ )
118
+
119
+ return True, None
120
+
121
+ def validate_workflow_state(self, state: str) -> Tuple[bool, Optional[str]]:
122
+ """
123
+ Validate a workflow state.
124
+
125
+ Returns:
126
+ Tuple of (is_valid, error_message)
127
+ """
128
+ if not state:
129
+ return False, "No workflow state provided"
130
+
131
+ if state not in self.VALID_WORKFLOW_STATES:
132
+ return (
133
+ False,
134
+ f"Invalid workflow state: {state}. Valid states: {', '.join(self.VALID_WORKFLOW_STATES)}",
135
+ )
136
+
137
+ return True, None
138
+
139
+ def validate_pagination(
140
+ self, page: int, page_size: int
141
+ ) -> Tuple[bool, Optional[str]]:
142
+ """
143
+ Validate pagination parameters.
144
+
145
+ Returns:
146
+ Tuple of (is_valid, error_message)
147
+ """
148
+ if page < 1:
149
+ return False, "Page number must be 1 or greater"
150
+
151
+ if page_size < 1:
152
+ return False, "Page size must be 1 or greater"
153
+
154
+ if page_size > 100:
155
+ return False, "Page size cannot exceed 100"
156
+
157
+ return True, None
158
+
159
+ def validate_create_params(
160
+ self, params: Dict[str, Any]
161
+ ) -> Tuple[bool, Optional[str]]:
162
+ """
163
+ Validate parameters for ticket creation.
164
+
165
+ Returns:
166
+ Tuple of (is_valid, error_message)
167
+ """
168
+ # Title is required
169
+ if not params.get("title"):
170
+ return False, "Title is required for ticket creation"
171
+
172
+ if len(params["title"]) < 3:
173
+ return False, "Title must be at least 3 characters long"
174
+
175
+ if len(params["title"]) > 200:
176
+ return False, "Title cannot exceed 200 characters"
177
+
178
+ # Validate optional fields if provided
179
+ if "type" in params:
180
+ valid, error = self.validate_ticket_type(params["type"])
181
+ if not valid:
182
+ return False, error
183
+
184
+ if "priority" in params:
185
+ valid, error = self.validate_priority(params["priority"])
186
+ if not valid:
187
+ return False, error
188
+
189
+ # Validate parent references
190
+ if params.get("parent_epic"):
191
+ valid, error = self.validate_ticket_id(params["parent_epic"])
192
+ if not valid:
193
+ return False, f"Invalid parent epic: {error}"
194
+
195
+ if params.get("parent_issue"):
196
+ valid, error = self.validate_ticket_id(params["parent_issue"])
197
+ if not valid:
198
+ return False, f"Invalid parent issue: {error}"
199
+
200
+ return True, None
201
+
202
+ def validate_update_params(
203
+ self, params: Dict[str, Any]
204
+ ) -> Tuple[bool, Optional[str]]:
205
+ """
206
+ Validate parameters for ticket update.
207
+
208
+ Returns:
209
+ Tuple of (is_valid, error_message)
210
+ """
211
+ # At least one update field must be provided
212
+ update_fields = ["status", "priority", "description", "tags", "assign"]
213
+ if not any(field in params for field in update_fields):
214
+ return False, "No update fields specified"
215
+
216
+ # Validate status if provided
217
+ if "status" in params:
218
+ valid, error = self.validate_status(params["status"])
219
+ if not valid:
220
+ return False, error
221
+
222
+ # Validate priority if provided
223
+ if "priority" in params:
224
+ valid, error = self.validate_priority(params["priority"])
225
+ if not valid:
226
+ return False, error
227
+
228
+ return True, None
229
+
230
+ def sanitize_tags(self, tags: Any) -> List[str]:
231
+ """
232
+ Sanitize and parse tags input.
233
+
234
+ Returns:
235
+ List of sanitized tags
236
+ """
237
+ if not tags:
238
+ return []
239
+
240
+ if isinstance(tags, str):
241
+ # Split comma-separated tags
242
+ tag_list = [tag.strip() for tag in tags.split(",")]
243
+ elif isinstance(tags, list):
244
+ tag_list = [str(tag).strip() for tag in tags]
245
+ else:
246
+ return []
247
+
248
+ # Remove empty tags and duplicates
249
+ return list(filter(None, dict.fromkeys(tag_list)))
250
+
251
+ def sanitize_description(self, description: Any) -> str:
252
+ """
253
+ Sanitize description input.
254
+
255
+ Returns:
256
+ Sanitized description string
257
+ """
258
+ if not description:
259
+ return ""
260
+
261
+ if isinstance(description, list):
262
+ # Join list elements with spaces
263
+ return " ".join(str(item) for item in description)
264
+
265
+ return str(description).strip()
266
+
267
+ def validate_search_query(self, query: str) -> Tuple[bool, Optional[str]]:
268
+ """
269
+ Validate a search query.
270
+
271
+ Returns:
272
+ Tuple of (is_valid, error_message)
273
+ """
274
+ if not query:
275
+ return False, "Search query cannot be empty"
276
+
277
+ if len(query) < 2:
278
+ return False, "Search query must be at least 2 characters"
279
+
280
+ if len(query) > 100:
281
+ return False, "Search query cannot exceed 100 characters"
282
+
283
+ return True, None
284
+
285
+ def validate_comment(self, comment: Any) -> Tuple[bool, Optional[str]]:
286
+ """
287
+ Validate a comment.
288
+
289
+ Returns:
290
+ Tuple of (is_valid, error_message)
291
+ """
292
+ if not comment:
293
+ return False, "Comment cannot be empty"
294
+
295
+ comment_str = self.sanitize_description(comment)
296
+
297
+ if len(comment_str) < 1:
298
+ return False, "Comment cannot be empty"
299
+
300
+ if len(comment_str) > 5000:
301
+ return False, "Comment cannot exceed 5000 characters"
302
+
303
+ return True, None
@@ -0,0 +1,244 @@
1
+ """
2
+ Workflow service for ticket state transitions.
3
+
4
+ WHY: Manages ticket workflow states and transitions, ensuring valid
5
+ state changes and maintaining workflow integrity.
6
+
7
+ DESIGN DECISIONS:
8
+ - Enforces valid state transitions
9
+ - Handles workflow-specific operations (comments, notifications)
10
+ - Provides workflow history tracking
11
+ - Abstracts aitrackdown workflow commands
12
+ """
13
+
14
+ import subprocess
15
+ from typing import Any, Dict, List, Optional, Tuple
16
+
17
+ from ...core.logger import get_logger
18
+
19
+
20
+ class TicketWorkflowService:
21
+ """Service for managing ticket workflow states."""
22
+
23
+ # Valid workflow transitions
24
+ WORKFLOW_TRANSITIONS = {
25
+ "todo": ["in_progress", "blocked"],
26
+ "in_progress": ["ready", "blocked", "todo"],
27
+ "ready": ["tested", "in_progress", "blocked"],
28
+ "tested": ["done", "ready", "blocked"],
29
+ "done": ["tested", "ready"], # Can reopen
30
+ "blocked": ["todo", "in_progress", "ready"],
31
+ }
32
+
33
+ # Status to workflow state mapping
34
+ STATUS_TO_WORKFLOW = {
35
+ "open": "todo",
36
+ "in_progress": "in_progress",
37
+ "ready": "ready",
38
+ "tested": "tested",
39
+ "done": "done",
40
+ "closed": "done",
41
+ "blocked": "blocked",
42
+ "waiting": "blocked",
43
+ }
44
+
45
+ def __init__(self):
46
+ """Initialize the workflow service."""
47
+ self.logger = get_logger("services.ticket_workflow")
48
+
49
+ def transition_ticket(
50
+ self,
51
+ ticket_id: str,
52
+ new_state: str,
53
+ comment: Optional[str] = None,
54
+ force: bool = False,
55
+ ) -> Dict[str, Any]:
56
+ """
57
+ Transition a ticket to a new workflow state.
58
+
59
+ Args:
60
+ ticket_id: ID of the ticket
61
+ new_state: Target workflow state
62
+ comment: Optional comment for the transition
63
+ force: Force transition even if not normally allowed
64
+
65
+ Returns:
66
+ Dict with success status and message
67
+ """
68
+ try:
69
+ # Validate the transition if not forced
70
+ if not force:
71
+ valid, error = self.validate_transition(ticket_id, new_state)
72
+ if not valid:
73
+ return {"success": False, "error": error}
74
+
75
+ # Use aitrackdown CLI for the transition
76
+ result = self._transition_via_aitrackdown(ticket_id, new_state, comment)
77
+
78
+ if result["success"]:
79
+ # Log successful transition
80
+ self.logger.info(f"Transitioned {ticket_id} to {new_state}")
81
+
82
+ return result
83
+
84
+ except Exception as e:
85
+ self.logger.error(f"Error transitioning ticket {ticket_id}: {e}")
86
+ return {"success": False, "error": str(e)}
87
+
88
+ def _transition_via_aitrackdown(
89
+ self, ticket_id: str, state: str, comment: Optional[str]
90
+ ) -> Dict[str, Any]:
91
+ """Transition ticket using aitrackdown CLI."""
92
+ try:
93
+ cmd = ["aitrackdown", "transition", ticket_id, state]
94
+
95
+ if comment:
96
+ cmd.extend(["--comment", comment])
97
+
98
+ subprocess.run(cmd, check=True, capture_output=True, text=True)
99
+
100
+ return {
101
+ "success": True,
102
+ "message": f"Updated workflow state for {ticket_id} to: {state}",
103
+ }
104
+ except subprocess.CalledProcessError as e:
105
+ self.logger.error(f"Failed to transition via CLI: {e}")
106
+ return {
107
+ "success": False,
108
+ "error": f"Failed to update workflow state for ticket: {ticket_id}",
109
+ }
110
+
111
+ def validate_transition(
112
+ self, ticket_id: str, new_state: str
113
+ ) -> Tuple[bool, Optional[str]]:
114
+ """
115
+ Validate if a workflow transition is allowed.
116
+
117
+ Returns:
118
+ Tuple of (is_valid, error_message)
119
+ """
120
+ # For now, we assume all transitions are valid since we don't
121
+ # have access to current state without fetching the ticket
122
+ # In a real implementation, this would check current state
123
+
124
+ if new_state not in self.WORKFLOW_TRANSITIONS:
125
+ return False, f"Invalid workflow state: {new_state}"
126
+
127
+ # TODO: Fetch current state and validate transition
128
+ # current_state = self._get_current_state(ticket_id)
129
+ # if new_state not in self.WORKFLOW_TRANSITIONS.get(current_state, []):
130
+ # return False, f"Cannot transition from {current_state} to {new_state}"
131
+
132
+ return True, None
133
+
134
+ def add_comment(self, ticket_id: str, comment: str) -> Dict[str, Any]:
135
+ """
136
+ Add a comment to a ticket.
137
+
138
+ Args:
139
+ ticket_id: ID of the ticket
140
+ comment: Comment text
141
+
142
+ Returns:
143
+ Dict with success status and message
144
+ """
145
+ try:
146
+ cmd = ["aitrackdown", "comment", ticket_id, comment]
147
+
148
+ subprocess.run(cmd, check=True, capture_output=True, text=True)
149
+
150
+ return {"success": True, "message": f"Added comment to ticket: {ticket_id}"}
151
+ except subprocess.CalledProcessError as e:
152
+ self.logger.error(f"Failed to add comment: {e}")
153
+ return {
154
+ "success": False,
155
+ "error": f"Failed to add comment to ticket: {ticket_id}",
156
+ }
157
+
158
+ def get_workflow_states(self) -> List[str]:
159
+ """
160
+ Get list of all valid workflow states.
161
+
162
+ Returns:
163
+ List of workflow state names
164
+ """
165
+ return list(self.WORKFLOW_TRANSITIONS.keys())
166
+
167
+ def get_valid_transitions(self, current_state: str) -> List[str]:
168
+ """
169
+ Get valid transitions from a given state.
170
+
171
+ Args:
172
+ current_state: Current workflow state
173
+
174
+ Returns:
175
+ List of valid target states
176
+ """
177
+ return self.WORKFLOW_TRANSITIONS.get(current_state, [])
178
+
179
+ def map_status_to_workflow(self, status: str) -> str:
180
+ """
181
+ Map a ticket status to a workflow state.
182
+
183
+ Args:
184
+ status: Ticket status
185
+
186
+ Returns:
187
+ Corresponding workflow state
188
+ """
189
+ return self.STATUS_TO_WORKFLOW.get(status, "todo")
190
+
191
+ def bulk_transition(
192
+ self, ticket_ids: List[str], new_state: str, comment: Optional[str] = None
193
+ ) -> Dict[str, Any]:
194
+ """
195
+ Transition multiple tickets to a new state.
196
+
197
+ Args:
198
+ ticket_ids: List of ticket IDs
199
+ new_state: Target workflow state
200
+ comment: Optional comment for all transitions
201
+
202
+ Returns:
203
+ Dict with results for each ticket
204
+ """
205
+ results = {"succeeded": [], "failed": [], "total": len(ticket_ids)}
206
+
207
+ for ticket_id in ticket_ids:
208
+ result = self.transition_ticket(ticket_id, new_state, comment)
209
+
210
+ if result["success"]:
211
+ results["succeeded"].append(ticket_id)
212
+ else:
213
+ results["failed"].append(
214
+ {
215
+ "ticket_id": ticket_id,
216
+ "error": result.get("error", "Unknown error"),
217
+ }
218
+ )
219
+
220
+ return {"success": len(results["failed"]) == 0, "results": results}
221
+
222
+ def get_workflow_summary(self, tickets: List[Dict[str, Any]]) -> Dict[str, int]:
223
+ """
224
+ Get summary of tickets by workflow state.
225
+
226
+ Args:
227
+ tickets: List of ticket dictionaries
228
+
229
+ Returns:
230
+ Dict mapping workflow states to counts
231
+ """
232
+ summary = dict.fromkeys(self.WORKFLOW_TRANSITIONS.keys(), 0)
233
+ summary["unknown"] = 0
234
+
235
+ for ticket in tickets:
236
+ status = ticket.get("status", "unknown")
237
+ workflow_state = self.map_status_to_workflow(status)
238
+
239
+ if workflow_state in summary:
240
+ summary[workflow_state] += 1
241
+ else:
242
+ summary["unknown"] += 1
243
+
244
+ return summary
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-mpm
3
- Version: 4.1.4
3
+ Version: 4.1.6
4
4
  Summary: Claude Multi-Agent Project Manager - Orchestrate Claude with agent delegation and ticket tracking
5
5
  Author-email: Bob Matsuoka <bob@matsuoka.com>
6
6
  Maintainer: Claude MPM Team
@@ -46,6 +46,8 @@ Requires-Dist: toml>=0.10.2
46
46
  Requires-Dist: packaging>=21.0
47
47
  Requires-Dist: pydantic>=2.0.0
48
48
  Requires-Dist: pydantic-settings>=2.0.0
49
+ Requires-Dist: textual>=0.47.0
50
+ Requires-Dist: rich>=13.0.0
49
51
  Requires-Dist: pyee>=13.0.0
50
52
  Requires-Dist: importlib-resources>=5.0; python_version < "3.9"
51
53
  Provides-Extra: dev