claude-mpm 3.4.6__py3-none-any.whl → 3.4.7__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.
@@ -184,12 +184,8 @@ class ClaudeRunner:
184
184
  self.logger.warning(f"Failed to connect to Socket.IO server: {e}")
185
185
  self.websocket_server = None
186
186
 
187
- # Get version
188
- try:
189
- from claude_mpm import __version__
190
- version_str = f"v{__version__}"
191
- except:
192
- version_str = "v0.0.0"
187
+ # Get version with robust fallback mechanisms
188
+ version_str = self._get_version()
193
189
 
194
190
  # Print styled welcome box
195
191
  print("\033[32m╭───────────────────────────────────────────────────╮\033[0m")
@@ -750,6 +746,74 @@ class ClaudeRunner:
750
746
  except Exception as e:
751
747
  self.logger.debug(f"Failed to log session event: {e}")
752
748
 
749
+ def _get_version(self) -> str:
750
+ """
751
+ Robust version determination with multiple fallback mechanisms.
752
+
753
+ WHY: The version display is critical for debugging and user experience.
754
+ This implementation ensures we always show the correct version rather than
755
+ defaulting to v0.0.0, even in edge cases where imports might fail.
756
+
757
+ DESIGN DECISION: We try multiple methods in order of preference:
758
+ 1. Package import (__version__) - fastest for normal installations
759
+ 2. importlib.metadata - standard for installed packages
760
+ 3. VERSION file reading - fallback for development environments
761
+ 4. Only then default to v0.0.0 with detailed error logging
762
+
763
+ Returns version string formatted as "vX.Y.Z"
764
+ """
765
+ version = "0.0.0"
766
+ method_used = "default"
767
+
768
+ # Method 1: Try package import (fastest, most common)
769
+ try:
770
+ from claude_mpm import __version__
771
+ version = __version__
772
+ method_used = "package_import"
773
+ self.logger.debug(f"Version obtained via package import: {version}")
774
+ except ImportError as e:
775
+ self.logger.debug(f"Package import failed: {e}")
776
+ except Exception as e:
777
+ self.logger.warning(f"Unexpected error in package import: {e}")
778
+
779
+ # Method 2: Try importlib.metadata (standard for installed packages)
780
+ if version == "0.0.0":
781
+ try:
782
+ import importlib.metadata
783
+ version = importlib.metadata.version('claude-mpm')
784
+ method_used = "importlib_metadata"
785
+ self.logger.debug(f"Version obtained via importlib.metadata: {version}")
786
+ except importlib.metadata.PackageNotFoundError:
787
+ self.logger.debug("Package not found in importlib.metadata (likely development install)")
788
+ except ImportError:
789
+ self.logger.debug("importlib.metadata not available (Python < 3.8)")
790
+ except Exception as e:
791
+ self.logger.warning(f"Unexpected error in importlib.metadata: {e}")
792
+
793
+ # Method 3: Try reading VERSION file directly (development fallback)
794
+ if version == "0.0.0":
795
+ try:
796
+ # Calculate path relative to this file
797
+ version_file = Path(__file__).parent.parent.parent.parent / "VERSION"
798
+ if version_file.exists():
799
+ version = version_file.read_text().strip()
800
+ method_used = "version_file"
801
+ self.logger.debug(f"Version obtained via VERSION file: {version}")
802
+ else:
803
+ self.logger.debug(f"VERSION file not found at: {version_file}")
804
+ except Exception as e:
805
+ self.logger.warning(f"Failed to read VERSION file: {e}")
806
+
807
+ # Log final result
808
+ if version == "0.0.0":
809
+ self.logger.error(
810
+ "All version detection methods failed. This indicates a packaging or installation issue."
811
+ )
812
+ else:
813
+ self.logger.debug(f"Final version: {version} (method: {method_used})")
814
+
815
+ return f"v{version}"
816
+
753
817
  def _register_memory_hooks(self):
754
818
  """Register memory integration hooks with the hook service.
755
819
 
@@ -184,12 +184,8 @@ class ClaudeRunner:
184
184
  self.logger.warning(f"Failed to connect to Socket.IO server: {e}")
185
185
  self.websocket_server = None
186
186
 
187
- # Get version
188
- try:
189
- from claude_mpm import __version__
190
- version_str = f"v{__version__}"
191
- except:
192
- version_str = "v0.0.0"
187
+ # Get version with robust fallback mechanisms
188
+ version_str = self._get_version()
193
189
 
194
190
  # Print styled welcome box
195
191
  print("\033[32m╭───────────────────────────────────────────────────╮\033[0m")
@@ -750,6 +746,74 @@ class ClaudeRunner:
750
746
  except Exception as e:
751
747
  self.logger.debug(f"Failed to log session event: {e}")
752
748
 
749
+ def _get_version(self) -> str:
750
+ """
751
+ Robust version determination with multiple fallback mechanisms.
752
+
753
+ WHY: The version display is critical for debugging and user experience.
754
+ This implementation ensures we always show the correct version rather than
755
+ defaulting to v0.0.0, even in edge cases where imports might fail.
756
+
757
+ DESIGN DECISION: We try multiple methods in order of preference:
758
+ 1. Package import (__version__) - fastest for normal installations
759
+ 2. importlib.metadata - standard for installed packages
760
+ 3. VERSION file reading - fallback for development environments
761
+ 4. Only then default to v0.0.0 with detailed error logging
762
+
763
+ Returns version string formatted as "vX.Y.Z"
764
+ """
765
+ version = "0.0.0"
766
+ method_used = "default"
767
+
768
+ # Method 1: Try package import (fastest, most common)
769
+ try:
770
+ from claude_mpm import __version__
771
+ version = __version__
772
+ method_used = "package_import"
773
+ self.logger.debug(f"Version obtained via package import: {version}")
774
+ except ImportError as e:
775
+ self.logger.debug(f"Package import failed: {e}")
776
+ except Exception as e:
777
+ self.logger.warning(f"Unexpected error in package import: {e}")
778
+
779
+ # Method 2: Try importlib.metadata (standard for installed packages)
780
+ if version == "0.0.0":
781
+ try:
782
+ import importlib.metadata
783
+ version = importlib.metadata.version('claude-mpm')
784
+ method_used = "importlib_metadata"
785
+ self.logger.debug(f"Version obtained via importlib.metadata: {version}")
786
+ except importlib.metadata.PackageNotFoundError:
787
+ self.logger.debug("Package not found in importlib.metadata (likely development install)")
788
+ except ImportError:
789
+ self.logger.debug("importlib.metadata not available (Python < 3.8)")
790
+ except Exception as e:
791
+ self.logger.warning(f"Unexpected error in importlib.metadata: {e}")
792
+
793
+ # Method 3: Try reading VERSION file directly (development fallback)
794
+ if version == "0.0.0":
795
+ try:
796
+ # Calculate path relative to this file
797
+ version_file = Path(__file__).parent.parent.parent.parent / "VERSION"
798
+ if version_file.exists():
799
+ version = version_file.read_text().strip()
800
+ method_used = "version_file"
801
+ self.logger.debug(f"Version obtained via VERSION file: {version}")
802
+ else:
803
+ self.logger.debug(f"VERSION file not found at: {version_file}")
804
+ except Exception as e:
805
+ self.logger.warning(f"Failed to read VERSION file: {e}")
806
+
807
+ # Log final result
808
+ if version == "0.0.0":
809
+ self.logger.error(
810
+ "All version detection methods failed. This indicates a packaging or installation issue."
811
+ )
812
+ else:
813
+ self.logger.debug(f"Final version: {version} (method: {method_used})")
814
+
815
+ return f"v{version}"
816
+
753
817
  def _register_memory_hooks(self):
754
818
  """Register memory integration hooks with the hook service.
755
819
 
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Pure Python daemon management for Socket.IO server.
4
+ No external dependencies required.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import time
10
+ import signal
11
+ import subprocess
12
+ import psutil
13
+ import json
14
+ from pathlib import Path
15
+
16
+ # Handle imports for both development and installed scenarios
17
+ script_dir = Path(__file__).parent
18
+ try:
19
+ # When installed as package, this should work directly
20
+ from claude_mpm.services.socketio_server import SocketIOServer
21
+ except ImportError:
22
+ # When in development, add src to path
23
+ project_root = script_dir.parent.parent.parent # from scripts -> claude_mpm -> src -> project_root
24
+ src_path = project_root / "src"
25
+ if src_path.exists():
26
+ sys.path.insert(0, str(src_path))
27
+ from claude_mpm.services.socketio_server import SocketIOServer
28
+
29
+ PID_FILE = Path.home() / ".claude-mpm" / "socketio-server.pid"
30
+ LOG_FILE = Path.home() / ".claude-mpm" / "socketio-server.log"
31
+
32
+ def ensure_dirs():
33
+ """Ensure required directories exist."""
34
+ PID_FILE.parent.mkdir(parents=True, exist_ok=True)
35
+
36
+ def is_running():
37
+ """Check if server is already running."""
38
+ if not PID_FILE.exists():
39
+ return False
40
+
41
+ try:
42
+ with open(PID_FILE) as f:
43
+ pid = int(f.read().strip())
44
+
45
+ # Check if process exists
46
+ process = psutil.Process(pid)
47
+ return process.is_running()
48
+ except (ValueError, psutil.NoSuchProcess, psutil.AccessDenied):
49
+ # Clean up stale PID file
50
+ PID_FILE.unlink(missing_ok=True)
51
+ return False
52
+
53
+ def start_server():
54
+ """Start the Socket.IO server as a daemon with conflict detection."""
55
+ if is_running():
56
+ print("Socket.IO daemon server is already running.")
57
+ print(f"Use '{__file__} status' for details")
58
+ return
59
+
60
+ # Check for HTTP-managed server conflict
61
+ try:
62
+ import requests
63
+ response = requests.get("http://localhost:8765/health", timeout=1.0)
64
+ if response.status_code == 200:
65
+ data = response.json()
66
+ if 'server_id' in data:
67
+ print(f"⚠️ HTTP-managed server already running: {data.get('server_id')}")
68
+ print(f" Stop it first: socketio_server_manager.py stop --port 8765")
69
+ print(f" Or diagnose: socketio_server_manager.py diagnose")
70
+ return
71
+ except:
72
+ pass # No HTTP server, continue
73
+
74
+ ensure_dirs()
75
+
76
+ # Fork to create daemon
77
+ pid = os.fork()
78
+ if pid > 0:
79
+ # Parent process
80
+ print(f"Starting Socket.IO server (PID: {pid})...")
81
+ with open(PID_FILE, 'w') as f:
82
+ f.write(str(pid))
83
+ print("Socket.IO server started successfully.")
84
+ print(f"PID file: {PID_FILE}")
85
+ print(f"Log file: {LOG_FILE}")
86
+ sys.exit(0)
87
+
88
+ # Child process - become daemon
89
+ os.setsid()
90
+ os.umask(0)
91
+
92
+ # Redirect stdout/stderr to log file
93
+ with open(LOG_FILE, 'a') as log:
94
+ os.dup2(log.fileno(), sys.stdout.fileno())
95
+ os.dup2(log.fileno(), sys.stderr.fileno())
96
+
97
+ # Start server
98
+ print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Starting Socket.IO server...")
99
+ server = SocketIOServer(host="localhost", port=8765)
100
+
101
+ # Handle signals
102
+ def signal_handler(signum, frame):
103
+ print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Received signal {signum}, shutting down...")
104
+ server.stop()
105
+ PID_FILE.unlink(missing_ok=True)
106
+ sys.exit(0)
107
+
108
+ signal.signal(signal.SIGTERM, signal_handler)
109
+ signal.signal(signal.SIGINT, signal_handler)
110
+
111
+ # Start server
112
+ server.start()
113
+
114
+ # Keep running
115
+ try:
116
+ while True:
117
+ time.sleep(1)
118
+ except KeyboardInterrupt:
119
+ signal_handler(signal.SIGINT, None)
120
+
121
+ def stop_server():
122
+ """Stop the Socket.IO daemon server."""
123
+ if not is_running():
124
+ print("Socket.IO daemon server is not running.")
125
+ print(f"Check for other servers: socketio_server_manager.py status")
126
+ return
127
+
128
+ try:
129
+ with open(PID_FILE) as f:
130
+ pid = int(f.read().strip())
131
+
132
+ print(f"Stopping Socket.IO server (PID: {pid})...")
133
+ os.kill(pid, signal.SIGTERM)
134
+
135
+ # Wait for process to stop
136
+ for _ in range(10):
137
+ if not is_running():
138
+ print("Socket.IO server stopped successfully.")
139
+ PID_FILE.unlink(missing_ok=True)
140
+ return
141
+ time.sleep(0.5)
142
+
143
+ # Force kill if still running
144
+ print("Server didn't stop gracefully, forcing...")
145
+ os.kill(pid, signal.SIGKILL)
146
+ PID_FILE.unlink(missing_ok=True)
147
+
148
+ except Exception as e:
149
+ print(f"Error stopping server: {e}")
150
+
151
+ def status_server():
152
+ """Check server status with manager integration info."""
153
+ if is_running():
154
+ with open(PID_FILE) as f:
155
+ pid = int(f.read().strip())
156
+ print(f"Socket.IO daemon server is running (PID: {pid})")
157
+ print(f"PID file: {PID_FILE}")
158
+
159
+ # Check if port is listening
160
+ try:
161
+ import socket
162
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
163
+ result = sock.connect_ex(('localhost', 8765))
164
+ sock.close()
165
+ if result == 0:
166
+ print("✅ Server is listening on port 8765")
167
+ print("🔧 Management style: daemon")
168
+ else:
169
+ print("⚠️ WARNING: Server process exists but port 8765 is not accessible")
170
+ except:
171
+ pass
172
+
173
+ # Show management commands
174
+ print("\n🔧 Management Commands:")
175
+ print(f" • Stop: {__file__} stop")
176
+ print(f" • Restart: {__file__} restart")
177
+
178
+ # Check for manager conflicts
179
+ try:
180
+ import requests
181
+ response = requests.get("http://localhost:8765/health", timeout=1.0)
182
+ if response.status_code == 200:
183
+ data = response.json()
184
+ if 'server_id' in data and data.get('server_id') != 'daemon-socketio':
185
+ print(f"\n⚠️ POTENTIAL CONFLICT: HTTP-managed server also detected")
186
+ print(f" Server ID: {data.get('server_id')}")
187
+ print(f" Use 'socketio_server_manager.py diagnose' to resolve")
188
+ except:
189
+ pass
190
+
191
+ else:
192
+ print("Socket.IO daemon server is not running")
193
+ print(f"\n🔧 Start Commands:")
194
+ print(f" • Daemon: {__file__} start")
195
+ print(f" • HTTP-managed: socketio_server_manager.py start")
196
+
197
+ def main():
198
+ """Main entry point."""
199
+ if len(sys.argv) < 2:
200
+ print("Usage: socketio-daemon.py {start|stop|restart|status}")
201
+ sys.exit(1)
202
+
203
+ command = sys.argv[1]
204
+
205
+ if command == "start":
206
+ start_server()
207
+ elif command == "stop":
208
+ stop_server()
209
+ elif command == "restart":
210
+ stop_server()
211
+ time.sleep(1)
212
+ start_server()
213
+ elif command == "status":
214
+ status_server()
215
+ else:
216
+ print(f"Unknown command: {command}")
217
+ print("Usage: socketio-daemon.py {start|stop|restart|status}")
218
+ sys.exit(1)
219
+
220
+ if __name__ == "__main__":
221
+ # Install psutil if not available
222
+ try:
223
+ import psutil
224
+ except ImportError:
225
+ print("Installing psutil...")
226
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "psutil"])
227
+ import psutil
228
+
229
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-mpm
3
- Version: 3.4.6
3
+ Version: 3.4.7
4
4
  Summary: Claude Multi-agent Project Manager - Clean orchestration with ticket management
5
5
  Home-page: https://github.com/bobmatnyc/claude-mpm
6
6
  Author: Claude MPM Team
@@ -57,7 +57,7 @@ claude_mpm/core/agent_name_normalizer.py,sha256=-X68oz3_74t9BRbHA54NEGyjy0xjTsGp
57
57
  claude_mpm/core/agent_registry.py,sha256=4MAfc7xDlrYWWkHmDMRmWE8q32teuCmIcud9D0I0dLo,20496
58
58
  claude_mpm/core/agent_session_manager.py,sha256=6alXQr4gnMR-unT4J1ryEtTxJqQolA0-NgPQN6X3lqY,11212
59
59
  claude_mpm/core/base_service.py,sha256=qWI_rUybHmmKroptJxcE4rzPBhK8yeMKIt2JqnqJB7E,29125
60
- claude_mpm/core/claude_runner.py,sha256=kVfA_U1g-DKCEW7f_ZbscvrhVOj4DP_L7jSx8-_dkM8,40933
60
+ claude_mpm/core/claude_runner.py,sha256=qcbjmRSGD3R_Kt9OmHBexZMY8RpCRbtXRKZAJ31ZGUU,44076
61
61
  claude_mpm/core/config.py,sha256=lrgaTSHXw558AFwzEHElqPc7tUALFoXTZXisQ-_WRWo,20329
62
62
  claude_mpm/core/config_aliases.py,sha256=8eqA4wpWViIDm_9pL3f9j7cR_ssmhOYYiY2UzHrfUzg,10058
63
63
  claude_mpm/core/container.py,sha256=P4c4nSo_USSfHTxvpR1sQkVGNsgqozZBN27l3IXqiDc,12216
@@ -72,7 +72,7 @@ claude_mpm/core/mixins.py,sha256=rTEH-7FDpNiLB8oo6mSb0CLarJklv4fDJw1xM-gr5wI,559
72
72
  claude_mpm/core/pm_hook_interceptor.py,sha256=PRaloqgxn-Alt9HflrywYXRL2GL3Ixb8Wxov8GfAMMU,7173
73
73
  claude_mpm/core/service_registry.py,sha256=wKJUO1g4UFA4dUpE3RkIYz1Ek8kIh4XfvU1kFeLCl2Q,10529
74
74
  claude_mpm/core/session_manager.py,sha256=D6ZA7bHAgfdkv0nLKjza0FKDng5iqi___IESrb3nSuk,8292
75
- claude_mpm/core/simple_runner.py,sha256=kVfA_U1g-DKCEW7f_ZbscvrhVOj4DP_L7jSx8-_dkM8,40933
75
+ claude_mpm/core/simple_runner.py,sha256=qcbjmRSGD3R_Kt9OmHBexZMY8RpCRbtXRKZAJ31ZGUU,44076
76
76
  claude_mpm/core/socketio_pool.py,sha256=B83uDsmqRF5S0QDwwatyKS-m2SdTvotCVfc3_2uQxd8,22438
77
77
  claude_mpm/core/tool_access_control.py,sha256=htZbDhC8s7D7BVqfmk0BwRrYJnlnUAk8_NeJKOaeNlg,6632
78
78
  claude_mpm/dashboard/open_dashboard.py,sha256=aXUc6LzUMwmTQMkl_h2jjvICimr-ED4FPMHP_9mnrgQ,1108
@@ -113,6 +113,7 @@ claude_mpm/orchestration/archive/subprocess_orchestrator.py,sha256=TYTAHX6p4OpgB
113
113
  claude_mpm/orchestration/archive/system_prompt_orchestrator.py,sha256=R16sc-94kQVeGjJzTYmvKn0aYgj_9qxyzShDy1E5zpE,12853
114
114
  claude_mpm/orchestration/archive/wrapper_orchestrator.py,sha256=cvL0NJf9kCWf3QJl67ySwvtR1Hd9Rym28Ii8Rtsdi6Q,6806
115
115
  claude_mpm/schemas/workflow_validator.py,sha256=qRgGodJoIZQaLfZ8OzWz3Y9eVNz3ckrQwkJ2RvccxAs,17175
116
+ claude_mpm/scripts/socketio_daemon.py,sha256=xV2COFBtj2DZ2-hixfCw5beqz2dh4CEgynOtgmVsUdA,7459
116
117
  claude_mpm/services/__init__.py,sha256=dcZ5U4xQlk-zpAy8CLTuEcXzKDfHT0KdJf3bYSmZ1BM,1904
117
118
  claude_mpm/services/agent_capabilities_generator.py,sha256=hWG0zV2InmzrDMxSbQzjVBBTzEaxg0bFxl8tmTMJ8qA,6565
118
119
  claude_mpm/services/agent_deployment.py,sha256=DtK1BX2yCrutUkQdVPD01mYHm-ya36l3EPOnEcaDfog,67961
@@ -192,9 +193,9 @@ claude_mpm/utils/path_operations.py,sha256=6pLMnAWBVzHkgp6JyQHmHbGD-dWn-nX21yV4E
192
193
  claude_mpm/utils/paths.py,sha256=Xv0SZWdZRkRjN9e6clBcA165ya00GNQxt7SwMz51tfA,10153
193
194
  claude_mpm/validation/__init__.py,sha256=bJ19g9lnk7yIjtxzN8XPegp87HTFBzCrGQOpFgRTf3g,155
194
195
  claude_mpm/validation/agent_validator.py,sha256=GCA2b2rKhKDeaNyUqWxTiWIs3sDdWjD9cgOFRp9K6ic,18227
195
- claude_mpm-3.4.6.dist-info/licenses/LICENSE,sha256=cSdDfXjoTVhstrERrqme4zgxAu4GubU22zVEHsiXGxs,1071
196
- claude_mpm-3.4.6.dist-info/METADATA,sha256=yoTuc1u-MzevEMn4wfSEQK51A_nQodUAS4eGiBZ8Exg,6523
197
- claude_mpm-3.4.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
198
- claude_mpm-3.4.6.dist-info/entry_points.txt,sha256=3_d7wLrg9sRmQ1SfrFGWoTNL8Wrd6lQb2XVSYbTwRIg,324
199
- claude_mpm-3.4.6.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
200
- claude_mpm-3.4.6.dist-info/RECORD,,
196
+ claude_mpm-3.4.7.dist-info/licenses/LICENSE,sha256=cSdDfXjoTVhstrERrqme4zgxAu4GubU22zVEHsiXGxs,1071
197
+ claude_mpm-3.4.7.dist-info/METADATA,sha256=tT9hiWd9pr90NS5y_UbVARexJZv7rFW09EdGSnwJttQ,6523
198
+ claude_mpm-3.4.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
199
+ claude_mpm-3.4.7.dist-info/entry_points.txt,sha256=3_d7wLrg9sRmQ1SfrFGWoTNL8Wrd6lQb2XVSYbTwRIg,324
200
+ claude_mpm-3.4.7.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
201
+ claude_mpm-3.4.7.dist-info/RECORD,,