claude-mpm 3.5.4__py3-none-any.whl → 3.6.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.
- claude_mpm/.claude-mpm/logs/hooks_20250728.log +10 -0
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +96 -23
- claude_mpm/agents/BASE_PM.md +273 -0
- claude_mpm/agents/INSTRUCTIONS.md +114 -102
- claude_mpm/agents/agent-template.yaml +83 -0
- claude_mpm/agents/agent_loader.py +36 -1
- claude_mpm/agents/async_agent_loader.py +421 -0
- claude_mpm/agents/templates/code_analyzer.json +81 -0
- claude_mpm/agents/templates/data_engineer.json +18 -3
- claude_mpm/agents/templates/documentation.json +18 -3
- claude_mpm/agents/templates/engineer.json +19 -4
- claude_mpm/agents/templates/ops.json +18 -3
- claude_mpm/agents/templates/qa.json +20 -4
- claude_mpm/agents/templates/research.json +20 -4
- claude_mpm/agents/templates/security.json +18 -3
- claude_mpm/agents/templates/version_control.json +16 -3
- claude_mpm/cli/README.md +108 -0
- claude_mpm/cli/__init__.py +5 -1
- claude_mpm/cli/commands/__init__.py +5 -1
- claude_mpm/cli/commands/agents.py +233 -6
- claude_mpm/cli/commands/aggregate.py +462 -0
- claude_mpm/cli/commands/config.py +277 -0
- claude_mpm/cli/commands/run.py +228 -47
- claude_mpm/cli/parser.py +176 -1
- claude_mpm/cli/utils.py +9 -1
- claude_mpm/cli_module/refactoring_guide.md +253 -0
- claude_mpm/config/async_logging_config.yaml +145 -0
- claude_mpm/constants.py +19 -0
- claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +34 -0
- claude_mpm/core/claude_runner.py +413 -76
- claude_mpm/core/config.py +161 -4
- claude_mpm/core/config_paths.py +0 -1
- claude_mpm/core/factories.py +9 -3
- claude_mpm/core/framework_loader.py +81 -0
- claude_mpm/dashboard/.claude-mpm/memories/README.md +36 -0
- claude_mpm/dashboard/README.md +121 -0
- claude_mpm/dashboard/static/js/dashboard.js.backup +1973 -0
- claude_mpm/dashboard/templates/.claude-mpm/memories/README.md +36 -0
- claude_mpm/dashboard/templates/.claude-mpm/memories/engineer_agent.md +39 -0
- claude_mpm/dashboard/templates/.claude-mpm/memories/version_control_agent.md +38 -0
- claude_mpm/hooks/README.md +96 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +391 -9
- claude_mpm/init.py +123 -18
- claude_mpm/models/agent_session.py +511 -0
- claude_mpm/schemas/agent_schema.json +435 -0
- claude_mpm/scripts/__init__.py +15 -0
- claude_mpm/scripts/start_activity_logging.py +86 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +326 -24
- claude_mpm/services/agents/deployment/async_agent_deployment.py +461 -0
- claude_mpm/services/agents/management/agent_management_service.py +2 -1
- claude_mpm/services/event_aggregator.py +547 -0
- claude_mpm/services/framework_claude_md_generator/README.md +92 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +3 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +2 -2
- claude_mpm/services/version_control/VERSION +1 -0
- claude_mpm/utils/agent_dependency_loader.py +655 -0
- claude_mpm/utils/console.py +11 -0
- claude_mpm/utils/dependency_cache.py +376 -0
- claude_mpm/utils/dependency_strategies.py +343 -0
- claude_mpm/utils/environment_context.py +310 -0
- {claude_mpm-3.5.4.dist-info → claude_mpm-3.6.0.dist-info}/METADATA +87 -1
- {claude_mpm-3.5.4.dist-info → claude_mpm-3.6.0.dist-info}/RECORD +67 -37
- claude_mpm/agents/templates/pm.json +0 -122
- {claude_mpm-3.5.4.dist-info → claude_mpm-3.6.0.dist-info}/WHEEL +0 -0
- {claude_mpm-3.5.4.dist-info → claude_mpm-3.6.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.5.4.dist-info → claude_mpm-3.6.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.5.4.dist-info → claude_mpm-3.6.0.dist-info}/top_level.txt +0 -0
| @@ -0,0 +1,462 @@ | |
| 1 | 
            +
            """CLI commands for the Event Aggregator service.
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            WHY: Provides command-line interface for managing the event aggregator service
         | 
| 4 | 
            +
            that captures Socket.IO events and saves them as structured session documents.
         | 
| 5 | 
            +
            """
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            import json
         | 
| 8 | 
            +
            import sys
         | 
| 9 | 
            +
            from pathlib import Path
         | 
| 10 | 
            +
            from typing import Optional
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            from ...services.event_aggregator import (
         | 
| 13 | 
            +
                get_aggregator,
         | 
| 14 | 
            +
                start_aggregator,
         | 
| 15 | 
            +
                stop_aggregator,
         | 
| 16 | 
            +
                aggregator_status
         | 
| 17 | 
            +
            )
         | 
| 18 | 
            +
            from ...models.agent_session import AgentSession
         | 
| 19 | 
            +
            from ...core.logger import get_logger
         | 
| 20 | 
            +
             | 
| 21 | 
            +
             | 
| 22 | 
            +
            logger = get_logger("cli.aggregate")
         | 
| 23 | 
            +
             | 
| 24 | 
            +
             | 
| 25 | 
            +
            def aggregate_command(args):
         | 
| 26 | 
            +
                """Main entry point for aggregate commands.
         | 
| 27 | 
            +
                
         | 
| 28 | 
            +
                WHY: Routes subcommands to appropriate handlers for managing the
         | 
| 29 | 
            +
                event aggregator service.
         | 
| 30 | 
            +
                """
         | 
| 31 | 
            +
                subcommand = args.aggregate_subcommand
         | 
| 32 | 
            +
                
         | 
| 33 | 
            +
                if subcommand == 'start':
         | 
| 34 | 
            +
                    return start_command(args)
         | 
| 35 | 
            +
                elif subcommand == 'stop':
         | 
| 36 | 
            +
                    return stop_command(args)
         | 
| 37 | 
            +
                elif subcommand == 'status':
         | 
| 38 | 
            +
                    return status_command(args)
         | 
| 39 | 
            +
                elif subcommand == 'sessions':
         | 
| 40 | 
            +
                    return sessions_command(args)
         | 
| 41 | 
            +
                elif subcommand == 'view':
         | 
| 42 | 
            +
                    return view_command(args)
         | 
| 43 | 
            +
                elif subcommand == 'export':
         | 
| 44 | 
            +
                    return export_command(args)
         | 
| 45 | 
            +
                else:
         | 
| 46 | 
            +
                    print(f"Unknown subcommand: {subcommand}", file=sys.stderr)
         | 
| 47 | 
            +
                    return 1
         | 
| 48 | 
            +
             | 
| 49 | 
            +
             | 
| 50 | 
            +
            def start_command(args):
         | 
| 51 | 
            +
                """Start the event aggregator service.
         | 
| 52 | 
            +
                
         | 
| 53 | 
            +
                WHY: Starts capturing events from the Socket.IO dashboard server
         | 
| 54 | 
            +
                for building complete session documents.
         | 
| 55 | 
            +
                """
         | 
| 56 | 
            +
                print("Starting Event Aggregator service...")
         | 
| 57 | 
            +
                
         | 
| 58 | 
            +
                # Check if Socket.IO server is running
         | 
| 59 | 
            +
                import socket
         | 
| 60 | 
            +
                try:
         | 
| 61 | 
            +
                    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
         | 
| 62 | 
            +
                        s.settimeout(1)
         | 
| 63 | 
            +
                        result = s.connect_ex(('127.0.0.1', 8765))
         | 
| 64 | 
            +
                        if result != 0:
         | 
| 65 | 
            +
                            print("Warning: Socket.IO server not detected on port 8765")
         | 
| 66 | 
            +
                            print("The aggregator requires the dashboard server to be running.")
         | 
| 67 | 
            +
                            print("Start it with: claude-mpm monitor")
         | 
| 68 | 
            +
                            if not args.force:
         | 
| 69 | 
            +
                                return 1
         | 
| 70 | 
            +
                except Exception as e:
         | 
| 71 | 
            +
                    logger.error(f"Error checking server status: {e}")
         | 
| 72 | 
            +
                
         | 
| 73 | 
            +
                # Start the aggregator
         | 
| 74 | 
            +
                if start_aggregator():
         | 
| 75 | 
            +
                    print("✅ Event Aggregator started successfully")
         | 
| 76 | 
            +
                    print(f"Capturing events from localhost:8765")
         | 
| 77 | 
            +
                    print(f"Sessions will be saved to: .claude-mpm/sessions/")
         | 
| 78 | 
            +
                    
         | 
| 79 | 
            +
                    # Show initial status
         | 
| 80 | 
            +
                    status = aggregator_status()
         | 
| 81 | 
            +
                    print(f"\nStatus:")
         | 
| 82 | 
            +
                    print(f"  Connected: {status['connected']}")
         | 
| 83 | 
            +
                    print(f"  Active sessions: {status['active_sessions']}")
         | 
| 84 | 
            +
                    
         | 
| 85 | 
            +
                    if args.daemon:
         | 
| 86 | 
            +
                        print("\nAggregator running in background. Use 'claude-mpm aggregate stop' to stop it.")
         | 
| 87 | 
            +
                    else:
         | 
| 88 | 
            +
                        print("\nPress Ctrl+C to stop the aggregator...")
         | 
| 89 | 
            +
                        try:
         | 
| 90 | 
            +
                            # Keep running until interrupted
         | 
| 91 | 
            +
                            import time
         | 
| 92 | 
            +
                            while True:
         | 
| 93 | 
            +
                                time.sleep(1)
         | 
| 94 | 
            +
                        except KeyboardInterrupt:
         | 
| 95 | 
            +
                            print("\nStopping aggregator...")
         | 
| 96 | 
            +
                            stop_aggregator()
         | 
| 97 | 
            +
                    
         | 
| 98 | 
            +
                    return 0
         | 
| 99 | 
            +
                else:
         | 
| 100 | 
            +
                    print("❌ Failed to start Event Aggregator")
         | 
| 101 | 
            +
                    print("Check that python-socketio is installed: pip install python-socketio")
         | 
| 102 | 
            +
                    return 1
         | 
| 103 | 
            +
             | 
| 104 | 
            +
             | 
| 105 | 
            +
            def stop_command(args):
         | 
| 106 | 
            +
                """Stop the event aggregator service.
         | 
| 107 | 
            +
                
         | 
| 108 | 
            +
                WHY: Gracefully stops the aggregator and saves any active sessions.
         | 
| 109 | 
            +
                """
         | 
| 110 | 
            +
                print("Stopping Event Aggregator service...")
         | 
| 111 | 
            +
                
         | 
| 112 | 
            +
                # Get status before stopping
         | 
| 113 | 
            +
                status = aggregator_status()
         | 
| 114 | 
            +
                active_sessions = status.get('active_sessions', 0)
         | 
| 115 | 
            +
                
         | 
| 116 | 
            +
                if active_sessions > 0:
         | 
| 117 | 
            +
                    print(f"Saving {active_sessions} active session(s)...")
         | 
| 118 | 
            +
                
         | 
| 119 | 
            +
                stop_aggregator()
         | 
| 120 | 
            +
                print("✅ Event Aggregator stopped")
         | 
| 121 | 
            +
                
         | 
| 122 | 
            +
                # Show final statistics
         | 
| 123 | 
            +
                if status['total_events'] > 0:
         | 
| 124 | 
            +
                    print(f"\nStatistics:")
         | 
| 125 | 
            +
                    print(f"  Total events captured: {status['total_events']}")
         | 
| 126 | 
            +
                    print(f"  Sessions completed: {status['sessions_completed']}")
         | 
| 127 | 
            +
                    print(f"  Events by type:")
         | 
| 128 | 
            +
                    for event_type, count in sorted(status['events_by_type'].items(), 
         | 
| 129 | 
            +
                                                   key=lambda x: x[1], reverse=True)[:5]:
         | 
| 130 | 
            +
                        print(f"    {event_type}: {count}")
         | 
| 131 | 
            +
                
         | 
| 132 | 
            +
                return 0
         | 
| 133 | 
            +
             | 
| 134 | 
            +
             | 
| 135 | 
            +
            def status_command(args):
         | 
| 136 | 
            +
                """Show status of the event aggregator service.
         | 
| 137 | 
            +
                
         | 
| 138 | 
            +
                WHY: Provides visibility into what the aggregator is doing and
         | 
| 139 | 
            +
                what it has captured.
         | 
| 140 | 
            +
                """
         | 
| 141 | 
            +
                status = aggregator_status()
         | 
| 142 | 
            +
                
         | 
| 143 | 
            +
                print("Event Aggregator Status")
         | 
| 144 | 
            +
                print("=" * 50)
         | 
| 145 | 
            +
                print(f"Running: {status['running']}")
         | 
| 146 | 
            +
                print(f"Connected: {status['connected']}")
         | 
| 147 | 
            +
                print(f"Server: {status['server']}")
         | 
| 148 | 
            +
                print(f"Save directory: {status['save_directory']}")
         | 
| 149 | 
            +
                print()
         | 
| 150 | 
            +
                print(f"Active sessions: {status['active_sessions']}")
         | 
| 151 | 
            +
                if status['active_session_ids']:
         | 
| 152 | 
            +
                    for sid in status['active_session_ids']:
         | 
| 153 | 
            +
                        print(f"  - {sid}")
         | 
| 154 | 
            +
                print()
         | 
| 155 | 
            +
                print(f"Sessions completed: {status['sessions_completed']}")
         | 
| 156 | 
            +
                print(f"Total events captured: {status['total_events']}")
         | 
| 157 | 
            +
                
         | 
| 158 | 
            +
                if status['events_by_type']:
         | 
| 159 | 
            +
                    print("\nTop event types:")
         | 
| 160 | 
            +
                    for event_type, count in sorted(status['events_by_type'].items(), 
         | 
| 161 | 
            +
                                                   key=lambda x: x[1], reverse=True)[:10]:
         | 
| 162 | 
            +
                        print(f"  {event_type:30s} {count:6d}")
         | 
| 163 | 
            +
                
         | 
| 164 | 
            +
                return 0
         | 
| 165 | 
            +
             | 
| 166 | 
            +
             | 
| 167 | 
            +
            def sessions_command(args):
         | 
| 168 | 
            +
                """List captured sessions.
         | 
| 169 | 
            +
                
         | 
| 170 | 
            +
                WHY: Shows what sessions have been captured for analysis.
         | 
| 171 | 
            +
                """
         | 
| 172 | 
            +
                aggregator = get_aggregator()
         | 
| 173 | 
            +
                sessions = aggregator.list_sessions(limit=args.limit)
         | 
| 174 | 
            +
                
         | 
| 175 | 
            +
                if not sessions:
         | 
| 176 | 
            +
                    print("No sessions found")
         | 
| 177 | 
            +
                    return 0
         | 
| 178 | 
            +
                
         | 
| 179 | 
            +
                print(f"Recent Sessions (showing {len(sessions)} of {args.limit} max)")
         | 
| 180 | 
            +
                print("=" * 80)
         | 
| 181 | 
            +
                
         | 
| 182 | 
            +
                for session in sessions:
         | 
| 183 | 
            +
                    print(f"\n📁 {session['file']}")
         | 
| 184 | 
            +
                    print(f"   Session ID: {session['session_id']}")
         | 
| 185 | 
            +
                    print(f"   Start: {session['start_time']}")
         | 
| 186 | 
            +
                    print(f"   End: {session['end_time']}")
         | 
| 187 | 
            +
                    print(f"   Events: {session['events']}")
         | 
| 188 | 
            +
                    print(f"   Delegations: {session['delegations']}")
         | 
| 189 | 
            +
                    print(f"   Prompt: {session['initial_prompt']}")
         | 
| 190 | 
            +
                
         | 
| 191 | 
            +
                print(f"\nUse 'claude-mpm aggregate view <session_id>' to view details")
         | 
| 192 | 
            +
                
         | 
| 193 | 
            +
                return 0
         | 
| 194 | 
            +
             | 
| 195 | 
            +
             | 
| 196 | 
            +
            def view_command(args):
         | 
| 197 | 
            +
                """View details of a specific session.
         | 
| 198 | 
            +
                
         | 
| 199 | 
            +
                WHY: Allows detailed inspection of what happened during a session.
         | 
| 200 | 
            +
                """
         | 
| 201 | 
            +
                aggregator = get_aggregator()
         | 
| 202 | 
            +
                
         | 
| 203 | 
            +
                # Load the session
         | 
| 204 | 
            +
                session = aggregator.load_session(args.session_id)
         | 
| 205 | 
            +
                
         | 
| 206 | 
            +
                if not session:
         | 
| 207 | 
            +
                    print(f"Session not found: {args.session_id}")
         | 
| 208 | 
            +
                    print("Use 'claude-mpm aggregate sessions' to list available sessions")
         | 
| 209 | 
            +
                    return 1
         | 
| 210 | 
            +
                
         | 
| 211 | 
            +
                print(f"Session: {session.session_id}")
         | 
| 212 | 
            +
                print("=" * 80)
         | 
| 213 | 
            +
                print(f"Start: {session.start_time}")
         | 
| 214 | 
            +
                print(f"End: {session.end_time or 'In progress'}")
         | 
| 215 | 
            +
                print(f"Working directory: {session.working_directory}")
         | 
| 216 | 
            +
                print(f"Launch method: {session.launch_method}")
         | 
| 217 | 
            +
                
         | 
| 218 | 
            +
                if session.git_branch:
         | 
| 219 | 
            +
                    print(f"Git branch: {session.git_branch}")
         | 
| 220 | 
            +
                
         | 
| 221 | 
            +
                print(f"\nInitial prompt:")
         | 
| 222 | 
            +
                print("-" * 40)
         | 
| 223 | 
            +
                if session.initial_prompt:
         | 
| 224 | 
            +
                    print(session.initial_prompt[:500])
         | 
| 225 | 
            +
                    if len(session.initial_prompt) > 500:
         | 
| 226 | 
            +
                        print("...")
         | 
| 227 | 
            +
                else:
         | 
| 228 | 
            +
                    print("(No prompt captured)")
         | 
| 229 | 
            +
                
         | 
| 230 | 
            +
                print(f"\nMetrics:")
         | 
| 231 | 
            +
                print("-" * 40)
         | 
| 232 | 
            +
                metrics = session.metrics
         | 
| 233 | 
            +
                print(f"Total events: {metrics.total_events}")
         | 
| 234 | 
            +
                print(f"Delegations: {metrics.total_delegations}")
         | 
| 235 | 
            +
                print(f"Tool calls: {metrics.total_tool_calls}")
         | 
| 236 | 
            +
                print(f"File operations: {metrics.total_file_operations}")
         | 
| 237 | 
            +
                
         | 
| 238 | 
            +
                if metrics.session_duration_ms:
         | 
| 239 | 
            +
                    duration_sec = metrics.session_duration_ms / 1000
         | 
| 240 | 
            +
                    print(f"Duration: {duration_sec:.1f} seconds")
         | 
| 241 | 
            +
                
         | 
| 242 | 
            +
                if metrics.agents_used:
         | 
| 243 | 
            +
                    print(f"\nAgents used: {', '.join(sorted(metrics.agents_used))}")
         | 
| 244 | 
            +
                
         | 
| 245 | 
            +
                if metrics.tools_used:
         | 
| 246 | 
            +
                    print(f"\nTools used: {', '.join(sorted(metrics.tools_used))}")
         | 
| 247 | 
            +
                
         | 
| 248 | 
            +
                if metrics.files_modified:
         | 
| 249 | 
            +
                    print(f"\nFiles modified ({len(metrics.files_modified)}):")
         | 
| 250 | 
            +
                    for filepath in sorted(metrics.files_modified)[:10]:
         | 
| 251 | 
            +
                        print(f"  - {filepath}")
         | 
| 252 | 
            +
                    if len(metrics.files_modified) > 10:
         | 
| 253 | 
            +
                        print(f"  ... and {len(metrics.files_modified) - 10} more")
         | 
| 254 | 
            +
                
         | 
| 255 | 
            +
                if session.delegations:
         | 
| 256 | 
            +
                    print(f"\nDelegations ({len(session.delegations)}):")
         | 
| 257 | 
            +
                    print("-" * 40)
         | 
| 258 | 
            +
                    for i, delegation in enumerate(session.delegations, 1):
         | 
| 259 | 
            +
                        print(f"\n{i}. {delegation.agent_type}")
         | 
| 260 | 
            +
                        print(f"   Task: {delegation.task_description[:100]}")
         | 
| 261 | 
            +
                        if delegation.prompt:
         | 
| 262 | 
            +
                            print(f"   Prompt: {delegation.prompt[:100]}...")
         | 
| 263 | 
            +
                        print(f"   Tools: {len(delegation.tool_operations)}")
         | 
| 264 | 
            +
                        print(f"   Files: {len(delegation.file_changes)}")
         | 
| 265 | 
            +
                        if delegation.duration_ms:
         | 
| 266 | 
            +
                            print(f"   Duration: {delegation.duration_ms/1000:.1f}s")
         | 
| 267 | 
            +
                        print(f"   Status: {'✅ Success' if delegation.success else '❌ Failed'}")
         | 
| 268 | 
            +
                        if delegation.error:
         | 
| 269 | 
            +
                            print(f"   Error: {delegation.error}")
         | 
| 270 | 
            +
                
         | 
| 271 | 
            +
                if args.show_events:
         | 
| 272 | 
            +
                    print(f"\nEvents ({len(session.events)}):")
         | 
| 273 | 
            +
                    print("-" * 40)
         | 
| 274 | 
            +
                    for event in session.events[:args.event_limit]:
         | 
| 275 | 
            +
                        print(f"{event.timestamp} [{event.category.value:10s}] {event.event_type}")
         | 
| 276 | 
            +
                        if args.verbose:
         | 
| 277 | 
            +
                            print(f"  Agent: {event.agent_context or 'N/A'}")
         | 
| 278 | 
            +
                            if event.correlation_id:
         | 
| 279 | 
            +
                                print(f"  Correlation: {event.correlation_id}")
         | 
| 280 | 
            +
                
         | 
| 281 | 
            +
                if session.final_response and not args.no_response:
         | 
| 282 | 
            +
                    print(f"\nFinal response:")
         | 
| 283 | 
            +
                    print("-" * 40)
         | 
| 284 | 
            +
                    print(session.final_response[:1000])
         | 
| 285 | 
            +
                    if len(session.final_response) > 1000:
         | 
| 286 | 
            +
                        print("...")
         | 
| 287 | 
            +
                
         | 
| 288 | 
            +
                return 0
         | 
| 289 | 
            +
             | 
| 290 | 
            +
             | 
| 291 | 
            +
            def export_command(args):
         | 
| 292 | 
            +
                """Export a session to a file.
         | 
| 293 | 
            +
                
         | 
| 294 | 
            +
                WHY: Allows sessions to be exported for external analysis or sharing.
         | 
| 295 | 
            +
                """
         | 
| 296 | 
            +
                aggregator = get_aggregator()
         | 
| 297 | 
            +
                
         | 
| 298 | 
            +
                # Load the session
         | 
| 299 | 
            +
                session = aggregator.load_session(args.session_id)
         | 
| 300 | 
            +
                
         | 
| 301 | 
            +
                if not session:
         | 
| 302 | 
            +
                    print(f"Session not found: {args.session_id}")
         | 
| 303 | 
            +
                    return 1
         | 
| 304 | 
            +
                
         | 
| 305 | 
            +
                # Determine output file
         | 
| 306 | 
            +
                if args.output:
         | 
| 307 | 
            +
                    output_path = Path(args.output)
         | 
| 308 | 
            +
                else:
         | 
| 309 | 
            +
                    output_path = Path(f"session_{session.session_id[:8]}_export.json")
         | 
| 310 | 
            +
                
         | 
| 311 | 
            +
                # Export based on format
         | 
| 312 | 
            +
                if args.format == 'json':
         | 
| 313 | 
            +
                    # Full JSON export
         | 
| 314 | 
            +
                    with open(output_path, 'w') as f:
         | 
| 315 | 
            +
                        json.dump(session.to_dict(), f, indent=2)
         | 
| 316 | 
            +
                    print(f"✅ Exported session to {output_path}")
         | 
| 317 | 
            +
                    
         | 
| 318 | 
            +
                elif args.format == 'summary':
         | 
| 319 | 
            +
                    # Summary export
         | 
| 320 | 
            +
                    summary = {
         | 
| 321 | 
            +
                        'session_id': session.session_id,
         | 
| 322 | 
            +
                        'start_time': session.start_time,
         | 
| 323 | 
            +
                        'end_time': session.end_time,
         | 
| 324 | 
            +
                        'working_directory': session.working_directory,
         | 
| 325 | 
            +
                        'initial_prompt': session.initial_prompt,
         | 
| 326 | 
            +
                        'final_response': session.final_response,
         | 
| 327 | 
            +
                        'metrics': session.metrics.to_dict(),
         | 
| 328 | 
            +
                        'delegations_summary': [
         | 
| 329 | 
            +
                            {
         | 
| 330 | 
            +
                                'agent': d.agent_type,
         | 
| 331 | 
            +
                                'task': d.task_description,
         | 
| 332 | 
            +
                                'duration_ms': d.duration_ms,
         | 
| 333 | 
            +
                                'success': d.success,
         | 
| 334 | 
            +
                                'tools_used': len(d.tool_operations),
         | 
| 335 | 
            +
                                'files_changed': len(d.file_changes)
         | 
| 336 | 
            +
                            }
         | 
| 337 | 
            +
                            for d in session.delegations
         | 
| 338 | 
            +
                        ]
         | 
| 339 | 
            +
                    }
         | 
| 340 | 
            +
                    
         | 
| 341 | 
            +
                    with open(output_path, 'w') as f:
         | 
| 342 | 
            +
                        json.dump(summary, f, indent=2)
         | 
| 343 | 
            +
                    print(f"✅ Exported session summary to {output_path}")
         | 
| 344 | 
            +
                
         | 
| 345 | 
            +
                elif args.format == 'events':
         | 
| 346 | 
            +
                    # Events-only export
         | 
| 347 | 
            +
                    events_data = [e.to_dict() for e in session.events]
         | 
| 348 | 
            +
                    
         | 
| 349 | 
            +
                    with open(output_path, 'w') as f:
         | 
| 350 | 
            +
                        json.dump(events_data, f, indent=2)
         | 
| 351 | 
            +
                    print(f"✅ Exported {len(events_data)} events to {output_path}")
         | 
| 352 | 
            +
                
         | 
| 353 | 
            +
                return 0
         | 
| 354 | 
            +
             | 
| 355 | 
            +
             | 
| 356 | 
            +
            def add_aggregate_parser(subparsers):
         | 
| 357 | 
            +
                """Add the aggregate command parser.
         | 
| 358 | 
            +
                
         | 
| 359 | 
            +
                WHY: Integrates the aggregator commands into the main CLI system.
         | 
| 360 | 
            +
                """
         | 
| 361 | 
            +
                aggregate_parser = subparsers.add_parser(
         | 
| 362 | 
            +
                    'aggregate',
         | 
| 363 | 
            +
                    help='Manage event aggregator for capturing agent sessions'
         | 
| 364 | 
            +
                )
         | 
| 365 | 
            +
                
         | 
| 366 | 
            +
                aggregate_subparsers = aggregate_parser.add_subparsers(
         | 
| 367 | 
            +
                    dest='aggregate_subcommand',
         | 
| 368 | 
            +
                    help='Aggregator subcommands'
         | 
| 369 | 
            +
                )
         | 
| 370 | 
            +
                
         | 
| 371 | 
            +
                # Start command
         | 
| 372 | 
            +
                start_parser = aggregate_subparsers.add_parser(
         | 
| 373 | 
            +
                    'start',
         | 
| 374 | 
            +
                    help='Start the event aggregator service'
         | 
| 375 | 
            +
                )
         | 
| 376 | 
            +
                start_parser.add_argument(
         | 
| 377 | 
            +
                    '--daemon', '-d',
         | 
| 378 | 
            +
                    action='store_true',
         | 
| 379 | 
            +
                    help='Run in background as daemon'
         | 
| 380 | 
            +
                )
         | 
| 381 | 
            +
                start_parser.add_argument(
         | 
| 382 | 
            +
                    '--force', '-f',
         | 
| 383 | 
            +
                    action='store_true',
         | 
| 384 | 
            +
                    help='Start even if Socket.IO server not detected'
         | 
| 385 | 
            +
                )
         | 
| 386 | 
            +
                
         | 
| 387 | 
            +
                # Stop command
         | 
| 388 | 
            +
                aggregate_subparsers.add_parser(
         | 
| 389 | 
            +
                    'stop',
         | 
| 390 | 
            +
                    help='Stop the event aggregator service'
         | 
| 391 | 
            +
                )
         | 
| 392 | 
            +
                
         | 
| 393 | 
            +
                # Status command
         | 
| 394 | 
            +
                aggregate_subparsers.add_parser(
         | 
| 395 | 
            +
                    'status',
         | 
| 396 | 
            +
                    help='Show aggregator status and statistics'
         | 
| 397 | 
            +
                )
         | 
| 398 | 
            +
                
         | 
| 399 | 
            +
                # Sessions command
         | 
| 400 | 
            +
                sessions_parser = aggregate_subparsers.add_parser(
         | 
| 401 | 
            +
                    'sessions',
         | 
| 402 | 
            +
                    help='List captured sessions'
         | 
| 403 | 
            +
                )
         | 
| 404 | 
            +
                sessions_parser.add_argument(
         | 
| 405 | 
            +
                    '--limit', '-l',
         | 
| 406 | 
            +
                    type=int,
         | 
| 407 | 
            +
                    default=10,
         | 
| 408 | 
            +
                    help='Maximum number of sessions to show (default: 10)'
         | 
| 409 | 
            +
                )
         | 
| 410 | 
            +
                
         | 
| 411 | 
            +
                # View command
         | 
| 412 | 
            +
                view_parser = aggregate_subparsers.add_parser(
         | 
| 413 | 
            +
                    'view',
         | 
| 414 | 
            +
                    help='View details of a specific session'
         | 
| 415 | 
            +
                )
         | 
| 416 | 
            +
                view_parser.add_argument(
         | 
| 417 | 
            +
                    'session_id',
         | 
| 418 | 
            +
                    help='Session ID or prefix to view'
         | 
| 419 | 
            +
                )
         | 
| 420 | 
            +
                view_parser.add_argument(
         | 
| 421 | 
            +
                    '--show-events', '-e',
         | 
| 422 | 
            +
                    action='store_true',
         | 
| 423 | 
            +
                    help='Show event list'
         | 
| 424 | 
            +
                )
         | 
| 425 | 
            +
                view_parser.add_argument(
         | 
| 426 | 
            +
                    '--event-limit',
         | 
| 427 | 
            +
                    type=int,
         | 
| 428 | 
            +
                    default=50,
         | 
| 429 | 
            +
                    help='Maximum events to show (default: 50)'
         | 
| 430 | 
            +
                )
         | 
| 431 | 
            +
                view_parser.add_argument(
         | 
| 432 | 
            +
                    '--verbose', '-v',
         | 
| 433 | 
            +
                    action='store_true',
         | 
| 434 | 
            +
                    help='Show verbose event details'
         | 
| 435 | 
            +
                )
         | 
| 436 | 
            +
                view_parser.add_argument(
         | 
| 437 | 
            +
                    '--no-response',
         | 
| 438 | 
            +
                    action='store_true',
         | 
| 439 | 
            +
                    help='Don\'t show final response'
         | 
| 440 | 
            +
                )
         | 
| 441 | 
            +
                
         | 
| 442 | 
            +
                # Export command
         | 
| 443 | 
            +
                export_parser = aggregate_subparsers.add_parser(
         | 
| 444 | 
            +
                    'export',
         | 
| 445 | 
            +
                    help='Export a session to file'
         | 
| 446 | 
            +
                )
         | 
| 447 | 
            +
                export_parser.add_argument(
         | 
| 448 | 
            +
                    'session_id',
         | 
| 449 | 
            +
                    help='Session ID or prefix to export'
         | 
| 450 | 
            +
                )
         | 
| 451 | 
            +
                export_parser.add_argument(
         | 
| 452 | 
            +
                    '--output', '-o',
         | 
| 453 | 
            +
                    help='Output file path'
         | 
| 454 | 
            +
                )
         | 
| 455 | 
            +
                export_parser.add_argument(
         | 
| 456 | 
            +
                    '--format', '-f',
         | 
| 457 | 
            +
                    choices=['json', 'summary', 'events'],
         | 
| 458 | 
            +
                    default='json',
         | 
| 459 | 
            +
                    help='Export format (default: json)'
         | 
| 460 | 
            +
                )
         | 
| 461 | 
            +
                
         | 
| 462 | 
            +
                aggregate_parser.set_defaults(func=aggregate_command)
         |