claude-mpm 3.1.1__py3-none-any.whl → 3.1.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.
@@ -0,0 +1,171 @@
1
+ """
2
+ Utility functions for the CLI.
3
+
4
+ WHY: This module contains shared utility functions used across different CLI commands.
5
+ Centralizing these functions reduces code duplication and provides a single place
6
+ for common CLI operations.
7
+ """
8
+
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import Optional
12
+
13
+ from ..core.logger import get_logger
14
+
15
+
16
+ def get_user_input(input_arg: Optional[str], logger) -> str:
17
+ """
18
+ Get user input based on command line arguments.
19
+
20
+ WHY: This function handles the three ways users can provide input:
21
+ 1. Direct text via -i/--input
22
+ 2. File path via -i/--input
23
+ 3. stdin (for piping)
24
+
25
+ DESIGN DECISION: We check if the input is a file path first, then fall back
26
+ to treating it as direct text. This allows maximum flexibility.
27
+
28
+ Args:
29
+ input_arg: The value of the -i/--input argument
30
+ logger: Logger instance for output
31
+
32
+ Returns:
33
+ The user input as a string
34
+ """
35
+ if input_arg:
36
+ # Check if it's a file path
37
+ input_path = Path(input_arg)
38
+ if input_path.exists():
39
+ logger.info(f"Reading input from file: {input_path}")
40
+ return input_path.read_text()
41
+ else:
42
+ logger.info("Using command line input")
43
+ return input_arg
44
+ else:
45
+ # Read from stdin
46
+ logger.info("Reading input from stdin")
47
+ return sys.stdin.read()
48
+
49
+
50
+ def get_agent_versions_display() -> Optional[str]:
51
+ """
52
+ Get formatted agent versions display as a string.
53
+
54
+ WHY: This function provides a single source of truth for agent version
55
+ information that can be displayed both at startup and on-demand via the
56
+ /mpm agents command. This ensures consistency in how agent versions are
57
+ presented to users.
58
+
59
+ Returns:
60
+ Formatted string containing agent version information, or None if failed
61
+ """
62
+ try:
63
+ from ..services.agent_deployment import AgentDeploymentService
64
+ deployment_service = AgentDeploymentService()
65
+
66
+ # Get deployed agents
67
+ verification = deployment_service.verify_deployment()
68
+ if not verification.get("agents_found"):
69
+ return None
70
+
71
+ output_lines = []
72
+ output_lines.append("\nDeployed Agent Versions:")
73
+ output_lines.append("-" * 40)
74
+
75
+ # Sort agents by name for consistent display
76
+ agents = sorted(verification["agents_found"], key=lambda x: x.get('name', x.get('file', '')))
77
+
78
+ for agent in agents:
79
+ name = agent.get('name', 'unknown')
80
+ version = agent.get('version', 'unknown')
81
+ # Format: name (version)
82
+ output_lines.append(f" {name:<20} {version}")
83
+
84
+ # Add base agent version info
85
+ try:
86
+ import json
87
+ base_agent_path = deployment_service.base_agent_path
88
+ if base_agent_path.exists():
89
+ base_data = json.loads(base_agent_path.read_text())
90
+ # Parse version the same way as AgentDeploymentService
91
+ raw_version = base_data.get('base_version') or base_data.get('version', 0)
92
+ base_version_tuple = deployment_service._parse_version(raw_version)
93
+ base_version_str = deployment_service._format_version_display(base_version_tuple)
94
+ output_lines.append(f"\n Base Agent Version: {base_version_str}")
95
+ except:
96
+ pass
97
+
98
+ # Check for agents needing migration
99
+ if verification.get("agents_needing_migration"):
100
+ output_lines.append(f"\n ⚠️ {len(verification['agents_needing_migration'])} agent(s) need migration to semantic versioning")
101
+ output_lines.append(f" Run 'claude-mpm agents deploy' to update")
102
+
103
+ output_lines.append("-" * 40)
104
+ return "\n".join(output_lines)
105
+ except Exception as e:
106
+ # Log error but don't fail
107
+ logger = get_logger("cli")
108
+ logger.debug(f"Failed to get agent versions: {e}")
109
+ return None
110
+
111
+
112
+ def list_agent_versions_at_startup() -> None:
113
+ """
114
+ List deployed agent versions at startup.
115
+
116
+ WHY: Users want to see what agents are available when they start a session.
117
+ This provides immediate feedback about the deployed agent environment.
118
+ """
119
+ agent_versions = get_agent_versions_display()
120
+ if agent_versions:
121
+ print(agent_versions)
122
+ print() # Extra newline after the display
123
+
124
+
125
+ def setup_logging(args) -> object:
126
+ """
127
+ Set up logging based on parsed arguments.
128
+
129
+ WHY: This centralizes logging setup logic, handling the deprecated --debug flag
130
+ and the new --logging argument consistently across all commands.
131
+
132
+ Args:
133
+ args: Parsed command line arguments
134
+
135
+ Returns:
136
+ Logger instance
137
+ """
138
+ from ..core.logger import setup_logging as core_setup_logging, get_logger
139
+ from ..constants import LogLevel
140
+
141
+ # Handle deprecated --debug flag
142
+ if hasattr(args, 'debug') and args.debug and args.logging == LogLevel.INFO.value:
143
+ args.logging = LogLevel.DEBUG.value
144
+
145
+ # Only setup logging if not OFF
146
+ if args.logging != LogLevel.OFF.value:
147
+ logger = core_setup_logging(level=args.logging, log_dir=args.log_dir)
148
+ else:
149
+ # Minimal logger for CLI feedback
150
+ import logging
151
+ logger = logging.getLogger("cli")
152
+ logger.setLevel(logging.WARNING)
153
+
154
+ return logger
155
+
156
+
157
+ def ensure_directories() -> None:
158
+ """
159
+ Ensure required directories exist on first run.
160
+
161
+ WHY: Claude-mpm needs certain directories to function properly. Rather than
162
+ failing when they don't exist, we create them automatically for a better
163
+ user experience.
164
+ """
165
+ try:
166
+ from ..init import ensure_directories as init_ensure_directories
167
+ init_ensure_directories()
168
+ except Exception:
169
+ # Continue even if initialization fails
170
+ # The individual commands will handle missing directories as needed
171
+ pass
@@ -1,6 +1,25 @@
1
1
  """
2
2
  Enhanced CLI operations for claude-mpm.
3
3
 
4
+ WHY THIS FILE EXISTS:
5
+ This module provides an alternative CLI implementation with enhanced error handling
6
+ and validation features. It was created to explore advanced CLI patterns including:
7
+ - Comprehensive prerequisite validation
8
+ - User-friendly error messages with suggestions
9
+ - Dry-run mode for testing
10
+ - Profile validation and generation
11
+ - Rich terminal output with status indicators
12
+
13
+ CURRENT STATUS: This is an experimental/alternative CLI implementation that uses
14
+ Click instead of argparse. It's kept separate from the main CLI to:
15
+ 1. Preserve the existing CLI behavior
16
+ 2. Allow testing of new features without breaking the main interface
17
+ 3. Provide a reference implementation for future CLI enhancements
18
+
19
+ NOTE: This CLI is not currently used in production. The main CLI is in cli/__init__.py.
20
+ To use this enhanced CLI, you would need to create a separate entry point or
21
+ integrate selected features into the main CLI.
22
+
4
23
  Implements error handling and user guidance patterns from awesome-claude-code.
5
24
  """
6
25
 
@@ -0,0 +1,24 @@
1
+ """
2
+ Agent models package.
3
+
4
+ WHY: This package centralizes all data models used for agent management,
5
+ providing a single source of truth for data structures across the system.
6
+ """
7
+
8
+ from .agent_definition import (
9
+ AgentDefinition,
10
+ AgentMetadata,
11
+ AgentType,
12
+ AgentSection,
13
+ AgentWorkflow,
14
+ AgentPermissions
15
+ )
16
+
17
+ __all__ = [
18
+ 'AgentDefinition',
19
+ 'AgentMetadata',
20
+ 'AgentType',
21
+ 'AgentSection',
22
+ 'AgentWorkflow',
23
+ 'AgentPermissions'
24
+ ]
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Agent Definition Models
4
+ =======================
5
+
6
+ Data models for agent definitions used by AgentManager.
7
+
8
+ WHY: These models provide a structured representation of agent data to ensure
9
+ consistency across the system. They separate the data structure from the
10
+ business logic, following the separation of concerns principle.
11
+
12
+ DESIGN DECISION: Using dataclasses for models because:
13
+ - They provide automatic __init__, __repr__, and other methods
14
+ - Type hints ensure better IDE support and runtime validation
15
+ - Easy to serialize/deserialize for persistence
16
+ - Less boilerplate than traditional classes
17
+ """
18
+
19
+ from dataclasses import dataclass, field
20
+ from datetime import datetime
21
+ from enum import Enum
22
+ from typing import Dict, List, Optional, Any
23
+
24
+
25
+ class AgentType(str, Enum):
26
+ """Agent type classification.
27
+
28
+ WHY: Enum ensures only valid agent types are used throughout the system,
29
+ preventing typos and making the code more maintainable.
30
+ """
31
+ CORE = "core"
32
+ PROJECT = "project"
33
+ CUSTOM = "custom"
34
+ SYSTEM = "system"
35
+ SPECIALIZED = "specialized"
36
+
37
+
38
+ class AgentSection(str, Enum):
39
+ """Agent markdown section identifiers.
40
+
41
+ WHY: Standardizes section names across the codebase, making it easier
42
+ to parse and update specific sections programmatically.
43
+ """
44
+ PRIMARY_ROLE = "Primary Role"
45
+ WHEN_TO_USE = "When to Use This Agent"
46
+ CAPABILITIES = "Core Capabilities"
47
+ AUTHORITY = "Authority & Permissions"
48
+ WORKFLOWS = "Agent-Specific Workflows"
49
+ ESCALATION = "Unique Escalation Triggers"
50
+ KPI = "Key Performance Indicators"
51
+ DEPENDENCIES = "Critical Dependencies"
52
+ TOOLS = "Specialized Tools/Commands"
53
+
54
+
55
+ @dataclass
56
+ class AgentPermissions:
57
+ """Agent authority and permissions.
58
+
59
+ WHY: Separating permissions into a dedicated class allows for:
60
+ - Clear permission boundaries
61
+ - Easy permission checking and validation
62
+ - Future extension without modifying the main agent definition
63
+ """
64
+ exclusive_write_access: List[str] = field(default_factory=list)
65
+ forbidden_operations: List[str] = field(default_factory=list)
66
+ read_access: List[str] = field(default_factory=list)
67
+
68
+
69
+ @dataclass
70
+ class AgentWorkflow:
71
+ """Agent workflow definition.
72
+
73
+ WHY: Workflows are complex structures that benefit from their own model:
74
+ - Ensures consistent workflow structure
75
+ - Makes workflow validation easier
76
+ - Allows workflow-specific operations
77
+ """
78
+ name: str
79
+ trigger: str
80
+ process: List[str]
81
+ output: str
82
+ raw_yaml: Optional[str] = None
83
+
84
+
85
+ @dataclass
86
+ class AgentMetadata:
87
+ """Agent metadata information.
88
+
89
+ WHY: Metadata is separated from the main definition because:
90
+ - It changes independently of agent behavior
91
+ - It's used for discovery and management, not execution
92
+ - Different services may need different metadata views
93
+ """
94
+ type: AgentType
95
+ model_preference: str = "claude-3-sonnet"
96
+ version: str = "1.0.0"
97
+ last_updated: Optional[datetime] = None
98
+ author: Optional[str] = None
99
+ tags: List[str] = field(default_factory=list)
100
+ specializations: List[str] = field(default_factory=list)
101
+
102
+ def increment_serial_version(self) -> None:
103
+ """Increment the patch version number.
104
+
105
+ WHY: Automatic version incrementing ensures every change is tracked
106
+ and follows semantic versioning principles.
107
+ """
108
+ parts = self.version.split('.')
109
+ if len(parts) == 3:
110
+ parts[2] = str(int(parts[2]) + 1)
111
+ self.version = '.'.join(parts)
112
+ else:
113
+ # If version format is unexpected, append .1
114
+ self.version = f"{self.version}.1"
115
+
116
+
117
+ @dataclass
118
+ class AgentDefinition:
119
+ """Complete agent definition.
120
+
121
+ WHY: This is the main model that represents an agent's complete configuration:
122
+ - Combines all aspects of an agent in one place
123
+ - Provides a clear contract for what constitutes an agent
124
+ - Makes serialization/deserialization straightforward
125
+
126
+ DESIGN DECISION: Using composition over inheritance:
127
+ - AgentMetadata, AgentPermissions, and AgentWorkflow are separate classes
128
+ - This allows each component to evolve independently
129
+ - Services can work with just the parts they need
130
+ """
131
+ # Core identifiers
132
+ name: str
133
+ title: str
134
+ file_path: str
135
+
136
+ # Metadata
137
+ metadata: AgentMetadata
138
+
139
+ # Agent behavior definition
140
+ primary_role: str
141
+ when_to_use: Dict[str, List[str]] # {"select": [...], "do_not_select": [...]}
142
+ capabilities: List[str]
143
+ authority: AgentPermissions
144
+ workflows: List[AgentWorkflow]
145
+ escalation_triggers: List[str]
146
+ kpis: List[str]
147
+ dependencies: List[str]
148
+ tools_commands: str
149
+
150
+ # Raw content for preservation
151
+ raw_content: str = ""
152
+ raw_sections: Dict[str, str] = field(default_factory=dict)
153
+
154
+ def to_dict(self) -> Dict[str, Any]:
155
+ """Convert to dictionary for API responses.
156
+
157
+ WHY: Many parts of the system need agent data as dictionaries:
158
+ - JSON serialization for APIs
159
+ - Configuration storage
160
+ - Integration with other services
161
+ """
162
+ return {
163
+ "name": self.name,
164
+ "title": self.title,
165
+ "file_path": self.file_path,
166
+ "metadata": {
167
+ "type": self.metadata.type.value,
168
+ "model_preference": self.metadata.model_preference,
169
+ "version": self.metadata.version,
170
+ "last_updated": self.metadata.last_updated.isoformat() if self.metadata.last_updated else None,
171
+ "author": self.metadata.author,
172
+ "tags": self.metadata.tags,
173
+ "specializations": self.metadata.specializations
174
+ },
175
+ "primary_role": self.primary_role,
176
+ "when_to_use": self.when_to_use,
177
+ "capabilities": self.capabilities,
178
+ "authority": {
179
+ "exclusive_write_access": self.authority.exclusive_write_access,
180
+ "forbidden_operations": self.authority.forbidden_operations,
181
+ "read_access": self.authority.read_access
182
+ },
183
+ "workflows": [
184
+ {
185
+ "name": w.name,
186
+ "trigger": w.trigger,
187
+ "process": w.process,
188
+ "output": w.output
189
+ }
190
+ for w in self.workflows
191
+ ],
192
+ "escalation_triggers": self.escalation_triggers,
193
+ "kpis": self.kpis,
194
+ "dependencies": self.dependencies,
195
+ "tools_commands": self.tools_commands
196
+ }