claude-mpm 4.7.4__py3-none-any.whl → 4.18.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.
Files changed (308) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +118 -0
  3. claude_mpm/agents/BASE_ENGINEER.md +286 -0
  4. claude_mpm/agents/BASE_PM.md +106 -1
  5. claude_mpm/agents/OUTPUT_STYLE.md +329 -11
  6. claude_mpm/agents/PM_INSTRUCTIONS.md +397 -459
  7. claude_mpm/agents/agent_loader.py +17 -5
  8. claude_mpm/agents/frontmatter_validator.py +284 -253
  9. claude_mpm/agents/templates/README.md +465 -0
  10. claude_mpm/agents/templates/agent-manager.json +4 -1
  11. claude_mpm/agents/templates/agentic-coder-optimizer.json +13 -3
  12. claude_mpm/agents/templates/api_qa.json +11 -2
  13. claude_mpm/agents/templates/circuit_breakers.md +638 -0
  14. claude_mpm/agents/templates/clerk-ops.json +12 -2
  15. claude_mpm/agents/templates/code_analyzer.json +8 -2
  16. claude_mpm/agents/templates/content-agent.json +358 -0
  17. claude_mpm/agents/templates/dart_engineer.json +15 -2
  18. claude_mpm/agents/templates/data_engineer.json +15 -2
  19. claude_mpm/agents/templates/documentation.json +10 -2
  20. claude_mpm/agents/templates/engineer.json +21 -1
  21. claude_mpm/agents/templates/gcp_ops_agent.json +12 -2
  22. claude_mpm/agents/templates/git_file_tracking.md +584 -0
  23. claude_mpm/agents/templates/golang_engineer.json +270 -0
  24. claude_mpm/agents/templates/imagemagick.json +4 -1
  25. claude_mpm/agents/templates/java_engineer.json +346 -0
  26. claude_mpm/agents/templates/local_ops_agent.json +1227 -6
  27. claude_mpm/agents/templates/memory_manager.json +4 -1
  28. claude_mpm/agents/templates/nextjs_engineer.json +141 -133
  29. claude_mpm/agents/templates/ops.json +12 -2
  30. claude_mpm/agents/templates/php-engineer.json +270 -174
  31. claude_mpm/agents/templates/pm_examples.md +474 -0
  32. claude_mpm/agents/templates/pm_red_flags.md +240 -0
  33. claude_mpm/agents/templates/product_owner.json +338 -0
  34. claude_mpm/agents/templates/project_organizer.json +14 -4
  35. claude_mpm/agents/templates/prompt-engineer.json +13 -2
  36. claude_mpm/agents/templates/python_engineer.json +174 -81
  37. claude_mpm/agents/templates/qa.json +11 -2
  38. claude_mpm/agents/templates/react_engineer.json +16 -3
  39. claude_mpm/agents/templates/refactoring_engineer.json +12 -2
  40. claude_mpm/agents/templates/research.json +34 -21
  41. claude_mpm/agents/templates/response_format.md +583 -0
  42. claude_mpm/agents/templates/ruby-engineer.json +129 -192
  43. claude_mpm/agents/templates/rust_engineer.json +270 -0
  44. claude_mpm/agents/templates/security.json +10 -2
  45. claude_mpm/agents/templates/svelte-engineer.json +225 -0
  46. claude_mpm/agents/templates/ticketing.json +10 -2
  47. claude_mpm/agents/templates/typescript_engineer.json +116 -125
  48. claude_mpm/agents/templates/validation_templates.md +312 -0
  49. claude_mpm/agents/templates/vercel_ops_agent.json +12 -2
  50. claude_mpm/agents/templates/version_control.json +12 -2
  51. claude_mpm/agents/templates/web_qa.json +11 -2
  52. claude_mpm/agents/templates/web_ui.json +15 -2
  53. claude_mpm/cli/__init__.py +34 -614
  54. claude_mpm/cli/commands/agent_manager.py +25 -12
  55. claude_mpm/cli/commands/agent_state_manager.py +186 -0
  56. claude_mpm/cli/commands/agents.py +235 -148
  57. claude_mpm/cli/commands/agents_detect.py +380 -0
  58. claude_mpm/cli/commands/agents_recommend.py +309 -0
  59. claude_mpm/cli/commands/aggregate.py +7 -3
  60. claude_mpm/cli/commands/analyze.py +9 -4
  61. claude_mpm/cli/commands/analyze_code.py +7 -2
  62. claude_mpm/cli/commands/auto_configure.py +570 -0
  63. claude_mpm/cli/commands/config.py +47 -13
  64. claude_mpm/cli/commands/configure.py +419 -1571
  65. claude_mpm/cli/commands/configure_agent_display.py +261 -0
  66. claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
  67. claude_mpm/cli/commands/configure_hook_manager.py +225 -0
  68. claude_mpm/cli/commands/configure_models.py +18 -0
  69. claude_mpm/cli/commands/configure_navigation.py +167 -0
  70. claude_mpm/cli/commands/configure_paths.py +104 -0
  71. claude_mpm/cli/commands/configure_persistence.py +254 -0
  72. claude_mpm/cli/commands/configure_startup_manager.py +646 -0
  73. claude_mpm/cli/commands/configure_template_editor.py +497 -0
  74. claude_mpm/cli/commands/configure_validators.py +73 -0
  75. claude_mpm/cli/commands/local_deploy.py +537 -0
  76. claude_mpm/cli/commands/memory.py +54 -20
  77. claude_mpm/cli/commands/mpm_init.py +585 -196
  78. claude_mpm/cli/commands/mpm_init_handler.py +37 -3
  79. claude_mpm/cli/commands/search.py +170 -4
  80. claude_mpm/cli/commands/upgrade.py +152 -0
  81. claude_mpm/cli/executor.py +202 -0
  82. claude_mpm/cli/helpers.py +105 -0
  83. claude_mpm/cli/interactive/__init__.py +3 -0
  84. claude_mpm/cli/interactive/skills_wizard.py +491 -0
  85. claude_mpm/cli/parsers/__init__.py +7 -1
  86. claude_mpm/cli/parsers/agents_parser.py +9 -0
  87. claude_mpm/cli/parsers/auto_configure_parser.py +245 -0
  88. claude_mpm/cli/parsers/base_parser.py +110 -3
  89. claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
  90. claude_mpm/cli/parsers/mpm_init_parser.py +65 -5
  91. claude_mpm/cli/shared/output_formatters.py +28 -19
  92. claude_mpm/cli/startup.py +481 -0
  93. claude_mpm/cli/utils.py +52 -1
  94. claude_mpm/commands/mpm-agents-detect.md +168 -0
  95. claude_mpm/commands/mpm-agents-recommend.md +214 -0
  96. claude_mpm/commands/mpm-agents.md +75 -1
  97. claude_mpm/commands/mpm-auto-configure.md +217 -0
  98. claude_mpm/commands/mpm-help.md +163 -0
  99. claude_mpm/commands/mpm-init.md +148 -3
  100. claude_mpm/commands/mpm-version.md +113 -0
  101. claude_mpm/commands/mpm.md +1 -0
  102. claude_mpm/config/agent_config.py +2 -2
  103. claude_mpm/config/model_config.py +428 -0
  104. claude_mpm/constants.py +1 -0
  105. claude_mpm/core/base_service.py +13 -12
  106. claude_mpm/core/enums.py +452 -0
  107. claude_mpm/core/factories.py +1 -1
  108. claude_mpm/core/instruction_reinforcement_hook.py +2 -1
  109. claude_mpm/core/interactive_session.py +9 -3
  110. claude_mpm/core/log_manager.py +2 -0
  111. claude_mpm/core/logging_config.py +6 -2
  112. claude_mpm/core/oneshot_session.py +8 -4
  113. claude_mpm/core/optimized_agent_loader.py +3 -3
  114. claude_mpm/core/output_style_manager.py +12 -192
  115. claude_mpm/core/service_registry.py +5 -1
  116. claude_mpm/core/types.py +2 -9
  117. claude_mpm/core/typing_utils.py +7 -6
  118. claude_mpm/dashboard/static/js/dashboard.js +0 -14
  119. claude_mpm/dashboard/templates/index.html +3 -41
  120. claude_mpm/hooks/__init__.py +20 -0
  121. claude_mpm/hooks/claude_hooks/event_handlers.py +4 -2
  122. claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
  123. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +23 -2
  124. claude_mpm/hooks/failure_learning/__init__.py +60 -0
  125. claude_mpm/hooks/failure_learning/failure_detection_hook.py +235 -0
  126. claude_mpm/hooks/failure_learning/fix_detection_hook.py +217 -0
  127. claude_mpm/hooks/failure_learning/learning_extraction_hook.py +286 -0
  128. claude_mpm/hooks/instruction_reinforcement.py +7 -2
  129. claude_mpm/hooks/kuzu_enrichment_hook.py +263 -0
  130. claude_mpm/hooks/kuzu_memory_hook.py +37 -12
  131. claude_mpm/hooks/kuzu_response_hook.py +183 -0
  132. claude_mpm/models/resume_log.py +340 -0
  133. claude_mpm/services/agents/__init__.py +18 -5
  134. claude_mpm/services/agents/auto_config_manager.py +796 -0
  135. claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
  136. claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
  137. claude_mpm/services/agents/deployment/agent_validator.py +17 -1
  138. claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
  139. claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
  140. claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
  141. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +7 -6
  142. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
  143. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
  144. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +5 -3
  145. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
  146. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +9 -6
  147. claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
  148. claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
  149. claude_mpm/services/agents/local_template_manager.py +1 -1
  150. claude_mpm/services/agents/memory/agent_memory_manager.py +5 -2
  151. claude_mpm/services/agents/observers.py +547 -0
  152. claude_mpm/services/agents/recommender.py +568 -0
  153. claude_mpm/services/agents/registry/modification_tracker.py +5 -2
  154. claude_mpm/services/command_handler_service.py +11 -5
  155. claude_mpm/services/core/__init__.py +33 -1
  156. claude_mpm/services/core/interfaces/__init__.py +90 -3
  157. claude_mpm/services/core/interfaces/agent.py +184 -0
  158. claude_mpm/services/core/interfaces/health.py +172 -0
  159. claude_mpm/services/core/interfaces/model.py +281 -0
  160. claude_mpm/services/core/interfaces/process.py +372 -0
  161. claude_mpm/services/core/interfaces/project.py +121 -0
  162. claude_mpm/services/core/interfaces/restart.py +307 -0
  163. claude_mpm/services/core/interfaces/stability.py +260 -0
  164. claude_mpm/services/core/memory_manager.py +11 -24
  165. claude_mpm/services/core/models/__init__.py +79 -0
  166. claude_mpm/services/core/models/agent_config.py +381 -0
  167. claude_mpm/services/core/models/health.py +162 -0
  168. claude_mpm/services/core/models/process.py +235 -0
  169. claude_mpm/services/core/models/restart.py +302 -0
  170. claude_mpm/services/core/models/stability.py +264 -0
  171. claude_mpm/services/core/models/toolchain.py +306 -0
  172. claude_mpm/services/core/path_resolver.py +23 -7
  173. claude_mpm/services/diagnostics/__init__.py +2 -2
  174. claude_mpm/services/diagnostics/checks/agent_check.py +25 -24
  175. claude_mpm/services/diagnostics/checks/claude_code_check.py +24 -23
  176. claude_mpm/services/diagnostics/checks/common_issues_check.py +25 -24
  177. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -23
  178. claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
  179. claude_mpm/services/diagnostics/checks/installation_check.py +30 -29
  180. claude_mpm/services/diagnostics/checks/instructions_check.py +20 -19
  181. claude_mpm/services/diagnostics/checks/mcp_check.py +50 -36
  182. claude_mpm/services/diagnostics/checks/mcp_services_check.py +38 -33
  183. claude_mpm/services/diagnostics/checks/monitor_check.py +23 -22
  184. claude_mpm/services/diagnostics/checks/startup_log_check.py +9 -8
  185. claude_mpm/services/diagnostics/diagnostic_runner.py +6 -5
  186. claude_mpm/services/diagnostics/doctor_reporter.py +28 -25
  187. claude_mpm/services/diagnostics/models.py +19 -24
  188. claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
  189. claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
  190. claude_mpm/services/infrastructure/monitoring/base.py +5 -13
  191. claude_mpm/services/infrastructure/monitoring/network.py +7 -6
  192. claude_mpm/services/infrastructure/monitoring/process.py +13 -12
  193. claude_mpm/services/infrastructure/monitoring/resources.py +7 -6
  194. claude_mpm/services/infrastructure/monitoring/service.py +16 -15
  195. claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
  196. claude_mpm/services/local_ops/__init__.py +163 -0
  197. claude_mpm/services/local_ops/crash_detector.py +257 -0
  198. claude_mpm/services/local_ops/health_checks/__init__.py +28 -0
  199. claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
  200. claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
  201. claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
  202. claude_mpm/services/local_ops/health_manager.py +430 -0
  203. claude_mpm/services/local_ops/log_monitor.py +396 -0
  204. claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
  205. claude_mpm/services/local_ops/process_manager.py +595 -0
  206. claude_mpm/services/local_ops/resource_monitor.py +331 -0
  207. claude_mpm/services/local_ops/restart_manager.py +401 -0
  208. claude_mpm/services/local_ops/restart_policy.py +387 -0
  209. claude_mpm/services/local_ops/state_manager.py +372 -0
  210. claude_mpm/services/local_ops/unified_manager.py +600 -0
  211. claude_mpm/services/mcp_config_manager.py +9 -4
  212. claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
  213. claude_mpm/services/mcp_gateway/core/base.py +18 -31
  214. claude_mpm/services/mcp_gateway/main.py +30 -0
  215. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +206 -32
  216. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +30 -28
  217. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +25 -5
  218. claude_mpm/services/mcp_service_verifier.py +1 -1
  219. claude_mpm/services/memory/failure_tracker.py +563 -0
  220. claude_mpm/services/memory_hook_service.py +165 -4
  221. claude_mpm/services/model/__init__.py +147 -0
  222. claude_mpm/services/model/base_provider.py +365 -0
  223. claude_mpm/services/model/claude_provider.py +412 -0
  224. claude_mpm/services/model/model_router.py +453 -0
  225. claude_mpm/services/model/ollama_provider.py +415 -0
  226. claude_mpm/services/monitor/daemon_manager.py +3 -2
  227. claude_mpm/services/monitor/handlers/dashboard.py +2 -1
  228. claude_mpm/services/monitor/handlers/hooks.py +2 -1
  229. claude_mpm/services/monitor/management/lifecycle.py +3 -2
  230. claude_mpm/services/monitor/server.py +2 -1
  231. claude_mpm/services/project/__init__.py +23 -0
  232. claude_mpm/services/project/detection_strategies.py +719 -0
  233. claude_mpm/services/project/toolchain_analyzer.py +581 -0
  234. claude_mpm/services/self_upgrade_service.py +342 -0
  235. claude_mpm/services/session_management_service.py +3 -2
  236. claude_mpm/services/session_manager.py +205 -1
  237. claude_mpm/services/shared/async_service_base.py +16 -27
  238. claude_mpm/services/shared/lifecycle_service_base.py +1 -14
  239. claude_mpm/services/socketio/handlers/__init__.py +5 -2
  240. claude_mpm/services/socketio/handlers/hook.py +13 -2
  241. claude_mpm/services/socketio/handlers/registry.py +4 -2
  242. claude_mpm/services/socketio/server/main.py +10 -8
  243. claude_mpm/services/subprocess_launcher_service.py +14 -5
  244. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +8 -7
  245. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +6 -5
  246. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +8 -7
  247. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +7 -6
  248. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +5 -4
  249. claude_mpm/services/unified/config_strategies/validation_strategy.py +13 -9
  250. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +10 -3
  251. claude_mpm/services/unified/deployment_strategies/local.py +6 -5
  252. claude_mpm/services/unified/deployment_strategies/utils.py +6 -5
  253. claude_mpm/services/unified/deployment_strategies/vercel.py +7 -6
  254. claude_mpm/services/unified/interfaces.py +3 -1
  255. claude_mpm/services/unified/unified_analyzer.py +14 -10
  256. claude_mpm/services/unified/unified_config.py +2 -1
  257. claude_mpm/services/unified/unified_deployment.py +9 -4
  258. claude_mpm/services/version_service.py +104 -1
  259. claude_mpm/skills/__init__.py +21 -0
  260. claude_mpm/skills/bundled/__init__.py +6 -0
  261. claude_mpm/skills/bundled/api-documentation.md +393 -0
  262. claude_mpm/skills/bundled/async-testing.md +571 -0
  263. claude_mpm/skills/bundled/code-review.md +143 -0
  264. claude_mpm/skills/bundled/database-migration.md +199 -0
  265. claude_mpm/skills/bundled/docker-containerization.md +194 -0
  266. claude_mpm/skills/bundled/express-local-dev.md +1429 -0
  267. claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
  268. claude_mpm/skills/bundled/git-workflow.md +414 -0
  269. claude_mpm/skills/bundled/imagemagick.md +204 -0
  270. claude_mpm/skills/bundled/json-data-handling.md +223 -0
  271. claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
  272. claude_mpm/skills/bundled/pdf.md +141 -0
  273. claude_mpm/skills/bundled/performance-profiling.md +567 -0
  274. claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
  275. claude_mpm/skills/bundled/security-scanning.md +327 -0
  276. claude_mpm/skills/bundled/systematic-debugging.md +473 -0
  277. claude_mpm/skills/bundled/test-driven-development.md +378 -0
  278. claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
  279. claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
  280. claude_mpm/skills/bundled/xlsx.md +157 -0
  281. claude_mpm/skills/registry.py +286 -0
  282. claude_mpm/skills/skill_manager.py +310 -0
  283. claude_mpm/storage/state_storage.py +15 -15
  284. claude_mpm/tools/code_tree_analyzer.py +177 -141
  285. claude_mpm/tools/code_tree_events.py +4 -2
  286. claude_mpm/utils/agent_dependency_loader.py +40 -20
  287. claude_mpm/utils/display_helper.py +260 -0
  288. claude_mpm/utils/git_analyzer.py +407 -0
  289. claude_mpm/utils/robust_installer.py +73 -19
  290. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/METADATA +129 -12
  291. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/RECORD +295 -193
  292. claude_mpm/dashboard/static/css/code-tree.css +0 -1639
  293. claude_mpm/dashboard/static/index-hub-backup.html +0 -713
  294. claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +0 -353
  295. claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +0 -235
  296. claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +0 -409
  297. claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +0 -435
  298. claude_mpm/dashboard/static/js/components/code-tree.js +0 -5869
  299. claude_mpm/dashboard/static/js/components/code-viewer.js +0 -1386
  300. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +0 -425
  301. claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1041
  302. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
  303. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
  304. claude_mpm/services/project/analyzer_refactored.py +0 -450
  305. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/WHEEL +0 -0
  306. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/entry_points.txt +0 -0
  307. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/licenses/LICENSE +0 -0
  308. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/top_level.txt +0 -0
@@ -1,286 +1,85 @@
1
- from pathlib import Path
2
- from typing import Optional
3
-
4
1
  """
5
2
  Claude MPM Command-Line Interface.
6
3
 
7
- WHY: This module serves as the main entry point for the CLI, coordinating
8
- argument parsing and command execution. It replaces the monolithic cli.py
9
- with a more modular structure.
4
+ Main entry point for CLI. Implementation details extracted to:
5
+ - cli/helpers.py: Configuration checks and prompts
6
+ - cli/startup.py: Initialization (registry, MCP, updates)
7
+ - cli/executor.py: Command execution routing
10
8
 
11
- DESIGN DECISION: We maintain backward compatibility by keeping the same
12
- interface while organizing code into logical modules. The main() function
13
- remains the primary entry point for both direct execution and package imports.
9
+ Refactored from 803 lines to <130 lines (TSK-0053).
14
10
  """
15
11
 
12
+ import os
16
13
  import sys
14
+ from pathlib import Path
15
+ from typing import Optional
17
16
 
18
17
  from claude_mpm.config.paths import paths
19
18
 
20
- from ..constants import CLICommands, LogLevel
21
- from .commands import ( # run_guarded_session is imported lazily to avoid loading experimental code
22
- aggregate_command,
23
- cleanup_memory,
24
- manage_agent_manager,
25
- manage_agents,
26
- manage_config,
27
- manage_configure,
28
- manage_debug,
29
- manage_mcp,
30
- manage_memory,
31
- manage_monitor,
32
- manage_tickets,
33
- run_doctor,
34
- run_session,
35
- show_info,
19
+ from ..constants import CLICommands
20
+ from .executor import ensure_run_attributes, execute_command
21
+ from .helpers import (
22
+ handle_missing_configuration,
23
+ has_configuration_file,
24
+ should_skip_config_check,
36
25
  )
37
- from .commands.analyze_code import manage_analyze_code
38
- from .commands.dashboard import manage_dashboard
39
26
  from .parser import create_parser, preprocess_args
27
+ from .startup import (
28
+ run_background_services,
29
+ setup_configure_command_environment,
30
+ setup_early_environment,
31
+ setup_mcp_server_logging,
32
+ should_skip_background_services,
33
+ )
40
34
  from .utils import ensure_directories, setup_logging
41
35
 
42
- # Get version using centralized path management
43
- # Try package VERSION file first (for installed packages)
36
+ # Version resolution
44
37
  package_version_file = Path(__file__).parent.parent / "VERSION"
45
38
  if package_version_file.exists():
46
39
  __version__ = package_version_file.read_text().strip()
47
- # Use centralized path management for VERSION file
48
40
  elif paths.version_file.exists():
49
41
  __version__ = paths.version_file.read_text().strip()
50
42
  else:
51
- # Try to import from package as fallback
52
43
  try:
53
44
  from .. import __version__
54
45
  except ImportError:
55
- # Default version if all else fails
56
46
  __version__ = "0.0.0"
57
47
 
58
48
 
59
- def _has_configuration_file() -> bool:
60
- """Check if any configuration file exists in standard locations."""
61
- config_paths = [
62
- Path.cwd() / ".claude-mpm" / "configuration.yaml",
63
- Path.cwd() / ".claude-mpm" / "configuration.yml",
64
- Path.home() / ".claude-mpm" / "configuration.yaml",
65
- Path.home() / ".claude-mpm" / "configuration.yml",
66
- ]
67
- return any(path.exists() for path in config_paths)
68
-
69
-
70
- def _is_interactive_session() -> bool:
71
- """Check if running in an interactive terminal."""
72
- return sys.stdin.isatty() and sys.stdout.isatty()
73
-
74
-
75
- def _should_skip_config_check(command: Optional[str]) -> bool:
76
- """Check if command should skip configuration check."""
77
- skip_commands = ["configure", "doctor", "info", "mcp", "config"]
78
- return command in skip_commands if command else False
79
-
80
-
81
- def _prompt_for_configuration() -> bool:
82
- """Prompt user to run configuration wizard.
83
-
84
- Returns:
85
- bool: True if user wants to configure, False otherwise
86
- """
87
- from rich.console import Console as RichConsole
88
- from rich.prompt import Confirm
89
-
90
- console = RichConsole()
91
-
92
- console.print()
93
- console.print("[yellow]⚙️ Configuration Recommended[/yellow]")
94
- console.print()
95
- console.print("Claude MPM works best with proper configuration.")
96
- console.print("The configurator helps you:")
97
- console.print(" • Enable/disable MCP services (ticketer, browser, vector-search)")
98
- console.print(" • Configure hook services (monitor, dashboard)")
99
- console.print(" • Select system agents to use")
100
- console.print()
101
-
102
- return Confirm.ask("Would you like to run the configurator now?", default=True)
103
-
104
-
105
49
  def main(argv: Optional[list] = None):
106
- """
107
- Main CLI entry point.
108
-
109
- WHY: This function orchestrates the entire CLI flow:
110
- 1. Ensures directories exist
111
- 2. Preprocesses arguments (handling --mpm: prefix)
112
- 3. Parses arguments
113
- 4. Sets up logging
114
- 5. Executes the appropriate command
115
-
116
- DESIGN DECISION: We keep error handling at this level to provide consistent
117
- error messages and exit codes across all commands.
118
-
119
- Args:
120
- argv: Optional list of command line arguments for testing
121
-
122
- Returns:
123
- Exit code (0 for success, non-zero for errors)
124
- """
125
- # Disable telemetry by default (set early in case any imported modules check it)
126
- import os
50
+ """Main CLI entry point orchestrating argument parsing and command execution."""
51
+ argv = setup_early_environment(argv)
127
52
 
128
- os.environ.setdefault("DISABLE_TELEMETRY", "1")
129
-
130
- # Set environment variable BEFORE parsing args to prevent cleanup noise during configure
131
- # This will be checked later after we know the command
132
- os.environ.setdefault("CLAUDE_MPM_SKIP_CLEANUP", "0")
133
-
134
- # EARLY CHECK: If 'configure' is in argv, suppress logging IMMEDIATELY
135
- # This must happen BEFORE any imports that trigger logging
136
- if argv is None:
137
- argv = sys.argv[1:]
138
- if "configure" in argv or (len(argv) > 0 and argv[0] == "configure"):
139
- import logging
140
-
141
- logging.getLogger("claude_mpm").setLevel(logging.WARNING)
142
- os.environ["CLAUDE_MPM_SKIP_CLEANUP"] = "1"
143
-
144
- # Parse args early to check if we should skip background services
145
- # (for commands like --version, --help, configure, etc.)
146
53
  parser = create_parser(version=__version__)
147
54
  processed_argv = preprocess_args(argv)
148
55
  args = parser.parse_args(processed_argv)
149
56
 
150
- # Check for configuration file on startup
151
- # Skip for help/version flags or specific commands
152
57
  help_version_flags = ["--version", "-v", "--help", "-h"]
153
58
  is_help_or_version = any(
154
59
  flag in (processed_argv or sys.argv[1:]) for flag in help_version_flags
155
60
  )
156
61
 
157
- if not _has_configuration_file() and not is_help_or_version:
158
- # Skip check for certain commands
159
- if not _should_skip_config_check(getattr(args, "command", None)):
160
- if _is_interactive_session():
161
- # Interactive: Offer to run configurator
162
- if _prompt_for_configuration():
163
- # User wants to configure - run configure command
164
- from rich.console import Console as RichConsole
165
-
166
- from .commands.configure import ConfigureCommand
167
-
168
- console = RichConsole()
169
- config_cmd = ConfigureCommand()
170
- try:
171
- config_cmd.execute({})
172
- console.print(
173
- "\n[green]✓[/green] Configuration complete! "
174
- "Continuing with command...\n"
175
- )
176
- except Exception as e:
177
- console.print(f"\n[yellow]⚠[/yellow] Configuration error: {e}")
178
- console.print(
179
- "[yellow]Continuing with default "
180
- "configuration...\n[/yellow]"
181
- )
182
- else:
183
- from rich.console import Console as RichConsole
184
-
185
- console = RichConsole()
186
- console.print("\n[dim]Using default configuration.[/dim]")
187
- console.print(
188
- "[dim]Run 'claude-mpm configure' anytime to set up your "
189
- "preferences.\n[/dim]"
190
- )
191
- else:
192
- # Non-interactive: Log message only
193
- import logging
194
-
195
- logger = logging.getLogger(__name__)
196
- logger.info(
197
- "Configuration file not found, using defaults. "
198
- "Run 'claude-mpm configure' to customize."
199
- )
200
-
201
- # Skip background services for configure command - it needs clean state
202
- # Also skip for help/version/diagnostic commands
203
- skip_background_services_commands = ["--version", "-v", "--help", "-h"]
204
- should_skip_background = any(
205
- cmd in (processed_argv or sys.argv[1:])
206
- for cmd in skip_background_services_commands
207
- ) or (
208
- hasattr(args, "command")
209
- and args.command in ["info", "doctor", "config", "mcp", "configure"]
210
- )
211
-
212
- # Set environment variable to skip cleanup for configure command
213
- # Also suppress INFO logging to prevent noise during configure
214
- if hasattr(args, "command") and args.command == "configure":
215
- os.environ["CLAUDE_MPM_SKIP_CLEANUP"] = "1"
216
- # Suppress INFO logs for configure command
217
- import logging
218
-
219
- logging.getLogger("claude_mpm").setLevel(logging.WARNING)
220
-
221
- # Only initialize background services if not skipping
222
- if not should_skip_background:
223
- # Ensure directories are initialized on first run
224
- ensure_directories()
225
-
226
- # Initialize or update project registry
227
- _initialize_project_registry()
228
-
229
- # Check for MCP auto-configuration (pipx installations)
230
- _check_mcp_auto_configuration()
62
+ if not has_configuration_file() and not is_help_or_version:
63
+ if not should_skip_config_check(getattr(args, "command", None)):
64
+ handle_missing_configuration()
231
65
 
232
- # Re-enabled: MCP pre-warming is safe with subprocess daemon (v4.2.40)
233
- # The subprocess approach avoids fork() issues entirely
234
- _verify_mcp_gateway_startup()
235
- else:
236
- # Still need directories for configure command to work
237
- ensure_directories()
66
+ setup_configure_command_environment(args)
238
67
 
239
- # Set up logging
240
- # Special case: For MCP start command, we need minimal logging to avoid stdout interference
241
- if (
242
- args.command == CLICommands.MCP.value
243
- and getattr(args, "mcp_command", None) == "start"
244
- ):
245
- # For MCP server, configure minimal stderr-only logging
246
- import logging
68
+ ensure_directories()
69
+ if not should_skip_background_services(args, processed_argv):
70
+ run_background_services()
71
+ logger = setup_mcp_server_logging(args)
247
72
 
248
- # sys is already imported at module level
249
- # Only log errors to stderr for MCP server
250
- if not getattr(args, "test", False) and not getattr(
251
- args, "instructions", False
252
- ):
253
- # Production MCP mode - minimal logging
254
- logging.basicConfig(
255
- level=logging.ERROR, format="%(message)s", stream=sys.stderr, force=True
256
- )
257
- logger = logging.getLogger("claude_mpm")
258
- else:
259
- # Test or instructions mode - normal logging
260
- logger = setup_logging(args)
261
- else:
262
- # Normal logging for all other commands
263
- logger = setup_logging(args)
264
-
265
- # Debug output if requested
266
73
  if hasattr(args, "debug") and args.debug:
267
74
  logger.debug(f"Command: {args.command}")
268
75
  logger.debug(f"Arguments: {args}")
269
76
 
270
- # Hook system note: Claude Code hooks are handled externally via the
271
- # hook_handler.py script installed in ~/.claude/settings.json
272
- # The --no-hooks flag is kept for backward compatibility but doesn't affect
273
- # Claude Code hooks which are configured separately.
274
-
275
- # Default to run command if no command specified
276
77
  if not args.command:
277
78
  args.command = CLICommands.RUN.value
278
- # Ensure run-specific attributes exist when defaulting to run
279
- _ensure_run_attributes(args)
79
+ ensure_run_attributes(args)
280
80
 
281
- # Execute command
282
81
  try:
283
- return _execute_command(args.command, args)
82
+ return execute_command(args.command, args)
284
83
  except KeyboardInterrupt:
285
84
  logger.info("Session interrupted by user")
286
85
  return 0
@@ -293,385 +92,6 @@ def main(argv: Optional[list] = None):
293
92
  return 1
294
93
 
295
94
 
296
- def _initialize_project_registry():
297
- """
298
- Initialize or update the project registry for the current session.
299
-
300
- WHY: The project registry tracks all claude-mpm projects and their metadata
301
- across sessions. This function ensures the current project is properly
302
- registered and updates session information.
303
-
304
- DESIGN DECISION: Registry failures are logged but don't prevent startup
305
- to ensure claude-mpm remains functional even if registry operations fail.
306
- """
307
- try:
308
- from ..services.project.registry import ProjectRegistry
309
-
310
- registry = ProjectRegistry()
311
- registry.get_or_create_project_entry()
312
- except Exception as e:
313
- # Import logger here to avoid circular imports
314
- from ..core.logger import get_logger
315
-
316
- logger = get_logger("cli")
317
- logger.debug(f"Failed to initialize project registry: {e}")
318
- # Continue execution - registry failure shouldn't block startup
319
-
320
-
321
- def _check_mcp_auto_configuration():
322
- """
323
- Check and potentially auto-configure MCP for pipx installations.
324
-
325
- WHY: Users installing via pipx should have MCP work out-of-the-box with
326
- minimal friction. This function offers one-time auto-configuration with
327
- user consent.
328
-
329
- DESIGN DECISION: This is blocking but quick - it only runs once and has
330
- a 10-second timeout. We want to catch users on first run for the best
331
- experience.
332
- """
333
- try:
334
- from ..services.mcp_gateway.auto_configure import check_and_configure_mcp
335
-
336
- # This function handles all the logic:
337
- # - Checks if already configured
338
- # - Checks if pipx installation
339
- # - Checks if already asked before
340
- # - Prompts user if needed
341
- # - Configures if user agrees
342
- check_and_configure_mcp()
343
-
344
- except Exception as e:
345
- # Non-critical - log but don't fail
346
- from ..core.logger import get_logger
347
-
348
- logger = get_logger("cli")
349
- logger.debug(f"MCP auto-configuration check failed: {e}")
350
-
351
- # Skip MCP service fixes for the doctor and configure commands
352
- # The doctor command performs its own comprehensive MCP service check
353
- # The configure command allows users to configure which services to enable
354
- # Running both would cause duplicate checks and log messages (9 seconds apart)
355
- import sys
356
-
357
- if len(sys.argv) > 1 and sys.argv[1] in ("doctor", "configure"):
358
- return
359
-
360
- # Also ensure MCP services are properly configured in ~/.claude.json
361
- # This fixes incorrect paths and adds missing services
362
- try:
363
- from ..core.logger import get_logger
364
- from ..services.mcp_config_manager import MCPConfigManager
365
-
366
- logger = get_logger("cli")
367
- mcp_manager = MCPConfigManager()
368
-
369
- # Fix any corrupted installations first
370
- fix_success, fix_message = mcp_manager.fix_mcp_service_issues()
371
- if fix_message and "Fixed:" in fix_message:
372
- logger.info(f"MCP service fixes applied: {fix_message}")
373
-
374
- # Ensure all services are configured correctly
375
- config_success, config_message = mcp_manager.ensure_mcp_services_configured()
376
- if config_message and "Added MCP services" in config_message:
377
- logger.info(f"MCP services configured: {config_message}")
378
-
379
- except Exception as e:
380
- # Non-critical - log but don't fail
381
- from ..core.logger import get_logger
382
-
383
- logger = get_logger("cli")
384
- logger.debug(f"MCP services configuration update failed: {e}")
385
-
386
-
387
- def _verify_mcp_gateway_startup():
388
- """
389
- Verify MCP Gateway configuration on startup and pre-warm MCP services.
390
-
391
- WHY: The MCP gateway should be automatically configured and verified on startup
392
- to provide a seamless experience with diagnostic tools, file summarizer, and
393
- ticket service. Pre-warming MCP services eliminates the 11.9s delay on first use.
394
-
395
- DESIGN DECISION: This is non-blocking - failures are logged but don't prevent
396
- startup to ensure claude-mpm remains functional even if MCP gateway has issues.
397
- """
398
- # Quick verification of MCP services installation
399
- try:
400
- from ..core.logger import get_logger
401
- from ..services.mcp_service_verifier import verify_mcp_services_on_startup
402
-
403
- logger = get_logger("mcp_verify")
404
- all_ok, message = verify_mcp_services_on_startup()
405
- if not all_ok:
406
- logger.warning(message)
407
- except Exception:
408
- # Non-critical - continue with startup
409
- pass
410
-
411
- try:
412
- import asyncio
413
- import time
414
-
415
- from ..core.logger import get_logger
416
- from ..services.mcp_gateway.core.process_pool import pre_warm_mcp_servers
417
- from ..services.mcp_gateway.core.startup_verification import (
418
- is_mcp_gateway_configured,
419
- verify_mcp_gateway_on_startup,
420
- )
421
-
422
- logger = get_logger("mcp_prewarm")
423
-
424
- # Quick check first - if already configured, skip detailed verification
425
- gateway_configured = is_mcp_gateway_configured()
426
-
427
- # DISABLED: Pre-warming MCP servers can interfere with Claude Code's MCP management
428
- # This was causing issues with MCP server initialization and stderr handling
429
- # def run_pre_warming():
430
- # loop = None
431
- # try:
432
- # start_time = time.time()
433
- # loop = asyncio.new_event_loop()
434
- # asyncio.set_event_loop(loop)
435
- #
436
- # # Pre-warm MCP servers (especially vector search)
437
- # logger.info("Pre-warming MCP servers to eliminate startup delay...")
438
- # loop.run_until_complete(pre_warm_mcp_servers())
439
- #
440
- # pre_warm_time = time.time() - start_time
441
- # if pre_warm_time > 1.0:
442
- # logger.info(f"MCP servers pre-warmed in {pre_warm_time:.2f}s")
443
-
444
- # Dummy function to maintain structure
445
- def run_pre_warming():
446
- loop = None
447
- try:
448
- time.time()
449
- loop = asyncio.new_event_loop()
450
- asyncio.set_event_loop(loop)
451
-
452
- # Also run gateway verification if needed
453
- if not gateway_configured:
454
- loop.run_until_complete(verify_mcp_gateway_on_startup())
455
-
456
- except Exception as e:
457
- # Non-blocking - log but don't fail
458
- logger.debug(f"MCP pre-warming error (non-critical): {e}")
459
- finally:
460
- # Properly clean up event loop to prevent kqueue warnings
461
- if loop is not None:
462
- try:
463
- # Cancel all running tasks
464
- pending = asyncio.all_tasks(loop)
465
- for task in pending:
466
- task.cancel()
467
- # Wait for tasks to complete cancellation
468
- if pending:
469
- loop.run_until_complete(
470
- asyncio.gather(*pending, return_exceptions=True)
471
- )
472
- except Exception:
473
- pass # Ignore cleanup errors
474
- finally:
475
- loop.close()
476
- # Clear the event loop reference to help with cleanup
477
- asyncio.set_event_loop(None)
478
-
479
- # Run pre-warming in background thread
480
- import threading
481
-
482
- pre_warm_thread = threading.Thread(target=run_pre_warming, daemon=True)
483
- pre_warm_thread.start()
484
-
485
- return
486
-
487
- # Run detailed verification in background if not configured
488
- if not gateway_configured:
489
- # Note: We don't await this to avoid blocking startup
490
- def run_verification():
491
- loop = None
492
- try:
493
- loop = asyncio.new_event_loop()
494
- asyncio.set_event_loop(loop)
495
- results = loop.run_until_complete(verify_mcp_gateway_on_startup())
496
-
497
- # Log results but don't block
498
- from ..core.logger import get_logger
499
-
500
- logger = get_logger("cli")
501
-
502
- if results.get("gateway_configured"):
503
- logger.debug("MCP Gateway verification completed successfully")
504
- else:
505
- logger.debug("MCP Gateway verification completed with warnings")
506
-
507
- except Exception as e:
508
- from ..core.logger import get_logger
509
-
510
- logger = get_logger("cli")
511
- logger.debug(f"MCP Gateway verification failed: {e}")
512
- finally:
513
- # Properly clean up event loop to prevent kqueue warnings
514
- if loop is not None:
515
- try:
516
- # Cancel all running tasks
517
- pending = asyncio.all_tasks(loop)
518
- for task in pending:
519
- task.cancel()
520
- # Wait for tasks to complete cancellation
521
- if pending:
522
- loop.run_until_complete(
523
- asyncio.gather(*pending, return_exceptions=True)
524
- )
525
- except Exception:
526
- pass # Ignore cleanup errors
527
- finally:
528
- loop.close()
529
- # Clear the event loop reference to help with cleanup
530
- asyncio.set_event_loop(None)
531
-
532
- # Run in background thread to avoid blocking startup
533
- import threading
534
-
535
- verification_thread = threading.Thread(target=run_verification, daemon=True)
536
- verification_thread.start()
537
-
538
- except Exception as e:
539
- # Import logger here to avoid circular imports
540
- from ..core.logger import get_logger
541
-
542
- logger = get_logger("cli")
543
- logger.debug(f"Failed to start MCP Gateway verification: {e}")
544
- # Continue execution - MCP gateway issues shouldn't block startup
545
-
546
-
547
- def _ensure_run_attributes(args):
548
- """
549
- Ensure run command attributes exist when defaulting to run.
550
-
551
- WHY: When no command is specified, we default to 'run' but the args object
552
- won't have run-specific attributes from the subparser. This function ensures
553
- they exist with sensible defaults.
554
-
555
- Args:
556
- args: Parsed arguments object to update
557
- """
558
- # Set defaults for run command attributes
559
- args.no_tickets = getattr(args, "no_tickets", False)
560
- args.no_hooks = getattr(args, "no_hooks", False)
561
- args.intercept_commands = getattr(args, "intercept_commands", False)
562
- args.input = getattr(args, "input", None)
563
- args.non_interactive = getattr(args, "non_interactive", False)
564
- args.no_native_agents = getattr(args, "no_native_agents", False)
565
-
566
- # Handle claude_args - if --resume flag is set, add it to claude_args
567
- claude_args = getattr(args, "claude_args", [])
568
- if getattr(args, "resume", False):
569
- # Add --resume to claude_args if not already present
570
- if "--resume" not in claude_args:
571
- claude_args = ["--resume", *claude_args]
572
- args.claude_args = claude_args
573
-
574
- args.launch_method = getattr(args, "launch_method", "exec")
575
- args.websocket = getattr(args, "websocket", False)
576
- args.websocket_port = getattr(args, "websocket_port", 8765)
577
- # CRITICAL: Include mpm_resume attribute for session resumption
578
- args.mpm_resume = getattr(args, "mpm_resume", None)
579
- # Also include monitor and force attributes
580
- args.monitor = getattr(args, "monitor", False)
581
- args.force = getattr(args, "force", False)
582
- args.reload_agents = getattr(args, "reload_agents", False)
583
- # Include dependency checking attributes
584
- args.check_dependencies = getattr(args, "check_dependencies", True)
585
- args.force_check_dependencies = getattr(args, "force_check_dependencies", False)
586
- args.no_prompt = getattr(args, "no_prompt", False)
587
- args.force_prompt = getattr(args, "force_prompt", False)
588
-
589
-
590
- def _execute_command(command: str, args) -> int:
591
- """
592
- Execute the specified command.
593
-
594
- WHY: This function maps command names to their implementations, providing
595
- a single place to manage command routing. Experimental commands are imported
596
- lazily to avoid loading unnecessary code.
597
-
598
- DESIGN DECISION: run_guarded is imported only when needed to maintain
599
- separation between stable and experimental features.
600
-
601
- Args:
602
- command: The command name to execute
603
- args: Parsed command line arguments
604
-
605
- Returns:
606
- Exit code from the command
607
- """
608
- # Handle experimental run-guarded command separately with lazy import
609
- if command == "run-guarded":
610
- # Lazy import to avoid loading experimental code unless needed
611
- from .commands.run_guarded import execute_run_guarded
612
-
613
- result = execute_run_guarded(args)
614
- return result if result is not None else 0
615
-
616
- # Handle mpm-init command with lazy import
617
- if command == "mpm-init":
618
- # Lazy import to avoid loading unless needed
619
- from .commands.mpm_init_handler import manage_mpm_init
620
-
621
- result = manage_mpm_init(args)
622
- return result if result is not None else 0
623
-
624
- # Handle uninstall command with lazy import
625
- if command == "uninstall":
626
- # Lazy import to avoid loading unless needed
627
- from .commands.uninstall import UninstallCommand
628
-
629
- cmd = UninstallCommand()
630
- result = cmd.execute(args)
631
- # Convert CommandResult to exit code
632
- return result.exit_code if result else 0
633
-
634
- # Handle verify command with lazy import
635
- if command == "verify":
636
- # Lazy import to avoid loading unless needed
637
- from .commands.verify import handle_verify
638
-
639
- result = handle_verify(args)
640
- return result if result is not None else 0
641
-
642
- # Map stable commands to their implementations
643
- command_map = {
644
- CLICommands.RUN.value: run_session,
645
- # CLICommands.RUN_GUARDED.value is handled above
646
- CLICommands.TICKETS.value: manage_tickets,
647
- CLICommands.INFO.value: show_info,
648
- CLICommands.AGENTS.value: manage_agents,
649
- CLICommands.AGENT_MANAGER.value: manage_agent_manager,
650
- CLICommands.MEMORY.value: manage_memory,
651
- CLICommands.MONITOR.value: manage_monitor,
652
- CLICommands.DASHBOARD.value: manage_dashboard,
653
- CLICommands.CONFIG.value: manage_config,
654
- CLICommands.CONFIGURE.value: manage_configure,
655
- CLICommands.AGGREGATE.value: aggregate_command,
656
- CLICommands.ANALYZE_CODE.value: manage_analyze_code,
657
- CLICommands.CLEANUP.value: cleanup_memory,
658
- CLICommands.MCP.value: manage_mcp,
659
- CLICommands.DOCTOR.value: run_doctor,
660
- "debug": manage_debug, # Add debug command
661
- "mpm-init": None, # Will be handled separately with lazy import
662
- }
663
-
664
- # Execute command if found
665
- if command in command_map:
666
- result = command_map[command](args)
667
- # Commands may return None (success) or an exit code
668
- return result if result is not None else 0
669
- # Unknown command - this shouldn't happen with argparse
670
- # but we handle it for completeness
671
- print(f"Unknown command: {command}")
672
- return 1
673
-
674
-
675
95
  # For backward compatibility - export main
676
96
  if __name__ == "__main__":
677
97
  sys.exit(main())